cuke_modeler 3.1.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -4
  3. data/README.md +3 -2
  4. data/cuke_modeler.gemspec +25 -16
  5. data/lib/cuke_modeler.rb +2 -1
  6. data/lib/cuke_modeler/adapters/gherkin_10_adapter.rb +2 -1
  7. data/lib/cuke_modeler/adapters/gherkin_11_adapter.rb +2 -1
  8. data/lib/cuke_modeler/adapters/gherkin_12_adapter.rb +2 -1
  9. data/lib/cuke_modeler/adapters/gherkin_13_adapter.rb +2 -1
  10. data/lib/cuke_modeler/adapters/gherkin_14_adapter.rb +2 -1
  11. data/lib/cuke_modeler/adapters/gherkin_15_adapter.rb +13 -0
  12. data/lib/cuke_modeler/adapters/gherkin_16_adapter.rb +13 -0
  13. data/lib/cuke_modeler/adapters/gherkin_9_adapter.rb +267 -223
  14. data/lib/cuke_modeler/containing.rb +57 -89
  15. data/lib/cuke_modeler/described.rb +40 -1
  16. data/lib/cuke_modeler/models/background.rb +11 -11
  17. data/lib/cuke_modeler/models/cell.rb +13 -7
  18. data/lib/cuke_modeler/models/comment.rb +5 -5
  19. data/lib/cuke_modeler/models/directory.rb +13 -17
  20. data/lib/cuke_modeler/models/doc_string.rb +10 -7
  21. data/lib/cuke_modeler/models/example.rb +63 -45
  22. data/lib/cuke_modeler/models/feature.rb +37 -19
  23. data/lib/cuke_modeler/models/feature_file.rb +5 -7
  24. data/lib/cuke_modeler/models/model.rb +2 -2
  25. data/lib/cuke_modeler/models/outline.rb +19 -14
  26. data/lib/cuke_modeler/models/row.rb +15 -7
  27. data/lib/cuke_modeler/models/rule.rb +101 -0
  28. data/lib/cuke_modeler/models/scenario.rb +17 -12
  29. data/lib/cuke_modeler/models/step.rb +40 -18
  30. data/lib/cuke_modeler/models/table.rb +9 -6
  31. data/lib/cuke_modeler/models/tag.rb +9 -5
  32. data/lib/cuke_modeler/named.rb +5 -1
  33. data/lib/cuke_modeler/nested.rb +22 -18
  34. data/lib/cuke_modeler/parsed.rb +8 -0
  35. data/lib/cuke_modeler/parsing.rb +38 -28
  36. data/lib/cuke_modeler/sourceable.rb +8 -0
  37. data/lib/cuke_modeler/stepped.rb +8 -0
  38. data/lib/cuke_modeler/taggable.rb +9 -1
  39. data/lib/cuke_modeler/version.rb +1 -1
  40. data/testing/cucumber/features/modeling/feature_modeling.feature +28 -7
  41. data/testing/cucumber/features/modeling/feature_output.feature +45 -23
  42. data/testing/cucumber/features/modeling/rule_modeling.feature +108 -0
  43. data/testing/cucumber/features/modeling/rule_output.feature +111 -0
  44. metadata +61 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3287a6e2f6cc9ce0269ce039535fe37bbe9be5bc6251c170efd87bdfa6d25b5e
4
- data.tar.gz: 9413347eaf273b881e62dd792a1a7110ccf30d89ee7ed1620ae53c882008310b
3
+ metadata.gz: 599071d6967852941776e6bcfe690c2fcdb9f294c99d497dd5f18b69305ca0ab
4
+ data.tar.gz: 7d51fc3ad42c67520a5c374d84ee178a1d6f1aae40d1208888d9c58bfdcef8d6
5
5
  SHA512:
6
- metadata.gz: cc3d0ee5ca02ca1b81fd3784e471488952c8e5fe88401ce12ea0e10360b32a667b80934db8480aeae7936463ddc9c04ff0a02702d88ef0eaf6957b69a16680c5
7
- data.tar.gz: 4af05a8b47ab76911764ebef5f10524c525a8c88852193bd928ac868e779cb4244fa540f7978df08fa58af602e53f0b6db5fb515d216f40117e58f939d49f8c9
6
+ metadata.gz: 1c9c233ece2d62ca17ae1a370918651c4b97ff96cff408059de93cd9bffd7ce9eed78b728fd56ea5c84d4e7234dc3977af9904e932924f2ec23ff9c94a3a09f9
7
+ data.tar.gz: fa19be7a982e76b4ce9fae430f633b5933a90ec44e6414da94c7d2ea2d668a526f26c4d7b8393354496ec241a73d3bf947bc5380da22266ca2a55073bfcb5430
@@ -8,6 +8,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  Nothing yet...
10
10
 
11
+ ## [3.6.0] - 2021-01-05
12
+
13
+ ### Added
14
+ - Ruby 3.x is now supported
15
+ - All models are now `Enumerable`. However, some methods such as `Enumerable#max` and `Enumerable#sort` do not work
16
+ because models do not meaningfully compare to each other.
17
+
18
+ ### Deprecated
19
+ - `Model#each_descendant` and `Model#each_model` will be removed on the next major release. `Model#each` and methods in
20
+ the `Enumerable` module now provide this kind of functionality.
21
+
22
+ ### Fixed
23
+ - `Row#children` now returns the row's `Cell` models instead of returning an empty array
24
+
25
+
26
+ ## [3.5.0] - 2020-12-19
27
+
28
+ ### Added
29
+ - Support added for more versions of the `cucumber-gherkin` gem
30
+ - 16.x
31
+
32
+ ## [3.4.0] - 2020-09-02
33
+
34
+ ### Added
35
+ - `Feature#has_background?` and `Rule#has_background?` now both have a more conventional name via the alias `#background?`
36
+
37
+ ## [3.3.0] - 2020-08-15
38
+
39
+ ### Added
40
+ - Support added for more versions of the `cucumber-gherkin` gem
41
+ - 15.x
42
+
43
+ ## [3.2.0] - 2020-07-27
44
+
45
+ ### Added
46
+ - The `Rule` keyword is now a modeled element.
47
+
48
+ ### Deprecated
49
+ - `Feature#test_case_count` will be removed on the next major release. It's a random analysis method in what is
50
+ otherwise a purely abstraction layer library. The [CQL](https://github.com/enkessler/cql) gem is better suited to such tasks.
51
+
11
52
  ## [3.1.0] - 2020-06-28
12
53
 
13
54
  ### Added
@@ -16,8 +57,8 @@ Nothing yet...
16
57
 
17
58
  ### Fixed
18
59
  - Text is converted to UTF-8 encoding before being passed to the underlying Gherkin gem. This is due to UTF-8 being
19
- the only encoding supported by Gherkin. The `gherkin` gem did the conversion automatically but the `cucumber-gherkin`
20
- gem does not.
60
+ the only encoding supported by Gherkin. The `gherkin` gem did the conversion automatically and so this conversion
61
+ was not necessary previously but the `cucumber-gherkin` gem does not do any automatic conversion.
21
62
 
22
63
  ## [3.0.0] - 2020-06-08
23
64
 
@@ -303,7 +344,7 @@ Nothing yet...
303
344
 
304
345
  ### Fixed
305
346
 
306
- - Fixed a bug that was causing object comparison using #== to not
347
+ - Fixed a bug that was causing object comparison using `#==` to not
307
348
  work when comparing some models to other types of objects.
308
349
 
309
350
 
@@ -314,7 +355,12 @@ Nothing yet...
314
355
  - Initial release
315
356
 
316
357
 
317
- [Unreleased]: https://github.com/enkessler/cuke_modeler/compare/v3.1.0...HEAD
358
+ [Unreleased]: https://github.com/enkessler/cuke_modeler/compare/v3.6.0...HEAD
359
+ [3.6.0]: https://github.com/enkessler/cuke_modeler/compare/v3.5.0...v3.6.0
360
+ [3.5.0]: https://github.com/enkessler/cuke_modeler/compare/v3.4.0...v3.5.0
361
+ [3.4.0]: https://github.com/enkessler/cuke_modeler/compare/v3.3.0...v3.4.0
362
+ [3.3.0]: https://github.com/enkessler/cuke_modeler/compare/v3.2.0...v3.3.0
363
+ [3.2.0]: https://github.com/enkessler/cuke_modeler/compare/v3.1.0...v3.2.0
318
364
  [3.1.0]: https://github.com/enkessler/cuke_modeler/compare/v3.0.0...v3.1.0
319
365
  [3.0.0]: https://github.com/enkessler/cuke_modeler/compare/v2.1.0...v3.0.0
320
366
  [2.1.0]: https://github.com/enkessler/cuke_modeler/compare/v2.0.0...v2.1.0
data/README.md CHANGED
@@ -82,13 +82,14 @@ and setting their attributes afterward.
82
82
  One could, if so inclined, use this method to dynamically edit or even create
83
83
  an entire test suite!
84
84
 
85
- For more information on the different models and how to use them, see the
85
+ For more information on the different models (which more or less have the same relation
86
+ to each other as described in the AST [here](https://github.com/cucumber/cucumber/tree/master/gherkin#ast)) and how to use them, see the
86
87
  [documentation](https://app.cucumber.pro/projects/cuke_modeler).
87
88
 
88
89
  ## Modeling dialects other than English
89
90
 
90
91
  The modeling functionality provided by this gem will work with any dialect that
91
- is supported by the **gherkin** gem. For modeling at the feature level or higher,
92
+ is supported by the **cucumber-gherkin** gem. For modeling at the feature level or higher,
92
93
  no additional effort is needed because the `# language` header at the top of a
93
94
  feature already indicates that a non-default dialect is being used.
94
95
 
@@ -1,17 +1,21 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'cuke_modeler/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "cuke_modeler"
6
+ spec.name = 'cuke_modeler'
8
7
  spec.version = CukeModeler::VERSION
9
- spec.authors = ["Eric Kessler"]
10
- spec.email = ["morrow748@gmail.com"]
11
- spec.summary = %q{A gem providing functionality to model Gherkin based test suites.}
12
- spec.description = %q{This gem facilitates modeling a test suite that is written in Gherkin (e.g. Cucumber, SpecFlow, Lettuce, etc.). It does this by providing an abstraction layer on top of the Abstract Syntax Tree that the 'cucumber-gherkin' gem generates when parsing features, as well as providing models for feature files and directories in order to be able to have a fully traversable model tree of a test suite's structure. These models can then be analyzed or manipulated more easily than the underlying AST layer.}
8
+ spec.authors = ['Eric Kessler']
9
+ spec.email = ['morrow748@gmail.com']
10
+ spec.summary = 'A gem providing functionality to model Gherkin based test suites.'
11
+ spec.description = ['This gem facilitates modeling a test suite that is written in Gherkin (e.g. Cucumber, ',
12
+ 'SpecFlow, Lettuce, etc.). It does this by providing an abstraction layer on top of the ',
13
+ "Abstract Syntax Tree that the 'cucumber-gherkin' gem generates when parsing features, ",
14
+ 'as well as providing models for feature files and directories in order to be able to ',
15
+ "have a fully traversable model tree of a test suite's structure. These models can then ",
16
+ 'be analyzed or manipulated more easily than the underlying AST layer.'].join("\n")
13
17
  spec.homepage = 'https://github.com/enkessler/cuke_modeler'
14
- spec.license = "MIT"
18
+ spec.license = 'MIT'
15
19
 
16
20
  # Specify which files should be added to the gem when it is released.
17
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -20,19 +24,24 @@ Gem::Specification.new do |spec|
20
24
  source_controlled_files.keep_if { |file| file =~ %r{^(lib|testing/cucumber/features)} }
21
25
  source_controlled_files + ['README.md', 'LICENSE.txt', 'CHANGELOG.md', 'cuke_modeler.gemspec']
22
26
  end
23
- spec.require_paths = ["lib"]
27
+ spec.require_paths = ['lib']
24
28
 
25
- spec.required_ruby_version = '>= 2.3', '< 3.0'
29
+ spec.required_ruby_version = '>= 2.3', '< 4.0'
26
30
 
27
- spec.add_runtime_dependency 'cucumber-gherkin', '< 15.0'
31
+ spec.add_runtime_dependency 'cucumber-gherkin', '< 17.0'
28
32
 
29
33
  spec.add_development_dependency 'bundler', '< 3.0'
30
- spec.add_development_dependency "rake", '< 13.0.0'
31
- spec.add_development_dependency 'cucumber', '< 5.0.0'
32
- spec.add_development_dependency 'rspec', '~> 3.0'
33
- spec.add_development_dependency 'simplecov', '<= 0.16.1'
34
- spec.add_development_dependency 'racatt', '~> 1.0'
35
34
  spec.add_development_dependency 'coveralls', '< 1.0.0'
35
+ # Cucumber 4.x is the earliest version to use cucumber-gherkin
36
+ spec.add_development_dependency 'cucumber', '>= 4.0.0', '< 6.0.0'
37
+ spec.add_development_dependency 'racatt', '~> 1.0'
36
38
  spec.add_development_dependency 'rainbow', '< 4.0.0'
39
+ spec.add_development_dependency 'rake', '< 14.0.0'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
41
+ # RuboCop drops Ruby 2.3 support after this version and we need to maintain Ruby 2.3 compatibility when writing code
42
+ # for this gem
43
+ spec.add_development_dependency 'rubocop', '< 0.82.0'
44
+ # Coveralls gem does not support any newer version than this
45
+ spec.add_development_dependency 'simplecov', '<= 0.16.1'
37
46
  spec.add_development_dependency 'test-unit', '< 4.0.0'
38
47
  end
@@ -4,7 +4,7 @@ module CukeModeler
4
4
  end
5
5
 
6
6
 
7
- require "cuke_modeler/version"
7
+ require 'cuke_modeler/version'
8
8
 
9
9
  require 'cuke_modeler/parsing'
10
10
  require 'cuke_modeler/containing'
@@ -19,6 +19,7 @@ require 'cuke_modeler/models/model'
19
19
  require 'cuke_modeler/models/feature_file'
20
20
  require 'cuke_modeler/models/directory'
21
21
  require 'cuke_modeler/models/feature'
22
+ require 'cuke_modeler/models/rule'
22
23
  require 'cuke_modeler/models/background'
23
24
  require 'cuke_modeler/models/scenario'
24
25
  require 'cuke_modeler/models/outline'
@@ -4,7 +4,8 @@ require_relative 'gherkin_9_adapter'
4
4
  module CukeModeler
5
5
 
6
6
  # NOT A PART OF THE PUBLIC API
7
- # An adapter that can convert the output of version 10.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 10.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
8
9
 
9
10
  class Gherkin10Adapter < Gherkin9Adapter
10
11
 
@@ -4,7 +4,8 @@ require_relative 'gherkin_9_adapter'
4
4
  module CukeModeler
5
5
 
6
6
  # NOT A PART OF THE PUBLIC API
7
- # An adapter that can convert the output of version 11.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 11.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
8
9
 
9
10
  class Gherkin11Adapter < Gherkin9Adapter
10
11
 
@@ -4,7 +4,8 @@ require_relative 'gherkin_9_adapter'
4
4
  module CukeModeler
5
5
 
6
6
  # NOT A PART OF THE PUBLIC API
7
- # An adapter that can convert the output of version 12.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 12.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
8
9
 
9
10
  class Gherkin12Adapter < Gherkin9Adapter
10
11
 
@@ -4,7 +4,8 @@ require_relative 'gherkin_9_adapter'
4
4
  module CukeModeler
5
5
 
6
6
  # NOT A PART OF THE PUBLIC API
7
- # An adapter that can convert the output of version 13.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 13.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
8
9
 
9
10
  class Gherkin13Adapter < Gherkin9Adapter
10
11
 
@@ -4,7 +4,8 @@ require_relative 'gherkin_9_adapter'
4
4
  module CukeModeler
5
5
 
6
6
  # NOT A PART OF THE PUBLIC API
7
- # An adapter that can convert the output of version 14.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 14.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
8
9
 
9
10
  class Gherkin14Adapter < Gherkin9Adapter
10
11
 
@@ -0,0 +1,13 @@
1
+ require_relative 'gherkin_9_adapter'
2
+
3
+
4
+ module CukeModeler
5
+
6
+ # NOT A PART OF THE PUBLIC API
7
+ # An adapter that can convert the output of version 15.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
9
+
10
+ class Gherkin15Adapter < Gherkin9Adapter
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'gherkin_9_adapter'
2
+
3
+
4
+ module CukeModeler
5
+
6
+ # NOT A PART OF THE PUBLIC API
7
+ # An adapter that can convert the output of version 16.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
9
+
10
+ class Gherkin16Adapter < Gherkin9Adapter
11
+
12
+ end
13
+ end
@@ -1,320 +1,364 @@
1
+ # Some things just aren't going to get better due to the inherent complexity of the AST
2
+ # rubocop:disable Metrics/ClassLength, Metrics/AbcSize, Metrics/MethodLength
3
+
1
4
  module CukeModeler
2
5
 
3
6
  # NOT A PART OF THE PUBLIC API
4
- # An adapter that can convert the output of version 9.x of the *cucumber-gherkin* gem into input that is consumable by this gem.
7
+ # An adapter that can convert the output of version 9.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
5
9
 
6
10
  class Gherkin9Adapter
7
11
 
8
12
  # Adapts the given AST into the shape that this gem expects
9
- def adapt(parsed_ast)
10
- # Saving off the original data
11
- parsed_ast['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_ast))
12
-
13
- # Removing parsed data for child elements in order to avoid duplicating data
14
- parsed_ast['cuke_modeler_parsing_data'][:feature] = nil if parsed_ast['cuke_modeler_parsing_data'][:feature]
15
- parsed_ast['cuke_modeler_parsing_data'][:comments] = nil if parsed_ast['cuke_modeler_parsing_data'][:comments]
13
+ def adapt(ast)
14
+ adapted_ast = {}
16
15
 
17
- parsed_ast['comments'] = []
18
- if parsed_ast[:comments]
19
- parsed_ast[:comments].each do |comment|
20
- adapt_comment!(comment)
21
- end
22
- parsed_ast['comments'].concat(parsed_ast.delete(:comments))
23
- end
16
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
17
+ save_original_data(adapted_ast, ast)
18
+ clear_child_elements(adapted_ast, [[:feature],
19
+ [:comments]])
24
20
 
25
- adapt_feature!(parsed_ast[:feature]) if parsed_ast[:feature]
26
- parsed_ast['feature'] = parsed_ast.delete(:feature)
21
+ adapted_ast['comments'] = adapt_comments(ast)
22
+ adapted_ast['feature'] = adapt_feature(ast[:feature])
27
23
 
28
- parsed_ast
24
+ adapted_ast
29
25
  end
30
26
 
31
27
  # Adapts the AST sub-tree that is rooted at the given feature node.
32
- def adapt_feature!(parsed_feature)
33
- # Saving off the original data
34
- parsed_feature['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_feature))
28
+ def adapt_feature(feature_ast)
29
+ return nil unless feature_ast
35
30
 
36
- # Removing parsed data for child elements in order to avoid duplicating data
37
- parsed_feature['cuke_modeler_parsing_data'][:tags] = nil if parsed_feature['cuke_modeler_parsing_data'][:tags]
38
- parsed_feature['cuke_modeler_parsing_data'][:children] = nil if parsed_feature['cuke_modeler_parsing_data'][:children]
31
+ adapted_feature = {}
39
32
 
40
- parsed_feature['keyword'] = parsed_feature.delete(:keyword)
41
- parsed_feature['name'] = parsed_feature.delete(:name)
42
- parsed_feature['description'] = parsed_feature.delete(:description) || ''
43
- parsed_feature['line'] = parsed_feature.delete(:location)[:line]
33
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
34
+ save_original_data(adapted_feature, feature_ast)
35
+ clear_child_elements(adapted_feature, [[:tags],
36
+ [:children]])
44
37
 
45
- parsed_feature['elements'] = []
46
- if parsed_feature[:children]
47
- adapt_child_elements!(parsed_feature[:children])
48
- parsed_feature['elements'].concat(parsed_feature.delete(:children))
49
- end
38
+ adapted_feature['keyword'] = feature_ast[:keyword]
39
+ adapted_feature['name'] = feature_ast[:name]
40
+ adapted_feature['description'] = feature_ast[:description] || ''
41
+ adapted_feature['line'] = feature_ast[:location][:line]
50
42
 
51
- parsed_feature['tags'] = []
52
- if parsed_feature[:tags]
53
- parsed_feature[:tags].each do |tag|
54
- adapt_tag!(tag)
55
- end
56
- parsed_feature['tags'].concat(parsed_feature.delete(:tags))
57
- end
43
+ adapted_feature['elements'] = adapt_child_elements(feature_ast)
44
+ adapted_feature['tags'] = adapt_tags(feature_ast)
45
+
46
+ adapted_feature
58
47
  end
59
48
 
60
49
  # Adapts the AST sub-tree that is rooted at the given background node.
61
- def adapt_background!(parsed_background)
62
- # Saving off the original data
63
- parsed_background['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_background))
50
+ def adapt_background(background_ast)
51
+ adapted_background = {}
64
52
 
65
- # Removing parsed data for child elements in order to avoid duplicating data
66
- parsed_background['cuke_modeler_parsing_data'][:background][:steps] = nil if parsed_background['cuke_modeler_parsing_data'][:background][:steps]
53
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
54
+ save_original_data(adapted_background, background_ast)
55
+ clear_child_elements(adapted_background, [[:background, :steps]])
67
56
 
68
- parsed_background['type'] = 'Background'
69
- parsed_background['keyword'] = parsed_background[:background].delete(:keyword)
70
- parsed_background['name'] = parsed_background[:background].delete(:name)
71
- parsed_background['description'] = parsed_background[:background].delete(:description) || ''
72
- parsed_background['line'] = parsed_background[:background].delete(:location)[:line]
57
+ adapted_background['type'] = 'Background'
58
+ adapted_background['keyword'] = background_ast[:background][:keyword]
59
+ adapted_background['name'] = background_ast[:background][:name]
60
+ adapted_background['description'] = background_ast[:background][:description] || ''
61
+ adapted_background['line'] = background_ast[:background][:location][:line]
73
62
 
74
- parsed_background['steps'] = []
75
- if parsed_background[:background][:steps]
76
- parsed_background[:background][:steps].each do |step|
77
- adapt_step!(step)
78
- end
79
- parsed_background['steps'].concat(parsed_background[:background].delete(:steps))
80
- end
63
+ adapted_background['steps'] = adapt_steps(background_ast[:background])
64
+
65
+ adapted_background
81
66
  end
82
67
 
83
- # Adapts the AST sub-tree that is rooted at the given scenario node.
84
- def adapt_scenario!(parsed_test)
85
- # Removing parsed data for child elements in order to avoid duplicating data
86
- parsed_test['cuke_modeler_parsing_data'][:scenario][:tags] = nil if parsed_test['cuke_modeler_parsing_data'][:scenario][:tags]
87
- parsed_test['cuke_modeler_parsing_data'][:scenario][:steps] = nil if parsed_test['cuke_modeler_parsing_data'][:scenario][:steps]
88
-
89
- parsed_test['type'] = 'Scenario'
90
- parsed_test['keyword'] = parsed_test[:scenario].delete(:keyword)
91
- parsed_test['name'] = parsed_test[:scenario].delete(:name)
92
- parsed_test['description'] = parsed_test[:scenario].delete(:description) || ''
93
- parsed_test['line'] = parsed_test[:scenario].delete(:location)[:line]
94
-
95
- parsed_test['tags'] = []
96
- if parsed_test[:scenario][:tags]
97
- parsed_test[:scenario][:tags].each do |tag|
98
- adapt_tag!(tag)
99
- end
100
- parsed_test['tags'].concat(parsed_test[:scenario].delete(:tags))
101
- end
68
+ # Adapts the AST sub-tree that is rooted at the given rule node.
69
+ def adapt_rule(rule_ast)
70
+ adapted_rule = {}
102
71
 
103
- parsed_test['steps'] = []
104
- if parsed_test[:scenario][:steps]
105
- parsed_test[:scenario][:steps].each do |step|
106
- adapt_step!(step)
107
- end
108
- parsed_test['steps'].concat(parsed_test[:scenario].delete(:steps))
109
- end
72
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
73
+ save_original_data(adapted_rule, rule_ast)
74
+ clear_child_elements(adapted_rule, [[:rule, :children]])
75
+
76
+ adapted_rule['type'] = 'Rule'
77
+ adapted_rule['keyword'] = rule_ast[:rule][:keyword]
78
+ adapted_rule['name'] = rule_ast[:rule][:name]
79
+ adapted_rule['description'] = rule_ast[:rule][:description] || ''
80
+ adapted_rule['line'] = rule_ast[:rule][:location][:line]
81
+
82
+ adapted_rule['elements'] = adapt_child_elements(rule_ast[:rule])
83
+
84
+ adapted_rule
110
85
  end
111
86
 
112
- # Adapts the AST sub-tree that is rooted at the given outline node.
113
- def adapt_outline!(parsed_test)
114
- # Removing parsed data for child elements in order to avoid duplicating data
115
- parsed_test['cuke_modeler_parsing_data'][:scenario][:tags] = nil if parsed_test['cuke_modeler_parsing_data'][:scenario][:tags]
116
- parsed_test['cuke_modeler_parsing_data'][:scenario][:steps] = nil if parsed_test['cuke_modeler_parsing_data'][:scenario][:steps]
117
- parsed_test['cuke_modeler_parsing_data'][:scenario][:examples] = nil if parsed_test['cuke_modeler_parsing_data'][:scenario][:examples]
118
-
119
- parsed_test['type'] = 'ScenarioOutline'
120
- parsed_test['keyword'] = parsed_test[:scenario].delete(:keyword)
121
- parsed_test['name'] = parsed_test[:scenario].delete(:name)
122
- parsed_test['description'] = parsed_test[:scenario].delete(:description) || ''
123
- parsed_test['line'] = parsed_test[:scenario].delete(:location)[:line]
124
-
125
- parsed_test['tags'] = []
126
- if parsed_test[:scenario][:tags]
127
- parsed_test[:scenario][:tags].each do |tag|
128
- adapt_tag!(tag)
129
- end
130
- parsed_test['tags'].concat(parsed_test[:scenario].delete(:tags))
131
- end
87
+ # Adapts the AST sub-tree that is rooted at the given scenario node.
88
+ def adapt_scenario(test_ast)
89
+ adapted_scenario = {}
132
90
 
133
- parsed_test['steps'] = []
134
- if parsed_test[:scenario][:steps]
135
- parsed_test[:scenario][:steps].each do |step|
136
- adapt_step!(step)
137
- end
138
- parsed_test['steps'].concat(parsed_test[:scenario].delete(:steps))
139
- end
91
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
92
+ save_original_data(adapted_scenario, test_ast)
93
+ clear_child_elements(adapted_scenario, [[:scenario, :tags],
94
+ [:scenario, :steps]])
140
95
 
141
- parsed_test['examples'] = []
142
- if parsed_test[:scenario][:examples]
143
- parsed_test[:scenario][:examples].each do |step|
144
- adapt_example!(step)
145
- end
146
- parsed_test['examples'].concat(parsed_test[:scenario].delete(:examples))
147
- end
96
+ adapted_scenario['type'] = 'Scenario'
97
+ adapted_scenario['keyword'] = test_ast[:scenario][:keyword]
98
+ adapted_scenario['name'] = test_ast[:scenario][:name]
99
+ adapted_scenario['description'] = test_ast[:scenario][:description] || ''
100
+ adapted_scenario['line'] = test_ast[:scenario][:location][:line]
101
+
102
+ adapted_scenario['tags'] = adapt_tags(test_ast[:scenario])
103
+ adapted_scenario['steps'] = adapt_steps(test_ast[:scenario])
104
+
105
+ adapted_scenario
106
+ end
107
+
108
+ # Adapts the AST sub-tree that is rooted at the given outline node.
109
+ def adapt_outline(test_ast)
110
+ adapted_outline = {}
111
+
112
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
113
+ save_original_data(adapted_outline, test_ast)
114
+ clear_child_elements(adapted_outline, [[:scenario, :tags],
115
+ [:scenario, :steps],
116
+ [:scenario, :examples]])
117
+
118
+ adapted_outline['type'] = 'ScenarioOutline'
119
+ adapted_outline['keyword'] = test_ast[:scenario][:keyword]
120
+ adapted_outline['name'] = test_ast[:scenario][:name]
121
+ adapted_outline['description'] = test_ast[:scenario][:description] || ''
122
+ adapted_outline['line'] = test_ast[:scenario][:location][:line]
123
+
124
+ adapted_outline['tags'] = adapt_tags(test_ast[:scenario])
125
+ adapted_outline['steps'] = adapt_steps(test_ast[:scenario])
126
+ adapted_outline['examples'] = adapt_examples(test_ast[:scenario])
127
+
128
+ adapted_outline
148
129
  end
149
130
 
150
131
  # Adapts the AST sub-tree that is rooted at the given example node.
151
- def adapt_example!(parsed_example)
152
- # Saving off the original data
153
- parsed_example['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_example))
132
+ def adapt_example(example_ast)
133
+ adapted_example = {}
154
134
 
155
- # Removing parsed data for child elements in order to avoid duplicating data
156
- parsed_example['cuke_modeler_parsing_data'][:tags] = nil if parsed_example['cuke_modeler_parsing_data'][:tags]
157
- parsed_example['cuke_modeler_parsing_data'][:table_header] = nil if parsed_example['cuke_modeler_parsing_data'][:table_header]
158
- parsed_example['cuke_modeler_parsing_data'][:table_body] = nil if parsed_example['cuke_modeler_parsing_data'][:table_body]
135
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
136
+ save_original_data(adapted_example, example_ast)
137
+ clear_child_elements(adapted_example, [[:tags],
138
+ [:table_header],
139
+ [:table_body]])
159
140
 
160
- parsed_example['keyword'] = parsed_example.delete(:keyword)
161
- parsed_example['name'] = parsed_example.delete(:name)
162
- parsed_example['line'] = parsed_example.delete(:location)[:line]
163
- parsed_example['description'] = parsed_example.delete(:description) || ''
141
+ adapted_example['keyword'] = example_ast[:keyword]
142
+ adapted_example['name'] = example_ast[:name]
143
+ adapted_example['line'] = example_ast[:location][:line]
144
+ adapted_example['description'] = example_ast[:description] || ''
164
145
 
165
- parsed_example['rows'] = []
146
+ adapted_example['rows'] = []
147
+ adapted_example['rows'] << adapt_table_row(example_ast[:table_header]) if example_ast[:table_header]
166
148
 
167
- if parsed_example[:table_header]
168
- adapt_table_row!(parsed_example[:table_header])
169
- parsed_example['rows'] << parsed_example.delete(:table_header)
149
+ example_ast[:table_body]&.each do |row|
150
+ adapted_example['rows'] << adapt_table_row(row)
170
151
  end
171
152
 
172
- if parsed_example[:table_body]
173
- parsed_example[:table_body].each do |row|
174
- adapt_table_row!(row)
175
- end
176
- parsed_example['rows'].concat(parsed_example.delete(:table_body))
177
- end
153
+ adapted_example['tags'] = adapt_tags(example_ast)
178
154
 
179
- parsed_example['tags'] = []
180
- if parsed_example[:tags]
181
- parsed_example[:tags].each do |tag|
182
- adapt_tag!(tag)
183
- end
184
- parsed_example['tags'].concat(parsed_example.delete(:tags))
185
- end
155
+ adapted_example
186
156
  end
187
157
 
188
158
  # Adapts the AST sub-tree that is rooted at the given tag node.
189
- def adapt_tag!(parsed_tag)
159
+ def adapt_tag(tag_ast)
160
+ adapted_tag = {}
161
+
190
162
  # Saving off the original data
191
- parsed_tag['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_tag))
163
+ save_original_data(adapted_tag, tag_ast)
164
+
165
+ adapted_tag['name'] = tag_ast[:name]
166
+ adapted_tag['line'] = tag_ast[:location][:line]
192
167
 
193
- parsed_tag['name'] = parsed_tag.delete(:name)
194
- parsed_tag['line'] = parsed_tag.delete(:location)[:line]
168
+ adapted_tag
195
169
  end
196
170
 
197
171
  # Adapts the AST sub-tree that is rooted at the given comment node.
198
- def adapt_comment!(parsed_comment)
172
+ def adapt_comment(comment_ast)
173
+ adapted_comment = {}
174
+
199
175
  # Saving off the original data
200
- parsed_comment['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_comment))
176
+ save_original_data(adapted_comment, comment_ast)
177
+
178
+ adapted_comment['text'] = comment_ast[:text]
179
+ adapted_comment['line'] = comment_ast[:location][:line]
201
180
 
202
- parsed_comment['text'] = parsed_comment.delete(:text)
203
- parsed_comment['line'] = parsed_comment.delete(:location)[:line]
181
+ adapted_comment
204
182
  end
205
183
 
206
184
  # Adapts the AST sub-tree that is rooted at the given step node.
207
- def adapt_step!(parsed_step)
208
- # Saving off the original data
209
- parsed_step['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step))
210
-
211
- # Removing parsed data for child elements in order to avoid duplicating data
212
- parsed_step['cuke_modeler_parsing_data'][:data_table] = nil if parsed_step['cuke_modeler_parsing_data'][:data_table]
213
- parsed_step['cuke_modeler_parsing_data'][:doc_string] = nil if parsed_step['cuke_modeler_parsing_data'][:doc_string]
214
-
215
- parsed_step['keyword'] = parsed_step.delete(:keyword)
216
- parsed_step['name'] = parsed_step.delete(:text)
217
- parsed_step['line'] = parsed_step.delete(:location)[:line]
218
-
219
- case
220
- when parsed_step[:doc_string]
221
- adapt_doc_string!(parsed_step[:doc_string])
222
- parsed_step['doc_string'] = parsed_step.delete(:doc_string)
223
- when parsed_step[:data_table]
224
- adapt_step_table!(parsed_step[:data_table])
225
- parsed_step['table'] = parsed_step.delete(:data_table)
226
- else
227
- # Step has no extra argument
185
+ def adapt_step(step_ast)
186
+ adapted_step = {}
187
+
188
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
189
+ save_original_data(adapted_step, step_ast)
190
+ clear_child_elements(adapted_step, [[:data_table],
191
+ [:doc_string]])
192
+
193
+ adapted_step['keyword'] = step_ast[:keyword]
194
+ adapted_step['name'] = step_ast[:text]
195
+ adapted_step['line'] = step_ast[:location][:line]
196
+
197
+ if step_ast[:doc_string]
198
+ adapted_step['doc_string'] = adapt_doc_string(step_ast[:doc_string])
199
+ elsif step_ast[:data_table]
200
+ adapted_step['table'] = adapt_step_table(step_ast[:data_table])
228
201
  end
202
+
203
+ adapted_step
229
204
  end
230
205
 
231
206
  # Adapts the AST sub-tree that is rooted at the given doc string node.
232
- def adapt_doc_string!(parsed_doc_string)
207
+ def adapt_doc_string(doc_string_ast)
208
+ adapted_doc_string = {}
209
+
233
210
  # Saving off the original data
234
- parsed_doc_string['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_doc_string))
211
+ save_original_data(adapted_doc_string, doc_string_ast)
212
+
213
+ adapted_doc_string['value'] = doc_string_ast[:content]
214
+ adapted_doc_string['content_type'] = doc_string_ast[:media_type]
215
+ adapted_doc_string['line'] = doc_string_ast[:location][:line]
235
216
 
236
- parsed_doc_string['value'] = parsed_doc_string.delete(:content)
237
- parsed_doc_string['content_type'] = parsed_doc_string.delete(:media_type)
238
- parsed_doc_string['line'] = parsed_doc_string.delete(:location)[:line]
217
+ adapted_doc_string
239
218
  end
240
219
 
241
220
  # Adapts the AST sub-tree that is rooted at the given table node.
242
- def adapt_step_table!(parsed_step_table)
243
- # Saving off the original data
244
- parsed_step_table['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step_table))
221
+ def adapt_step_table(step_table_ast)
222
+ adapted_step_table = {}
245
223
 
246
- # Removing parsed data for child elements in order to avoid duplicating data
247
- parsed_step_table['cuke_modeler_parsing_data'][:rows] = nil
224
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
225
+ save_original_data(adapted_step_table, step_table_ast)
226
+ clear_child_elements(adapted_step_table, [[:rows]])
248
227
 
249
- parsed_step_table['rows'] = []
250
- parsed_step_table[:rows].each do |row|
251
- adapt_table_row!(row)
228
+ adapted_step_table['rows'] = []
229
+ step_table_ast[:rows].each do |row|
230
+ adapted_step_table['rows'] << adapt_table_row(row)
252
231
  end
253
- parsed_step_table['rows'].concat(parsed_step_table.delete(:rows))
254
- parsed_step_table['line'] = parsed_step_table.delete(:location)[:line]
232
+ adapted_step_table['line'] = step_table_ast[:location][:line]
233
+
234
+ adapted_step_table
255
235
  end
256
236
 
257
237
  # Adapts the AST sub-tree that is rooted at the given row node.
258
- def adapt_table_row!(parsed_table_row)
259
- # Saving off the original data
260
- parsed_table_row['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_table_row))
238
+ def adapt_table_row(table_row_ast)
239
+ adapted_table_row = {}
261
240
 
262
- # Removing parsed data for child elements in order to avoid duplicating data which the child elements will themselves include
263
- parsed_table_row['cuke_modeler_parsing_data'][:cells] = nil
241
+ # Saving off the original data and removing parsed data for child elements in order to avoid duplicating data
242
+ save_original_data(adapted_table_row, table_row_ast)
243
+ clear_child_elements(adapted_table_row, [[:cells]])
264
244
 
265
- parsed_table_row['line'] = parsed_table_row.delete(:location)[:line]
245
+ adapted_table_row['line'] = table_row_ast[:location][:line]
266
246
 
267
- parsed_table_row['cells'] = []
268
- parsed_table_row[:cells].each do |row|
269
- adapt_table_cell!(row)
247
+ adapted_table_row['cells'] = []
248
+ table_row_ast[:cells].each do |row|
249
+ adapted_table_row['cells'] << adapt_table_cell(row)
270
250
  end
271
- parsed_table_row['cells'].concat(parsed_table_row.delete(:cells))
251
+
252
+ adapted_table_row
272
253
  end
273
254
 
274
255
  # Adapts the AST sub-tree that is rooted at the given cell node.
275
- def adapt_table_cell!(parsed_cell)
256
+ def adapt_table_cell(cell_ast)
257
+ adapted_cell = {}
258
+
276
259
  # Saving off the original data
277
- parsed_cell['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_cell))
260
+ save_original_data(adapted_cell, cell_ast)
278
261
 
279
- parsed_cell['value'] = parsed_cell.delete(:value)
280
- parsed_cell['line'] = parsed_cell.delete(:location)[:line]
262
+ adapted_cell['value'] = cell_ast[:value]
263
+ adapted_cell['line'] = cell_ast[:location][:line]
264
+
265
+ adapted_cell
281
266
  end
282
267
 
283
268
 
284
269
  private
285
270
 
286
271
 
287
- def adapt_child_elements!(parsed_children)
288
- background_child = parsed_children.find { |child| child[:background] }
272
+ def adapt_comments(file_ast)
273
+ return [] unless file_ast[:comments]
274
+
275
+ file_ast[:comments].map { |comment| adapt_comment(comment) }
276
+ end
277
+
278
+ def adapt_tags(element_ast)
279
+ return [] unless element_ast[:tags]
280
+
281
+ element_ast[:tags].map { |tag| adapt_tag(tag) }
282
+ end
283
+
284
+ def adapt_steps(element_ast)
285
+ return [] unless element_ast[:steps]
289
286
 
290
- if background_child
291
- adapt_background!(background_child)
287
+ element_ast[:steps].map { |step| adapt_step(step) }
288
+ end
292
289
 
293
- remaining_children = parsed_children.reject { |child| child[:background] }
290
+ def adapt_examples(element_ast)
291
+ return [] unless element_ast[:examples]
292
+
293
+ element_ast[:examples].map { |example| adapt_example(example) }
294
+ end
295
+
296
+ def adapt_child_elements(element_ast)
297
+ return [] unless element_ast[:children]
298
+
299
+ adapted_children = []
300
+
301
+ element_ast[:children].each do |child_element|
302
+ adapted_children << if child_element[:background]
303
+ adapt_background(child_element)
304
+ elsif child_element[:rule]
305
+ adapt_rule(child_element)
306
+ else
307
+ adapt_test(child_element)
308
+ end
294
309
  end
295
310
 
296
- adapt_tests!(remaining_children || parsed_children)
311
+ adapted_children
297
312
  end
298
313
 
299
- def adapt_tests!(parsed_tests)
300
- parsed_tests.each do |test|
301
- adapt_test!(test)
314
+ def adapt_test(test_ast)
315
+ if (test_node?(test_ast) && test_has_examples?(test_ast)) ||
316
+ (test_node?(test_ast) && test_uses_outline_keyword?(test_ast))
317
+
318
+ adapt_outline(test_ast)
319
+ elsif test_node?(test_ast)
320
+ adapt_scenario(test_ast)
321
+ else
322
+ raise(ArgumentError, "Unknown test type with keys: #{test_ast.keys}")
302
323
  end
303
324
  end
304
325
 
305
- def adapt_test!(parsed_test)
306
- # Saving off the original data
307
- parsed_test['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_test))
308
-
309
- case
310
- when (parsed_test[:scenario] && parsed_test[:scenario][:examples]) || (parsed_test[:scenario] && Parsing.dialects[Parsing.dialect]['scenarioOutline'].include?(parsed_test[:scenario][:keyword]))
311
- adapt_outline!(parsed_test)
312
- when parsed_test[:scenario]
313
- adapt_scenario!(parsed_test)
314
- else
315
- raise(ArgumentError, "Unknown test type with keys: #{parsed_test.keys}")
326
+ def save_original_data(adapted_ast, raw_ast)
327
+ adapted_ast['cuke_modeler_parsing_data'] = Marshal.load(Marshal.dump(raw_ast))
328
+ end
329
+
330
+ def clear_child_elements(ast, child_paths)
331
+ child_paths.each do |traversal_path|
332
+ if ast['cuke_modeler_parsing_data'].dig(*traversal_path)
333
+ bury(ast['cuke_modeler_parsing_data'], traversal_path, nil)
334
+ end
335
+ end
336
+ end
337
+
338
+ def bury(hash, traversal_path, value)
339
+ keys = *traversal_path
340
+
341
+ current = hash
342
+ (keys.count - 1).times do |index|
343
+ current = hash[keys[index]]
316
344
  end
345
+
346
+ current[keys.last] = value
347
+ end
348
+
349
+ def test_node?(ast_node)
350
+ ast_node[:scenario]
351
+ end
352
+
353
+ def test_has_examples?(ast_node)
354
+ ast_node[:scenario][:examples]
355
+ end
356
+
357
+ def test_uses_outline_keyword?(test_ast)
358
+ Parsing.dialects[Parsing.dialect]['scenarioOutline'].include?(test_ast[:scenario][:keyword])
317
359
  end
318
360
 
319
361
  end
320
362
  end
363
+
364
+ # rubocop:enable Metrics/ClassLength, Metrics/AbcSize, Metrics/MethodLength