cuke_modeler 3.0.0 → 3.5.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/README.md +3 -2
  4. data/cuke_modeler.gemspec +24 -15
  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 +13 -0
  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 +41 -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 -1
  25. data/lib/cuke_modeler/models/outline.rb +19 -14
  26. data/lib/cuke_modeler/models/row.rb +10 -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 +39 -29
  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 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12a7bac33e0fe407f250239e15637b732497d9d106893ae9d08943bb2497821c
4
- data.tar.gz: 26e6cf550dfdec1f87109651d73c01f403a97235277497ea7dc09017448a9a7c
3
+ metadata.gz: ba3e400ea68f67944c0c5bf170179fb50a60237e507d3113a921c844010f7d91
4
+ data.tar.gz: 6224ca8ff2505acfb0415c82e9b1963ba5cf21573e2a9cac8857cb63171b288f
5
5
  SHA512:
6
- metadata.gz: 129e84d0e7d10a7b217c040d7f6cab3590096e76345f679f61a51b3833c0c771fbb2299d997fd319851042d4a40bb2990310112d7fa8836d260ff91107d760cd
7
- data.tar.gz: 7dd7f4ebd77a9c9b898f4f07fcd33fe1e9216205b9d745ce7da5cff54dc6ba5febe6ed129594287d2c006165ef08a9717c88710b5cae346d15fe98c3e90844fe
6
+ metadata.gz: a447530c96482e6464ad31151262e5a0fbfd11527445ae8965c834e6ece15ee33f89381eb2276d7ee043823ab491eaed932bb35f492bfdb2fe8a43defd605e37
7
+ data.tar.gz: e8b05b052cad1f7395dec1e8da58bd84145c1699dce19b9bb261eb76bf29ceb1ebf3e7fa2cfe13773ff9f29f8c86d93543f9a5a34f79b1fc2fe402257618bbe7
@@ -8,6 +8,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  Nothing yet...
10
10
 
11
+ ## [3.5.0] - 2020-12-19
12
+
13
+ ### Added
14
+ - Support added for more versions of the `cucumber-gherkin` gem
15
+ - 16.x
16
+
17
+ ## [3.4.0] - 2020-09-02
18
+
19
+ ### Added
20
+ - `Feature#has_background?` and `Rule#has_background?` now both have a more conventional name via the alias `#background?`
21
+
22
+ ## [3.3.0] - 2020-08-15
23
+
24
+ ### Added
25
+ - Support added for more versions of the `cucumber-gherkin` gem
26
+ - 15.x
27
+
28
+ ## [3.2.0] - 2020-07-27
29
+
30
+ ### Added
31
+ - The `Rule` keyword is now a modeled element.
32
+
33
+ ### Deprecated
34
+ - `Feature#test_case_count` will be removed on the next major release. It's a random analysis method in what is
35
+ otherwise a purely abstraction layer library. The [CQL](https://github.com/enkessler/cql) gem is better suited to such tasks.
36
+
37
+ ## [3.1.0] - 2020-06-28
38
+
39
+ ### Added
40
+ - Support added for more versions of the `cucumber-gherkin` gem
41
+ - 14.x
42
+
43
+ ### Fixed
44
+ - Text is converted to UTF-8 encoding before being passed to the underlying Gherkin gem. This is due to UTF-8 being
45
+ the only encoding supported by Gherkin. The `gherkin` gem did the conversion automatically and so this conversion
46
+ was not necessary previously but the `cucumber-gherkin` gem does not do any automatic conversion.
47
+
11
48
  ## [3.0.0] - 2020-06-08
12
49
 
13
50
  ### Changed
@@ -303,7 +340,12 @@ Nothing yet...
303
340
  - Initial release
304
341
 
305
342
 
306
- [Unreleased]: https://github.com/enkessler/cuke_modeler/compare/v3.0.0...HEAD
343
+ [Unreleased]: https://github.com/enkessler/cuke_modeler/compare/v3.5.0...HEAD
344
+ [3.5.0]: https://github.com/enkessler/cuke_modeler/compare/v3.4.0...v3.5.0
345
+ [3.4.0]: https://github.com/enkessler/cuke_modeler/compare/v3.3.0...v3.4.0
346
+ [3.3.0]: https://github.com/enkessler/cuke_modeler/compare/v3.2.0...v3.3.0
347
+ [3.2.0]: https://github.com/enkessler/cuke_modeler/compare/v3.1.0...v3.2.0
348
+ [3.1.0]: https://github.com/enkessler/cuke_modeler/compare/v3.0.0...v3.1.0
307
349
  [3.0.0]: https://github.com/enkessler/cuke_modeler/compare/v2.1.0...v3.0.0
308
350
  [2.1.0]: https://github.com/enkessler/cuke_modeler/compare/v2.0.0...v2.1.0
309
351
  [2.0.0]: https://github.com/enkessler/cuke_modeler/compare/v1.5.1...v2.0.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
29
  spec.required_ruby_version = '>= 2.3', '< 3.0'
26
30
 
27
- spec.add_runtime_dependency 'cucumber-gherkin', '< 14.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
 
@@ -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 14.x of the *cucumber-gherkin* gem into input that is consumable
8
+ # by this gem.
9
+
10
+ class Gherkin14Adapter < 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 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