cucumber-core 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53a124c933b4697ed6128b46c307617ea4399559
4
- data.tar.gz: e80a381d06f95b5416035f91a004d00cbd68057e
3
+ metadata.gz: 0f56999cf96221b9de93c823a5559efeb56bfb34
4
+ data.tar.gz: 1e955adc2d60f414a53e6775d0e6adc44b4eafa5
5
5
  SHA512:
6
- metadata.gz: 0b1e3a4359e8ccaeef234bfa1492e678145bf1f07320ffbf1e01814b2efd405da9ae10abb31347ac10642e49935183032b1f7a094631219c48d7a4b984c5e6cf
7
- data.tar.gz: e7d0c05ae5e57937e6858c91485157175e69d32332457db403a69d1e8b44e9a99bf95cc68e2bdb1b265ad5decaa69086fbca3a0d48e6cf97ca6cf64b4b54e855
6
+ metadata.gz: 529edb34199a3bea271b93d0f09de3e4b2c24dc7f4c198ff553fe029b7d0b5c1a7a4a18d744586725351d5e1f9795c6caec1e8e35b64e2c77eab858ad3d8052e
7
+ data.tar.gz: 42aa0c463801d3aaba42d95c2bbdd0519a8d5344da0ef6c594d7a6bd704329947c5265aea47e3eee601e20c043fac8ca31b25afc5e75a9d1630b85ead562bd80
data/HISTORY.md CHANGED
@@ -1,6 +1,14 @@
1
1
  ## [In Git](https://github.com/cucumber/cucumber-ruby-core/compare/v1.2.0...master)
2
2
 
3
- ...
3
+ ### New Features
4
+
5
+ * Add factory method to Cucumber::Core::Ast::Location that uses the output from Proc#source_location (@brasmusson)
6
+ * Integrate Gherkin3 parser (@brassmusson)
7
+
8
+ ### Bugfixes
9
+
10
+ * Make sure that `after_test_step` is sent also when a test step is interrupted by (a timeout in) an around hook ([cucumber/cucumber-ruby#909](https://github.com/cucumber/cucumber-ruby/issues/909) @brasmusson)
11
+ * Improve the check that a test_step quacks like a Cucumber::Core::Test::Step ([95](https://github.com/cucumber/cucumber-ruby-core/issues/95) @brasmusson)
4
12
 
5
13
  ## [v1.2.0](https://github.com/cucumber/cucumber-ruby-core/compare/v1.1.3...v1.2.0)
6
14
 
@@ -68,7 +76,7 @@
68
76
 
69
77
  ### Bugfixes
70
78
 
71
- * Handle empty feature files (#[77](https://github.com/cucumber/cucumber-ruby-core/pull/77), [cucumber/cucumber#771](https://github.com/cucumber/cucumber/issues/771) [@brasmusson](https://github.com/brasmusson))
79
+ * Handle empty feature files (#[77](https://github.com/cucumber/cucumber-ruby-core/pull/77), [cucumber/cucumber-ruby#771](https://github.com/cucumber/cucumber-ruby/issues/771) [@brasmusson](https://github.com/brasmusson))
72
80
  * Run after hooks in reverse order (#[69](https://github.com/cucumber/cucumber-ruby-core/pull/69) [@erran](https://github.com/erran))
73
81
 
74
82
  ## [v1.0.0.beta.3](https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0.beta.2...v1.0.0.beta.3)
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.license = "MIT"
15
15
  s.required_ruby_version = ">= 1.9.3"
16
16
 
17
- s.add_dependency 'gherkin', '~> 2.12.0'
17
+ s.add_dependency 'gherkin3', '~> 3.1.0'
18
18
 
19
19
  s.add_development_dependency 'bundler', '>= 1.3.5'
20
20
  s.add_development_dependency 'rake', '>= 0.9.2'
@@ -10,9 +10,7 @@ module Cucumber
10
10
  include HasLocation
11
11
  include DescribesItself
12
12
 
13
- def initialize(gherkin_statement, language, location, comments, keyword, name, description, raw_steps)
14
- @gherkin_statement = gherkin_statement
15
- @language = language
13
+ def initialize(location, comments, keyword, name, description, raw_steps)
16
14
  @location = location
17
15
  @comments = comments
18
16
  @keyword = keyword
@@ -21,11 +19,10 @@ module Cucumber
21
19
  @raw_steps = raw_steps
22
20
  end
23
21
 
24
- attr_reader :language, :description, :raw_steps
25
- private :language, :raw_steps
22
+ attr_reader :description, :raw_steps
23
+ private :raw_steps
26
24
 
27
25
  attr_reader :comments, :keyword, :location
28
- attr_reader :gherkin_statement
29
26
 
30
27
  def children
31
28
  raw_steps
@@ -1,6 +1,3 @@
1
- require 'gherkin/rubify'
2
- require 'gherkin/lexer/i18n_lexer'
3
- require 'gherkin/formatter/escaping'
4
1
  require 'cucumber/core/ast/describes_itself'
5
2
  require 'cucumber/core/ast/location'
6
3
 
@@ -30,15 +27,13 @@ module Cucumber
30
27
  include DescribesItself
31
28
  include HasLocation
32
29
 
33
- include ::Gherkin::Rubify
34
-
35
30
  # Creates a new instance. +raw+ should be an Array of Array of String
36
31
  # or an Array of Hash
37
32
  # You don't typically create your own DataTable objects - Cucumber will do
38
33
  # it internally and pass them to your Step Definitions.
39
34
  #
40
- def initialize(raw, location)
41
- raw = ensure_array_of_array(rubify(raw))
35
+ def initialize(rows, location)
36
+ raw = ensure_array_of_array(rows)
42
37
  verify_rows_are_same_length(raw)
43
38
  @raw = raw.freeze
44
39
  @location = location
@@ -5,7 +5,11 @@ module Cucumber
5
5
  def describe_to(visitor, *args)
6
6
  visitor.send(description_for_visitors, self, *args) do |child_visitor|
7
7
  children.each do |child|
8
- child.describe_to(child_visitor, *args)
8
+ begin
9
+ child.describe_to(child_visitor, *args)
10
+ rescue => e
11
+ raise e.class, "Failed describing child of #{self.inspect} - #{e.message}", e.backtrace
12
+ end
9
13
  end
10
14
  end
11
15
  self
@@ -25,8 +25,8 @@ module Cucumber
25
25
 
26
26
  attr_reader :content_type, :content
27
27
 
28
- def initialize(string, content_type, location)
29
- @content = string
28
+ def initialize(content, content_type, location)
29
+ @content = content
30
30
  @content_type = content_type
31
31
  @location = location
32
32
  super @content
@@ -11,8 +11,7 @@ module Cucumber
11
11
  include HasLocation
12
12
  include DescribesItself
13
13
 
14
- def initialize(gherkin_statement, location, comments, tags, keyword, name, description, header, example_rows)
15
- @gherkin_statement = gherkin_statement
14
+ def initialize(location, comments, tags, keyword, name, description, header, example_rows)
16
15
  @location = location
17
16
  @comments = comments
18
17
  @tags = tags
@@ -23,7 +22,7 @@ module Cucumber
23
22
  @example_rows = example_rows
24
23
  end
25
24
 
26
- attr_reader :gherkin_statement, :location, :comments, :tags, :keyword,
25
+ attr_reader :location, :tags, :keyword, :comments,
27
26
  :header, :example_rows
28
27
 
29
28
  private
@@ -109,6 +108,7 @@ module Cucumber
109
108
  end
110
109
  end
111
110
  end
111
+ class Examples < ExamplesTable; end
112
112
  end
113
113
  end
114
114
  end
@@ -1,6 +1,7 @@
1
1
  require 'cucumber/core/ast/describes_itself'
2
2
  require 'cucumber/core/ast/names'
3
3
  require 'cucumber/core/ast/location'
4
+ require 'gherkin3/dialect'
4
5
 
5
6
  module Cucumber
6
7
  module Core
@@ -11,12 +12,11 @@ module Cucumber
11
12
  include HasLocation
12
13
  include DescribesItself
13
14
 
14
- attr_reader :gherkin_statement, :language, :location, :background,
15
+ attr_reader :language, :location, :background,
15
16
  :comments, :tags, :keyword, :description,
16
17
  :feature_elements
17
18
 
18
- def initialize(gherkin_statement, language, location, background, comments, tags, keyword, name, description, feature_elements)
19
- @gherkin_statement = gherkin_statement
19
+ def initialize(language, location, background, comments, tags, keyword, name, description, scenario_definitions)
20
20
  @language = language
21
21
  @location = location
22
22
  @background = background
@@ -25,7 +25,7 @@ module Cucumber
25
25
  @keyword = keyword
26
26
  @name = name
27
27
  @description = description
28
- @feature_elements = feature_elements
28
+ @feature_elements = scenario_definitions
29
29
  end
30
30
 
31
31
  def children
@@ -34,7 +34,7 @@ module Cucumber
34
34
 
35
35
  def short_name
36
36
  first_line = name.split(/\n/)[0]
37
- if first_line =~ /#{language.keywords('feature')}:(.*)/
37
+ if first_line =~ /#{language.feature_keywords}:(.*)/
38
38
  $1.strip
39
39
  else
40
40
  first_line
@@ -65,6 +65,15 @@ module Cucumber
65
65
  self
66
66
  end
67
67
  end
68
+
69
+ class LanguageDelegator < SimpleDelegator
70
+ attr_reader :iso_code
71
+
72
+ def initialize(iso_code, obj)
73
+ super(obj)
74
+ @iso_code = iso_code
75
+ end
76
+ end
68
77
  end
69
78
  end
70
79
  end
@@ -13,9 +13,25 @@ module Cucumber
13
13
  def_delegator :filepath, :same_as?
14
14
  def_delegator :filepath, :filename, :file
15
15
 
16
- def self.of_caller
17
- file, raw_line = *caller[1].split(':')[0..1]
18
- new(file, raw_line.to_i)
16
+ def self.of_caller(additional_depth = 0)
17
+ from_file_colon_line(*caller[1 + additional_depth])
18
+ end
19
+
20
+ def self.from_file_colon_line(file_colon_line)
21
+ file, raw_line = file_colon_line.match(/(.*):(\d+)/)[1..2]
22
+ from_source_location(file, raw_line.to_i)
23
+ end
24
+
25
+ def self.from_source_location(file, line)
26
+ file = File.expand_path(file)
27
+ pwd = File.expand_path(Dir.pwd)
28
+ pwd.force_encoding(file.encoding)
29
+ if file.index(pwd)
30
+ file = file[pwd.length+1..-1]
31
+ elsif file =~ /.*\/gems\/(.*\.rb)$/
32
+ file = $1
33
+ end
34
+ new(file, line)
19
35
  end
20
36
 
21
37
  def initialize(filepath, raw_lines=WILDCARD)
@@ -63,9 +79,6 @@ module Cucumber
63
79
  attr_reader :line
64
80
 
65
81
  def initialize(raw_data)
66
- if Cucumber::JRUBY && raw_data.is_a?(::Java::GherkinFormatterModel::Range)
67
- raw_data = Range.new(raw_data.first, raw_data.last)
68
- end
69
82
  super Array(raw_data).to_set
70
83
  @line = data.first
71
84
  end
@@ -10,15 +10,14 @@ module Cucumber
10
10
  include HasLocation
11
11
  include DescribesItself
12
12
 
13
- attr_reader :gherkin_statement, :language, :location, :comments, :keyword, :name, :multiline_arg
13
+ attr_reader :language, :location, :comments, :keyword, :name, :multiline_arg
14
14
 
15
- def initialize(gherkin_statement, language, location, comments, keyword, name, multiline_arg)
16
- @gherkin_statement, @language, @location, @comments, @keyword, @name, @multiline_arg = gherkin_statement, language, location, comments, keyword, name, multiline_arg
17
- @language || raise("Language is required!")
15
+ def initialize(language, location, comments, keyword, text, multiline_arg)
16
+ @language, @location, @comments, @keyword, @name, @multiline_arg = language, location, comments, keyword, text, multiline_arg
18
17
  end
19
18
 
20
19
  def to_step(row)
21
- Ast::ExpandedOutlineStep.new(self, gherkin_statement, language, row.location, comments, keyword, row.expand(name), replace_multiline_arg(row))
20
+ Ast::ExpandedOutlineStep.new(self, language, row.location, comments, keyword, row.expand(name), replace_multiline_arg(row))
22
21
  end
23
22
 
24
23
  def inspect
@@ -11,23 +11,19 @@ module Cucumber
11
11
  include HasLocation
12
12
  include DescribesItself
13
13
 
14
- attr_reader :gherkin_statement, :language, :location, :background,
15
- :comments, :tags, :feature_tags, :keyword,
14
+ attr_reader :location, :background,
15
+ :comments, :tags, :keyword,
16
16
  :description, :raw_steps
17
17
  private :raw_steps
18
18
 
19
- def initialize(gherkin_statement, language, location, background, comments, tags, feature_tags, keyword, name, description, raw_steps)
20
- @gherkin_statement = gherkin_statement
21
- @language = language
19
+ def initialize(location, comments, tags, keyword, name, description, steps)
22
20
  @location = location
23
- @background = background
24
21
  @comments = comments
25
22
  @tags = tags
26
- @feature_tags = feature_tags
27
23
  @keyword = keyword
28
24
  @name = name
29
25
  @description = description
30
- @raw_steps = raw_steps
26
+ @raw_steps = steps
31
27
  end
32
28
 
33
29
  def children
@@ -1,6 +1,5 @@
1
1
  require 'cucumber/core/ast/names'
2
2
  require 'cucumber/core/ast/location'
3
- require 'cucumber/core/ast/empty_background'
4
3
  require 'cucumber/core/ast/describes_itself'
5
4
 
6
5
  module Cucumber
@@ -13,24 +12,19 @@ module Cucumber
13
12
 
14
13
  MissingExamples = Class.new(StandardError)
15
14
 
16
- attr_reader :gherkin_statement, :language, :background, :comments,
17
- :tags, :feature_tags, :keyword,
15
+ attr_reader :comments, :tags, :keyword,
18
16
  :steps, :examples_tables, :line
19
- private :language, :background, :feature_tags, :line
17
+ private :line
20
18
 
21
- def initialize(gherkin_statement, language, location, background, comments, tags, feature_tags, keyword, name, description, steps, examples_tables)
22
- @gherkin_statement = gherkin_statement
23
- @language = language
19
+ def initialize(location, comments, tags, keyword, name, description, steps, examples)
24
20
  @location = location
25
- @background = background
26
21
  @comments = comments
27
22
  @tags = tags
28
- @feature_tags = feature_tags
29
23
  @keyword = keyword
30
24
  @name = name
31
25
  @description = description
32
26
  @steps = steps
33
- @examples_tables = examples_tables
27
+ @examples_tables = examples
34
28
  end
35
29
 
36
30
  private
@@ -8,10 +8,10 @@ module Cucumber
8
8
  include HasLocation
9
9
  include DescribesItself
10
10
 
11
- attr_reader :keyword, :name, :language, :comments, :exception, :multiline_arg, :gherkin_statement
11
+ attr_reader :keyword, :name, :language, :comments, :exception, :multiline_arg
12
12
 
13
- def initialize(gherkin_statement, language, location, comments, keyword, name, multiline_arg)
14
- @gherkin_statement, @language, @location, @comments, @keyword, @name, @multiline_arg = gherkin_statement, language, location, comments, keyword, name, multiline_arg
13
+ def initialize(language, location, comments, keyword, name, multiline_arg)
14
+ @language, @location, @comments, @keyword, @name, @multiline_arg = language, location, comments, keyword, name, multiline_arg
15
15
  end
16
16
 
17
17
  def to_sexp
@@ -23,9 +23,9 @@ module Cucumber
23
23
  end
24
24
 
25
25
  def actual_keyword(previous_step_keyword = nil)
26
- if [language.keywords('and'), language.keywords('but')].flatten.uniq.include? keyword
26
+ if [language.and_keywords, language.but_keywords].flatten.uniq.include? keyword
27
27
  if previous_step_keyword.nil?
28
- language.keywords('given').reject{|kw| kw == '* '}[0]
28
+ language.given_keywords.reject{|kw| kw == '* '}[0]
29
29
  else
30
30
  previous_step_keyword
31
31
  end
@@ -52,14 +52,15 @@ module Cucumber
52
52
 
53
53
  class ExpandedOutlineStep < Step
54
54
 
55
- def initialize(outline_step, gherkin_statement, language, location, comments, keyword, name, multiline_arg)
56
- @outline_step, @gherkin_statement, @language, @location, @comments, @keyword, @name, @multiline_arg = outline_step, gherkin_statement, language, location, comments, keyword, name, multiline_arg
55
+ def initialize(outline_step, language, location, comments, keyword, name, multiline_arg)
56
+ @outline_step, @language, @location, @comments, @keyword, @name, @multiline_arg = outline_step, language, location, comments, keyword, name, multiline_arg
57
57
  end
58
58
 
59
59
  alias :self_match_locations? :match_locations?
60
60
 
61
61
  def match_locations?(queried_locations)
62
- self_match_locations?(queried_locations) or @outline_step.match_locations?(queried_locations)
62
+ return true if self_match_locations?(queried_locations)
63
+ @outline_step.match_locations?(queried_locations)
63
64
  end
64
65
 
65
66
  alias :step_backtrace_line :backtrace_line
@@ -1,6 +1,5 @@
1
1
  require 'cucumber/core/ast'
2
2
  require 'cucumber/core/platform'
3
- require 'gherkin/rubify'
4
3
 
5
4
  module Cucumber
6
5
  module Core
@@ -9,339 +8,364 @@ module Cucumber
9
8
  # Gherkin parser.
10
9
  class AstBuilder
11
10
 
12
- def initialize(path)
13
- @path = path
14
- @feature_builder = nil
11
+ def initialize(uri)
12
+ @uri = uri
15
13
  end
16
14
 
17
- def result
18
- return Ast::NullFeature.new unless @feature_builder
19
- @feature_builder.result(language)
20
- end
21
-
22
- def language=(language)
23
- @language = language
24
- end
25
-
26
- def uri(uri)
27
- @path = uri
28
- end
29
-
30
- def feature(node)
31
- @feature_builder = FeatureBuilder.new(file, node)
32
- end
33
-
34
- def background(node)
35
- builder = BackgroundBuilder.new(file, node)
36
- @feature_builder.background_builder = builder
37
- @current = builder
38
- end
39
-
40
- def scenario(node)
41
- builder = ScenarioBuilder.new(file, node)
42
- @feature_builder.add_child builder
43
- @current = builder
44
- end
45
-
46
- def scenario_outline(node)
47
- builder = ScenarioOutlineBuilder.new(file, node)
48
- @feature_builder.add_child builder
49
- @current = builder
50
- end
51
-
52
- def examples(node)
53
- @current.add_examples file, node
54
- end
55
-
56
- def step(node)
57
- @current.add_step file, node
58
- end
59
-
60
- def eof
61
- end
62
-
63
- def syntax_error(state, event, legal_events, line)
64
- # raise "SYNTAX ERROR"
15
+ def feature(attributes)
16
+ FeatureBuilder.new(file, attributes).result
65
17
  end
66
18
 
67
19
  private
68
20
 
69
- def language
70
- @language || raise("Language has not been set")
71
- end
72
-
73
21
  def file
74
- @path
22
+ @uri
75
23
  end
76
24
 
77
25
  class Builder
78
- attr_reader :file, :node
79
- private :file, :node
26
+ attr_reader :file, :attributes, :comments, :line
27
+ private :file, :attributes, :comments, :line
80
28
 
81
- def initialize(file, node)
29
+ def initialize(file, attributes)
82
30
  @file = file
83
- @node = node
31
+ @attributes = rubify_keys(attributes.dup)
32
+ @comments = []
33
+ @line = @attributes[:location][:line]
34
+ end
35
+
36
+ def handle_comments(comments)
37
+ remaining_comments = []
38
+ comments.each do |comment|
39
+ if line > comment.location.line
40
+ @comments << comment
41
+ else
42
+ remaining_comments << comment
43
+ end
44
+ end
45
+ children.each { |child| remaining_comments = child.handle_comments(remaining_comments) }
46
+ remaining_comments
84
47
  end
85
48
 
86
49
  private
87
50
 
51
+ def keyword
52
+ attributes[:keyword]
53
+ end
54
+
55
+ def name
56
+ attributes[:name]
57
+ end
58
+
59
+ def description
60
+ attributes[:description] ||= ""
61
+ end
62
+
88
63
  def tags
89
- node.tags.map do |tag|
64
+ attributes[:tags].map do |tag|
90
65
  Ast::Tag.new(
91
- Ast::Location.new(file, tag.line),
92
- tag.name)
66
+ Ast::Location.new(file, tag[:location][:line]),
67
+ tag[:name])
93
68
  end
94
69
  end
95
70
 
96
71
  def location
97
- Ast::Location.new(file, node.line)
72
+ Ast::Location.new(file, attributes[:location][:line])
98
73
  end
99
74
 
100
- def comments
101
- node.comments.map do |comment|
102
- Ast::Comment.new(
103
- Ast::Location.new(file, comment.line),
104
- comment.value
105
- )
75
+ def children
76
+ []
77
+ end
78
+
79
+ def rubify_keys(hash)
80
+ hash.keys.each do |key|
81
+ if key.downcase != key
82
+ hash[underscore(key).to_sym] = hash.delete(key)
83
+ end
106
84
  end
85
+ return hash
86
+ end
87
+
88
+ def underscore(string)
89
+ string.to_s.gsub(/::/, '/').
90
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
91
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
92
+ tr("-", "_").
93
+ downcase
107
94
  end
108
95
  end
109
96
 
110
97
  class FeatureBuilder < Builder
111
- attr_accessor :background_builder
112
- private :background_builder
98
+ attr_reader :language, :background_builder, :scenario_definition_builders
113
99
 
114
100
  def initialize(*)
115
101
  super
116
- @background_builder = nil
102
+ @language = Ast::LanguageDelegator.new(attributes[:language], ::Gherkin3::Dialect.for(attributes[:language]))
103
+ @background_builder = BackgroundBuilder.new(file, attributes[:background]) if attributes[:background]
104
+ @scenario_definition_builders = attributes[:scenario_definitions].map do |sd|
105
+ sd[:type] == :Scenario ? ScenarioBuilder.new(file, sd) : ScenarioOutlineBuilder.new(file, sd)
106
+ end
117
107
  end
118
108
 
119
- def result(language)
120
- background = background(language)
109
+ def result
110
+ handle_comments(all_comments)
121
111
  Ast::Feature.new(
122
- node,
123
112
  language,
124
113
  location,
125
114
  background,
126
115
  comments,
127
116
  tags,
128
- node.keyword,
129
- node.name.lstrip,
130
- node.description.rstrip,
131
- children.map { |builder| builder.result(background, language, tags) }
117
+ keyword,
118
+ name,
119
+ description,
120
+ scenario_definitions
132
121
  )
133
122
  end
134
123
 
135
- def add_child(child)
136
- children << child
124
+ private
125
+
126
+ def background
127
+ return Ast::EmptyBackground.new unless background_builder
128
+ background_builder.result(language)
137
129
  end
138
130
 
139
- def children
140
- @children ||= []
131
+ def scenario_definitions
132
+ scenario_definition_builders.map { |builder| builder.result(language) }
141
133
  end
142
134
 
143
- private
135
+ def all_comments
136
+ attributes[:comments].map do |comment|
137
+ Ast::Comment.new(
138
+ Ast::Location.new(file, comment[:location][:line]),
139
+ comment[:text]
140
+ )
141
+ end
142
+ end
144
143
 
145
- def background(language)
146
- return Ast::EmptyBackground.new unless background_builder
147
- @background ||= background_builder.result(language)
144
+ def children
145
+ (background_builder ? [background_builder] : []) + scenario_definition_builders
148
146
  end
149
147
  end
150
148
 
151
149
  class BackgroundBuilder < Builder
150
+ attr_reader :step_builders
151
+
152
+ def initialize(*)
153
+ super
154
+ @step_builders = attributes[:steps].map { |step| StepBuilder.new(file, step) }
155
+ end
156
+
152
157
  def result(language)
153
158
  Ast::Background.new(
154
- node,
155
- language,
156
159
  location,
157
160
  comments,
158
- node.keyword,
159
- node.name,
160
- node.description,
161
+ keyword,
162
+ name,
163
+ description,
161
164
  steps(language)
162
165
  )
163
166
  end
164
167
 
165
- def add_step(file, node)
166
- step_builders << ScenarioBuilder::StepBuilder.new(file, node)
167
- end
168
-
169
- private
170
-
171
168
  def steps(language)
172
- step_builders.map { |step_builder| step_builder.result(language) }
169
+ step_builders.map { |builder| builder.result(language) }
173
170
  end
174
171
 
175
- def step_builders
176
- @step_builders ||= []
172
+ def children
173
+ step_builders
177
174
  end
178
-
179
175
  end
180
176
 
181
177
  class ScenarioBuilder < Builder
182
- def result(background, language, feature_tags)
178
+ attr_reader :step_builders
179
+
180
+ def initialize(*)
181
+ super
182
+ @step_builders = attributes[:steps].map { |step| StepBuilder.new(file, step) }
183
+ end
184
+
185
+ def result(language)
183
186
  Ast::Scenario.new(
184
- node,
185
- language,
186
187
  location,
187
- background,
188
188
  comments,
189
189
  tags,
190
- feature_tags,
191
- node.keyword,
192
- node.name,
193
- node.description,
190
+ keyword,
191
+ name,
192
+ description,
194
193
  steps(language)
195
194
  )
196
195
  end
197
196
 
198
- def add_step(file, node)
199
- step_builders << StepBuilder.new(file, node)
197
+ def steps(language)
198
+ step_builders.map { |builder| builder.result(language) }
200
199
  end
201
200
 
202
- private
201
+ def children
202
+ step_builders
203
+ end
204
+ end
203
205
 
204
- def steps(language)
205
- step_builders.map { |step_builder| step_builder.result(language) }
206
+ class StepBuilder < Builder
207
+ attr_reader :multiline_argument_builder
208
+
209
+ def initialize(*)
210
+ super
211
+ @multiline_argument_builder = attributes[:argument] ? argument_builder(attributes[:argument]) : nil
206
212
  end
207
213
 
208
- def step_builders
209
- @step_builders ||= []
214
+ def result(language)
215
+ Ast::Step.new(
216
+ language,
217
+ location,
218
+ comments,
219
+ keyword,
220
+ attributes[:text],
221
+ multiline_argument
222
+ )
210
223
  end
211
224
 
212
- class StepBuilder < Builder
213
- def result(language)
214
- Ast::Step.new(
215
- node,
216
- language,
217
- location,
218
- comments,
219
- node.keyword,
220
- node.name,
221
-
222
- MultilineArgument.from(node.doc_string || node.rows, location)
223
- )
224
- end
225
+ def multiline_argument
226
+ return Ast::EmptyMultilineArgument.new unless multiline_argument_builder
227
+ multiline_argument_builder.result
228
+ end
229
+
230
+ def children
231
+ return [] unless multiline_argument_builder
232
+ [multiline_argument_builder]
233
+ end
234
+
235
+ private
236
+
237
+ def argument_builder(attributes)
238
+ attributes[:type] == :DataTable ? DataTableBuilder.new(file, attributes) : DocStringBuilder.new(file, attributes)
239
+ end
240
+ end
241
+
242
+ class OutlineStepBuilder < StepBuilder
243
+ def result(language)
244
+ Ast::OutlineStep.new(
245
+ language,
246
+ location,
247
+ comments,
248
+ keyword,
249
+ attributes[:text],
250
+ multiline_argument
251
+ )
225
252
  end
226
253
  end
227
254
 
228
255
  class ScenarioOutlineBuilder < Builder
229
- def result(background, language, feature_tags)
230
- raise ParseError.new("Missing Examples section for Scenario Outline at #{location}") if examples_builders.empty?
256
+ attr_reader :step_builders, :example_builders
257
+
258
+ def initialize(*)
259
+ super
260
+ @step_builders = attributes[:steps].map { |step| OutlineStepBuilder.new(file, step) }
261
+ @example_builders = attributes[:examples].map { |example| ExamplesTableBuilder.new(file, example) }
262
+ end
263
+
264
+ def result(language)
231
265
  Ast::ScenarioOutline.new(
232
- node,
233
- language,
234
266
  location,
235
- background,
236
267
  comments,
237
268
  tags,
238
- feature_tags,
239
- node.keyword,
240
- node.name,
241
- node.description,
269
+ keyword,
270
+ name,
271
+ description,
242
272
  steps(language),
243
- examples_tables(language)
273
+ examples(language)
244
274
  )
245
275
  end
246
276
 
247
- def add_examples(file, node)
248
- examples_builders << ExamplesTableBuilder.new(file, node)
277
+ def steps(language)
278
+ step_builders.map { |builder| builder.result(language) }
249
279
  end
250
280
 
251
- def add_step(file, node)
252
- step_builders << StepBuilder.new(file, node)
281
+ def examples(language)
282
+ example_builders.map { |builder| builder.result(language) }
253
283
  end
254
284
 
255
- private
256
-
257
- def steps(language)
258
- step_builders.map { |step_builder| step_builder.result(language) }
285
+ def children
286
+ step_builders + example_builders
259
287
  end
288
+ end
260
289
 
261
- def step_builders
262
- @step_builders ||= []
263
- end
290
+ class ExamplesTableBuilder < Builder
291
+ attr_reader :header_builder, :example_rows_builders
264
292
 
265
- def examples_tables(language)
266
- examples_builders.map { |examples_builder| examples_builder.result(language) }
293
+ def initialize(*)
294
+ super
295
+ @header_builder = HeaderBuilder.new(file, attributes[:table_header])
296
+ @example_rows_builders = attributes[:table_body].map do |row_attributes|
297
+ ExampleRowBuilder.new(file, row_attributes)
298
+ end
267
299
  end
268
300
 
269
- def examples_builders
270
- @examples_builders ||= []
301
+ def result(language)
302
+ Ast::Examples.new(
303
+ location,
304
+ comments,
305
+ tags,
306
+ keyword,
307
+ name,
308
+ description,
309
+ header,
310
+ example_rows(language)
311
+ )
271
312
  end
272
313
 
273
- class ExamplesTableBuilder < Builder
314
+ private
274
315
 
275
- def result(language)
276
- Ast::ExamplesTable.new(
277
- node,
278
- location,
279
- comments,
280
- tags,
281
- node.keyword,
282
- node.name,
283
- node.description,
284
- header,
285
- example_rows(language)
286
- )
287
- end
316
+ def header
317
+ @header = header_builder.result
318
+ end
288
319
 
289
- private
320
+ def example_rows(language)
321
+ example_rows_builders.each.with_index.map { |builder, index| builder.result(language, header, index) }
322
+ end
290
323
 
291
- def header
292
- row = node.rows[0]
293
- Ast::ExamplesTable::Header.new(row.cells, location, row_comments(row))
324
+ class HeaderBuilder < Builder
325
+ def result
326
+ cells = attributes[:cells].map { |c| c[:value] }
327
+ Ast::ExamplesTable::Header.new(cells, location, comments)
294
328
  end
329
+ end
295
330
 
296
- def example_rows(language)
297
- _, *raw_examples = *node.rows
298
- raw_examples.each_with_index.map do |row, index|
299
- header.build_row(row.cells, index + 1, location.on_line(row.line), language, row_comments(row))
300
- end
301
- end
331
+ def children
332
+ [header_builder] + example_rows_builders
333
+ end
302
334
 
303
- def row_comments(row)
304
- row.comments.map do |comment|
305
- Ast::Comment.new(
306
- Ast::Location.new(file, comment.line),
307
- comment.value
308
- )
309
- end
335
+ class ExampleRowBuilder < Builder
336
+ def result(language, header, index)
337
+ cells = attributes[:cells].map { |c| c[:value] }
338
+ header.build_row(cells, index + 1, location, language, comments)
310
339
  end
311
340
  end
341
+ end
312
342
 
313
- class StepBuilder < Builder
314
- def result(language)
315
- Ast::OutlineStep.new(
316
- node,
317
- language,
318
- location,
319
- comments,
320
- node.keyword,
321
- node.name,
322
- MultilineArgument.from(node.doc_string || node.rows, location)
323
- )
324
- end
343
+ class DataTableBuilder < Builder
344
+ def result
345
+ Ast::DataTable.new(
346
+ rows,
347
+ location
348
+ )
349
+ end
350
+
351
+ def rows
352
+ attributes[:rows] = attributes[:rows].map { |r| r[:cells].map { |c| c[:value] } }
325
353
  end
326
354
  end
327
355
 
328
- module MultilineArgument
329
- class << self
330
- include ::Gherkin::Rubify
331
-
332
- def from(argument, parent_location)
333
- return Ast::EmptyMultilineArgument.new unless argument
334
- argument = rubify(argument)
335
- case argument
336
- when ::Gherkin::Formatter::Model::DocString
337
- Ast::DocString.new(argument.value, argument.content_type, parent_location.on_line(argument.line_range))
338
- when Array
339
- location = parent_location.on_line(argument.first.line..argument.last.line)
340
- Ast::DataTable.new(argument.map{|row| row.cells}, location)
341
- else
342
- raise ArgumentError, "Don't know how to convert #{argument.inspect} into a MultilineArgument"
343
- end
344
- end
356
+ class DocStringBuilder < Builder
357
+ def result
358
+ Ast::DocString.new(
359
+ attributes[:content],
360
+ attributes[:content_type],
361
+ doc_string_location
362
+ )
363
+ end
364
+
365
+ def doc_string_location
366
+ start_line = attributes[:location][:line]
367
+ end_line = start_line + attributes[:content].each_line.to_a.length + 1
368
+ Ast::Location.new(file, start_line..end_line)
345
369
  end
346
370
  end
347
371