From 937a0164b119486b4888057f7d6ef3864605d8ea Mon Sep 17 00:00:00 2001
From: Ary Borenszweig <aborenszweig@manas.com.ar>
Date: Wed, 5 Nov 2014 11:38:11 -0300
Subject: [PATCH] Spec: added `-l` switch to match against a line. Compiler:
 can run `crystal spec file:line`

---
 src/compiler/crystal/command.cr | 18 +++++++++++++++++-
 src/spec/context.cr             | 10 +++++-----
 src/spec/source.cr              | 20 ++++++++++++++++++++
 src/spec/spec.cr                | 24 ++++++++++++++++++------
 4 files changed, 60 insertions(+), 12 deletions(-)
 create mode 100644 src/spec/source.cr

diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr
index 9901361d39..1e0d7736d4 100644
--- a/src/compiler/crystal/command.cr
+++ b/src/compiler/crystal/command.cr
@@ -122,8 +122,24 @@ module Crystal::Command
   end
 
   private def self.run_specs(options)
+    target_filename_and_line_number = options.shift?
+    if target_filename_and_line_number
+      splitted = target_filename_and_line_number.split ':', 2
+      target_filename = splitted[0]
+      cwd = Dir.working_directory
+      if target_filename.starts_with?(cwd)
+        target_filename = "./#{target_filename[cwd.length .. -1]}"
+      end
+      if splitted.length == 2
+        target_line = splitted[1]
+        options << "-l" << target_line
+      end
+    else
+      target_filename = "spec/**"
+    end
+
     compiler = Compiler.new
-    sources = [Compiler::Source.new("spec", %(require "spec/**"))]
+    sources = [Compiler::Source.new("spec", %(require "#{target_filename}"))]
 
     output_filename = tempfile "spec"
 
diff --git a/src/spec/context.cr b/src/spec/context.cr
index 970df25405..bb7f3ebdfb 100644
--- a/src/spec/context.cr
+++ b/src/spec/context.cr
@@ -125,11 +125,11 @@ module Spec
       @@contexts_stack.pop
     end
 
-    def self.matches?(description, pattern)
-      @@contexts_stack.any?(&.matches?(pattern)) || description =~ pattern
+    def self.matches?(description, pattern, line)
+      @@contexts_stack.any?(&.matches?(pattern, line)) || description =~ pattern
     end
 
-    def matches?(pattern)
+    def matches?(pattern, line)
       false
     end
   end
@@ -147,8 +147,8 @@ module Spec
       @parent.report Result.new(result.kind, "#{@description} #{result.description}", result.file, result.line, result.exception)
     end
 
-    def matches?(pattern)
-      @description =~ pattern
+    def matches?(pattern, line)
+      @description =~ pattern || @line == line
     end
   end
 end
diff --git a/src/spec/source.cr b/src/spec/source.cr
new file mode 100644
index 0000000000..6a5b2c9555
--- /dev/null
+++ b/src/spec/source.cr
@@ -0,0 +1,20 @@
+module Spec
+  def self.lines_cache
+    @@lines_cache ||= {} of String => Array(String)
+  end
+
+  def self.read_line(file, line)
+    return nil unless File.exists?(file)
+
+    lines = lines_cache[file] ||= File.read_lines(file)
+    lines[line - 1]?
+  end
+
+  def self.relative_file(file)
+    cwd = Dir.working_directory
+    if file.starts_with?(cwd)
+      file = ".#{file[cwd.length .. -1]}"
+    end
+    file
+  end
+end
diff --git a/src/spec/spec.cr b/src/spec/spec.cr
index 641651573c..54428ec94a 100644
--- a/src/spec/spec.cr
+++ b/src/spec/spec.cr
@@ -48,10 +48,19 @@ module Spec
     @@pattern = Regex.new(Regex.escape(pattern))
   end
 
-  def self.matches?(description)
-    pattern = @@pattern
-    if pattern
-      Spec::RootContext.matches?(description, pattern)
+  @@line = nil
+
+  def self.line=(@@line)
+  end
+
+  def self.matches?(description, file, line)
+    spec_pattern = @@pattern
+    spec_line = @@line
+
+    if line == spec_line
+      return true
+    elsif spec_pattern || spec_line
+      Spec::RootContext.matches?(description, spec_pattern, spec_line)
     else
       true
     end
@@ -77,7 +86,7 @@ end
 
 def it(description, file = __FILE__, line = __LINE__)
   return if Spec.aborted?
-  return unless Spec.matches?(description)
+  return unless Spec.matches?(description, file, line)
 
   Spec.formatter.before_example description
 
@@ -95,7 +104,7 @@ end
 
 def pending(description, file = __FILE__, line = __LINE__, &block)
   return if Spec.aborted?
-  return unless Spec.matches?(description)
+  return unless Spec.matches?(description, file, line)
 
   Spec.formatter.before_example description
 
@@ -115,6 +124,9 @@ OptionParser.parse! do |opts|
   opts.on("-e ", "--example STRING", "run examples whose full nested names include STRING") do |pattern|
     Spec.pattern = pattern
   end
+  opts.on("-l ", "--line LINE", "run examples whose line matches LINE") do |line|
+    Spec.line = line.to_i
+  end
   opts.on("--fail-fast", "abort the run on first failure") do
     Spec.fail_fast = true
   end
-- 
GitLab