cuke_modeler 3.2.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -3
  3. data/LICENSE.txt +1 -1
  4. data/README.md +7 -7
  5. data/cuke_modeler.gemspec +35 -16
  6. data/lib/cuke_modeler.rb +1 -2
  7. data/lib/cuke_modeler/adapters/gherkin_10_adapter.rb +2 -1
  8. data/lib/cuke_modeler/adapters/gherkin_11_adapter.rb +2 -1
  9. data/lib/cuke_modeler/adapters/gherkin_12_adapter.rb +2 -1
  10. data/lib/cuke_modeler/adapters/gherkin_13_adapter.rb +2 -1
  11. data/lib/cuke_modeler/adapters/gherkin_14_adapter.rb +2 -1
  12. data/lib/cuke_modeler/adapters/gherkin_15_adapter.rb +13 -0
  13. data/lib/cuke_modeler/adapters/gherkin_16_adapter.rb +13 -0
  14. data/lib/cuke_modeler/adapters/gherkin_17_adapter.rb +13 -0
  15. data/lib/cuke_modeler/adapters/gherkin_9_adapter.rb +261 -243
  16. data/lib/cuke_modeler/containing.rb +47 -90
  17. data/lib/cuke_modeler/described.rb +40 -2
  18. data/lib/cuke_modeler/models/background.rb +11 -12
  19. data/lib/cuke_modeler/models/cell.rb +13 -8
  20. data/lib/cuke_modeler/models/comment.rb +5 -6
  21. data/lib/cuke_modeler/models/directory.rb +13 -18
  22. data/lib/cuke_modeler/models/doc_string.rb +10 -8
  23. data/lib/cuke_modeler/models/example.rb +63 -46
  24. data/lib/cuke_modeler/models/feature.rb +23 -17
  25. data/lib/cuke_modeler/models/feature_file.rb +5 -8
  26. data/lib/cuke_modeler/models/model.rb +2 -3
  27. data/lib/cuke_modeler/models/outline.rb +19 -15
  28. data/lib/cuke_modeler/models/row.rb +15 -8
  29. data/lib/cuke_modeler/models/rule.rb +11 -10
  30. data/lib/cuke_modeler/models/scenario.rb +17 -13
  31. data/lib/cuke_modeler/models/step.rb +40 -19
  32. data/lib/cuke_modeler/models/table.rb +9 -7
  33. data/lib/cuke_modeler/models/tag.rb +9 -6
  34. data/lib/cuke_modeler/named.rb +5 -2
  35. data/lib/cuke_modeler/nested.rb +22 -19
  36. data/lib/cuke_modeler/parsed.rb +8 -1
  37. data/lib/cuke_modeler/parsing.rb +38 -29
  38. data/lib/cuke_modeler/sourceable.rb +8 -1
  39. data/lib/cuke_modeler/stepped.rb +8 -1
  40. data/lib/cuke_modeler/taggable.rb +9 -2
  41. data/lib/cuke_modeler/version.rb +1 -1
  42. metadata +91 -36
@@ -1,7 +1,9 @@
1
+ # I'll take extra class length due to extra helper methods over having fewer but more complex methods
2
+ # rubocop:disable Metrics/ClassLength
3
+
1
4
  module CukeModeler
2
5
 
3
6
  # A class modeling an example table of an outline.
4
-
5
7
  class Example < Model
6
8
 
7
9
  include Parsing
@@ -27,10 +29,10 @@ module CukeModeler
27
29
 
28
30
  super(source_text)
29
31
 
30
- if source_text
31
- parsed_example_data = parse_source(source_text)
32
- populate_example(self, parsed_example_data)
33
- end
32
+ return unless source_text
33
+
34
+ parsed_example_data = parse_source(source_text)
35
+ populate_example(self, parsed_example_data)
34
36
  end
35
37
 
36
38
  # Adds a row to the example table. The row can be given as a Hash of
@@ -40,42 +42,39 @@ module CukeModeler
40
42
  raise('Cannot add a row. No parameters have been set.') if rows.empty?
41
43
 
42
44
  # A quick 'deep clone' so that the input isn't modified
43
- row = Marshal::load(Marshal.dump(row))
44
-
45
- case
46
- when row.is_a?(Array)
47
- # 'stringify' input
48
- row.collect! { |value| value.to_s }
49
-
50
- @rows << Row.new("|#{row.join('|')}|")
51
- when row.is_a?(Hash)
52
- # 'stringify' input
53
- row = row.inject({}) { |hash, (key, value)| hash[key.to_s] = value.to_s; hash }
54
-
55
- @rows << Row.new("|#{ordered_row_values(row).join('|')}|")
56
- else
57
- raise(ArgumentError, "Can only add row from a Hash or an Array but received #{row.class}")
58
- end
45
+ row = Marshal.load(Marshal.dump(row))
46
+
47
+ values = if row.is_a?(Array)
48
+ row
49
+ elsif row.is_a?(Hash)
50
+ # There is no guarantee that the user built up their hash with the keys in the same order as
51
+ # the parameter row and so the values have to be ordered by us. Additionally, the hash needs
52
+ # to have string keys in order for #order_row_values to work
53
+ ordered_row_values(stringify_keys(row))
54
+ else
55
+ raise(ArgumentError, "Can only add row from a Hash or an Array but received #{row.class}")
56
+ end
57
+
58
+ @rows << Row.new("|#{values.join('|')}|")
59
59
  end
60
60
 
61
61
  # Removes a row from the example table. The row can be given as a Hash of
62
62
  # parameters and their corresponding values or as an Array of values
63
63
  # which will be assigned in order.
64
64
  def remove_row(row_removed)
65
- return unless argument_rows
66
-
67
- case
68
- when row_removed.is_a?(Array)
69
- location = argument_rows.index { |row| row.cells.collect { |cell| cell.value } == row_removed.collect { |value| value.strip } }
70
- when row_removed.is_a?(Hash)
71
- # Note: the hash value order has to be manually calculated because Ruby 1.8.7 does not have ordered
72
- # hash keys. Alternatively, the hash may have simply been built up 'willy nilly' by the user instead
73
- # of being built up in order according to the parameter order.
74
- location = argument_rows.index { |row| row.cells.collect { |cell| cell.value } == ordered_row_values(row_removed.each_value { |value| value.strip! }) }
75
- else
76
- raise(ArgumentError, "Can only remove row from a Hash or an Array but received #{row_removed.class}")
77
- end
78
-
65
+ return if argument_rows.empty?
66
+
67
+ values = if row_removed.is_a?(Array)
68
+ row_removed
69
+ elsif row_removed.is_a?(Hash)
70
+ # There is no guarantee that the user built up their hash with the keys in the same order as
71
+ # the parameter row and so the values have to be ordered by us.
72
+ ordered_row_values(row_removed)
73
+ else
74
+ raise(ArgumentError, "Can only remove row from a Hash or an Array but received #{row_removed.class}")
75
+ end
76
+
77
+ location = index_for_values(values.map(&:to_s).map(&:strip))
79
78
  @rows.delete_at(location + 1) if location
80
79
  end
81
80
 
@@ -91,7 +90,7 @@ module CukeModeler
91
90
 
92
91
  # Returns the parameters of the example table
93
92
  def parameters
94
- parameter_row ? parameter_row.cells.collect { |cell| cell.value } : []
93
+ parameter_row ? parameter_row.cells.map(&:value) : []
95
94
  end
96
95
 
97
96
  # Returns the model objects that belong to this model.
@@ -99,30 +98,38 @@ module CukeModeler
99
98
  rows + tags
100
99
  end
101
100
 
101
+ # Building strings just isn't pretty
102
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
103
+
102
104
  # Returns a string representation of this model. For an example model,
103
105
  # this will be Gherkin text that is equivalent to the example being modeled.
104
106
  def to_s
105
107
  text = ''
106
108
 
107
- text << tag_output_string + "\n" unless tags.empty?
109
+ text << "#{tag_output_string}\n" unless tags.empty?
108
110
  text << "#{@keyword}:#{name_output_string}"
109
- text << "\n" + description_output_string unless (description.nil? || description.empty?)
110
- text << "\n" unless (rows.empty? || description.nil? || description.empty?)
111
- text << "\n" + parameters_output_string if parameter_row
112
- text << "\n" + rows_output_string unless argument_rows.empty?
111
+ text << "\n#{description_output_string}" unless no_description_to_output?
112
+ text << "\n" unless rows.empty? || no_description_to_output?
113
+ text << "\n#{parameters_output_string}" if parameter_row
114
+ text << "\n#{rows_output_string}" unless argument_rows.empty?
113
115
 
114
116
  text
115
117
  end
116
118
 
119
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
120
+
117
121
 
118
122
  private
119
123
 
120
124
 
121
125
  def parse_source(source_text)
122
- base_file_string = "# language: #{Parsing.dialect}\n#{dialect_feature_keyword}: Fake feature to parse\n#{dialect_outline_keyword}:\n#{dialect_step_keyword} fake step\n"
126
+ base_file_string = "# language: #{Parsing.dialect}
127
+ #{dialect_feature_keyword}: Fake feature to parse
128
+ #{dialect_outline_keyword}:
129
+ #{dialect_step_keyword} fake step\n"
123
130
  source_text = base_file_string + source_text
124
131
 
125
- parsed_file = Parsing::parse_text(source_text, 'cuke_modeler_stand_alone_example.feature')
132
+ parsed_file = Parsing.parse_text(source_text, 'cuke_modeler_stand_alone_example.feature')
126
133
 
127
134
  parsed_file['feature']['elements'].first['examples'].first
128
135
  end
@@ -135,7 +142,7 @@ module CukeModeler
135
142
  text = ''
136
143
 
137
144
  unless parameter_row.nil?
138
- text << " |"
145
+ text << ' |'
139
146
  parameter_row.cells.count.times { |index| text << " #{string_for(parameter_row.cells, index)} |" }
140
147
  end
141
148
 
@@ -148,7 +155,7 @@ module CukeModeler
148
155
  unless argument_rows.empty?
149
156
 
150
157
  argument_rows.each do |row|
151
- text << " |"
158
+ text << ' |'
152
159
  row.cells.count.times { |index| text << " #{string_for(row.cells, index)} |" }
153
160
  text << "\n"
154
161
  end
@@ -164,8 +171,18 @@ module CukeModeler
164
171
  end
165
172
 
166
173
  def ordered_row_values(row_hash)
167
- parameter_row.cells.collect { |cell| cell.value }.collect { |parameter| row_hash[parameter] }
174
+ parameter_row.cells.map(&:value).collect { |parameter| row_hash[parameter] }
175
+ end
176
+
177
+ def stringify_keys(hash)
178
+ hash.each_with_object({}) { |(key, value), new_hash| new_hash[key.to_s] = value }
179
+ end
180
+
181
+ def index_for_values(values)
182
+ argument_rows.index { |row| row.cells.map(&:value) == values }
168
183
  end
169
184
 
170
185
  end
171
186
  end
187
+
188
+ # rubocop:enable Metrics/ClassLength
@@ -1,7 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
3
  # A class modeling a feature in a Cucumber suite.
4
-
5
4
  class Feature < Model
6
5
 
7
6
  include Parsed
@@ -33,17 +32,19 @@ module CukeModeler
33
32
 
34
33
  super(source_text)
35
34
 
36
- if source_text
37
- parsed_feature_data = parse_source(source_text)
38
- populate_feature(self, parsed_feature_data)
39
- end
35
+ return unless source_text
36
+
37
+ parsed_feature_data = parse_source(source_text)
38
+ populate_feature(self, parsed_feature_data)
40
39
  end
41
40
 
42
41
  # Returns *true* if the feature contains a background, *false* otherwise.
43
- def has_background?
42
+ def background?
44
43
  !@background.nil?
45
44
  end
46
45
 
46
+ alias has_background? background?
47
+
47
48
  # Returns the scenario models contained in the feature.
48
49
  def scenarios
49
50
  @tests.select { |test| test.is_a? Scenario }
@@ -60,11 +61,11 @@ module CukeModeler
60
61
  # single set of test values, such as an individual scenario or one example row
61
62
  # of an outline.
62
63
  def test_case_count
63
- scenarios.count + outlines.reduce(0) { |outline_sum, outline|
64
- outline_sum += outline.examples.reduce(0) { |example_sum, example|
65
- example_sum += example.argument_rows.count
66
- }
67
- }
64
+ scenarios.count + outlines.reduce(0) do |outline_sum, outline|
65
+ outline_sum + outline.examples.reduce(0) do |example_sum, example|
66
+ example_sum + example.argument_rows.count
67
+ end
68
+ end
68
69
  end
69
70
 
70
71
  # Returns the model objects that belong to this model.
@@ -75,27 +76,32 @@ module CukeModeler
75
76
  models
76
77
  end
77
78
 
79
+ # Building strings just isn't pretty
80
+ # rubocop:disable Metrics/AbcSize
81
+
78
82
  # Returns a string representation of this model. For a feature model,
79
83
  # this will be Gherkin text that is equivalent to the feature being modeled.
80
84
  def to_s
81
85
  text = ''
82
86
 
83
- text << tag_output_string + "\n" unless tags.empty?
87
+ text << "#{tag_output_string}\n" unless tags.empty?
84
88
  text << "#{@keyword}:#{name_output_string}"
85
- text << "\n" + description_output_string unless (description.nil? || description.empty?)
86
- text << "\n\n" + background_output_string if background
87
- text << "\n\n" + tests_output_string unless tests.empty?
88
- text << "\n\n" + rules_output_string unless rules.empty?
89
+ text << "\n#{description_output_string}" unless no_description_to_output?
90
+ text << "\n\n#{background_output_string}" if background
91
+ text << "\n\n#{tests_output_string}" unless tests.empty?
92
+ text << "\n\n#{rules_output_string}" unless rules.empty?
89
93
 
90
94
  text
91
95
  end
92
96
 
97
+ # rubocop:enable Metrics/AbcSize
98
+
93
99
 
94
100
  private
95
101
 
96
102
 
97
103
  def parse_source(source_text)
98
- parsed_file = Parsing::parse_text(source_text, 'cuke_modeler_stand_alone_feature.feature')
104
+ parsed_file = Parsing.parse_text(source_text, 'cuke_modeler_stand_alone_feature.feature')
99
105
 
100
106
  parsed_file['feature']
101
107
  end
@@ -1,7 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
3
  # A class modeling a feature file in a Cucumber suite.
4
-
5
4
  class FeatureFile < Model
6
5
 
7
6
  include Parsed
@@ -25,13 +24,11 @@ module CukeModeler
25
24
 
26
25
  super(file_path)
27
26
 
28
- if file_path
29
- raise(ArgumentError, "Unknown file: #{file_path.inspect}") unless File.exists?(file_path)
30
-
31
- processed_feature_file_data = process_feature_file(file_path)
32
- populate_featurefile(self, processed_feature_file_data)
33
- end
27
+ return unless file_path
28
+ raise(ArgumentError, "Unknown file: #{file_path.inspect}") unless File.exist?(file_path)
34
29
 
30
+ processed_feature_file_data = process_feature_file(file_path)
31
+ populate_featurefile(self, processed_feature_file_data)
35
32
  end
36
33
 
37
34
  # Returns the name of the modeled feature file.
@@ -57,7 +54,7 @@ module CukeModeler
57
54
  def process_feature_file(file_path)
58
55
  source_text = IO.read(file_path)
59
56
 
60
- feature_file_data = Parsing::parse_text(source_text, file_path)
57
+ feature_file_data = Parsing.parse_text(source_text, file_path)
61
58
  feature_file_data = feature_file_data.merge({ 'path' => file_path })
62
59
 
63
60
  feature_file_data
@@ -1,7 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
3
  # A class modeling an element of a Cucumber suite.
4
-
5
4
  class Model
6
5
 
7
6
  include Nested
@@ -11,7 +10,8 @@ module CukeModeler
11
10
  # Creates a new Model object and, if *source_text* is provided,
12
11
  # populates the object.
13
12
  def initialize(source_text = nil)
14
- raise(ArgumentError, "Can only create models from Strings but was given a #{source_text.class}.") if source_text && !source_text.is_a?(String)
13
+ error_message = "Can only create models from Strings but was given a #{source_text.class}."
14
+ raise(ArgumentError, error_message) if source_text && !source_text.is_a?(String)
15
15
 
16
16
  # This should be overridden by a child class
17
17
  end
@@ -24,7 +24,6 @@ module CukeModeler
24
24
 
25
25
  # Returns the model objects that belong to this model.
26
26
  def children
27
- # This should be overridden by a child class
28
27
  []
29
28
  end
30
29
 
@@ -1,7 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
3
  # A class modeling an individual outline in a Cucumber suite.
4
-
5
4
  class Outline < Model
6
5
 
7
6
  include Parsing
@@ -29,17 +28,17 @@ module CukeModeler
29
28
 
30
29
  super(source_text)
31
30
 
32
- if source_text
33
- parsed_outline_data = parse_source(source_text)
34
- populate_outline(self, parsed_outline_data)
35
- end
31
+ return unless source_text
32
+
33
+ parsed_outline_data = parse_source(source_text)
34
+ populate_outline(self, parsed_outline_data)
36
35
  end
37
36
 
38
37
  # Returns *true* if the two models have equivalent steps and *false* otherwise.
39
- def ==(other_model)
40
- return false unless other_model.respond_to?(:steps)
38
+ def ==(other)
39
+ return false unless other.respond_to?(:steps)
41
40
 
42
- steps == other_model.steps
41
+ steps == other.steps
43
42
  end
44
43
 
45
44
  # Returns the model objects that belong to this model.
@@ -47,21 +46,26 @@ module CukeModeler
47
46
  examples + steps + tags
48
47
  end
49
48
 
49
+ # Building strings just isn't pretty
50
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
51
+
50
52
  # Returns a string representation of this model. For an outline model,
51
53
  # this will be Gherkin text that is equivalent to the outline being modeled.
52
54
  def to_s
53
55
  text = ''
54
56
 
55
- text << tag_output_string + "\n" unless tags.empty?
57
+ text << "#{tag_output_string}\n" unless tags.empty?
56
58
  text << "#{@keyword}:#{name_output_string}"
57
- text << "\n" + description_output_string unless (description.nil? || description.empty?)
58
- text << "\n" unless (steps.empty? || description.nil? || description.empty?)
59
- text << "\n" + steps_output_string unless steps.empty?
60
- text << "\n\n" + examples_output_string unless examples.empty?
59
+ text << "\n#{description_output_string}" unless no_description_to_output?
60
+ text << "\n" unless steps.empty? || no_description_to_output?
61
+ text << "\n#{steps_output_string}" unless steps.empty?
62
+ text << "\n\n#{examples_output_string}" unless examples.empty?
61
63
 
62
64
  text
63
65
  end
64
66
 
67
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
68
+
65
69
 
66
70
  private
67
71
 
@@ -70,13 +74,13 @@ module CukeModeler
70
74
  base_file_string = "# language: #{Parsing.dialect}\n#{dialect_feature_keyword}: Fake feature to parse\n"
71
75
  source_text = base_file_string + source_text
72
76
 
73
- parsed_file = Parsing::parse_text(source_text, 'cuke_modeler_stand_alone_outline.feature')
77
+ parsed_file = Parsing.parse_text(source_text, 'cuke_modeler_stand_alone_outline.feature')
74
78
 
75
79
  parsed_file['feature']['elements'].first
76
80
  end
77
81
 
78
82
  def examples_output_string
79
- examples.empty? ? '' : examples.collect { |example| example.to_s }.join("\n\n")
83
+ examples.empty? ? '' : examples.join("\n\n")
80
84
  end
81
85
 
82
86
  end
@@ -1,7 +1,6 @@
1
1
  module CukeModeler
2
2
 
3
3
  # A class modeling a single row of a step table or example table.
4
-
5
4
  class Row < Model
6
5
 
7
6
  include Sourceable
@@ -19,16 +18,21 @@ module CukeModeler
19
18
 
20
19
  super(source_text)
21
20
 
22
- if source_text
23
- parsed_row_data = parse_source(source_text)
24
- populate_row(self, parsed_row_data)
25
- end
21
+ return unless source_text
22
+
23
+ parsed_row_data = parse_source(source_text)
24
+ populate_row(self, parsed_row_data)
25
+ end
26
+
27
+ # Returns the model objects that belong to this model.
28
+ def children
29
+ @cells
26
30
  end
27
31
 
28
32
  # Returns a string representation of this model. For a row model,
29
33
  # this will be Gherkin text that is equivalent to the row being modeled.
30
34
  def to_s
31
- text_cells = cells.collect { |cell| cell.to_s }
35
+ text_cells = cells.map(&:to_s)
32
36
 
33
37
  "| #{text_cells.join(' | ')} |"
34
38
  end
@@ -38,10 +42,13 @@ module CukeModeler
38
42
 
39
43
 
40
44
  def parse_source(source_text)
41
- base_file_string = "# language: #{Parsing.dialect}\n#{dialect_feature_keyword}: Fake feature to parse\n#{dialect_scenario_keyword}:\n#{dialect_step_keyword} fake step\n"
45
+ base_file_string = "# language: #{Parsing.dialect}
46
+ #{dialect_feature_keyword}: Fake feature to parse
47
+ #{dialect_scenario_keyword}:
48
+ #{dialect_step_keyword} fake step\n"
42
49
  source_text = base_file_string + source_text
43
50
 
44
- parsed_file = Parsing::parse_text(source_text, 'cuke_modeler_stand_alone_row.feature')
51
+ parsed_file = Parsing.parse_text(source_text, 'cuke_modeler_stand_alone_row.feature')
45
52
 
46
53
  parsed_file['feature']['elements'].first['steps'].first['table']['rows'].first
47
54
  end