yard-gherkin-turnip 1.0.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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +3 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +48 -0
  9. data/History.txt +293 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +182 -0
  12. data/Rakefile +24 -0
  13. data/example/README.md +8 -0
  14. data/example/child_feature/README.md +21 -0
  15. data/example/child_feature/child.feature +11 -0
  16. data/example/child_feature/grandchild_feature/grandchild.feature +12 -0
  17. data/example/empty.feature +2 -0
  18. data/example/placeholder.feature +34 -0
  19. data/example/scenario.feature +63 -0
  20. data/example/scenario_outline.feature +100 -0
  21. data/example/scenario_outline_multi.feature +15 -0
  22. data/example/step_definitions/customer.step.rb +16 -0
  23. data/example/step_definitions/duck.step.rb +6 -0
  24. data/example/step_definitions/placeholder.step.rb +70 -0
  25. data/example/step_definitions/text.step.rb +11 -0
  26. data/example/tags.feature +18 -0
  27. data/lib/cucumber/city_builder.rb +412 -0
  28. data/lib/docserver/default/fulldoc/html/js/cucumber.js +88 -0
  29. data/lib/docserver/default/layout/html/headers.erb +13 -0
  30. data/lib/docserver/doc_server/full_list/html/full_list.erb +39 -0
  31. data/lib/docserver/doc_server/full_list/html/setup.rb +18 -0
  32. data/lib/templates/default/feature/html/feature.erb +39 -0
  33. data/lib/templates/default/feature/html/no_steps_defined.erb +1 -0
  34. data/lib/templates/default/feature/html/outline.erb +59 -0
  35. data/lib/templates/default/feature/html/pystring.erb +3 -0
  36. data/lib/templates/default/feature/html/scenario.erb +55 -0
  37. data/lib/templates/default/feature/html/setup.rb +54 -0
  38. data/lib/templates/default/feature/html/steps.erb +39 -0
  39. data/lib/templates/default/feature/html/table.erb +20 -0
  40. data/lib/templates/default/featuredirectory/html/alpha_table.erb +30 -0
  41. data/lib/templates/default/featuredirectory/html/directory.erb +32 -0
  42. data/lib/templates/default/featuredirectory/html/setup.rb +41 -0
  43. data/lib/templates/default/featuretags/html/namespace.erb +159 -0
  44. data/lib/templates/default/featuretags/html/setup.rb +34 -0
  45. data/lib/templates/default/fulldoc/html/css/cucumber.css +226 -0
  46. data/lib/templates/default/fulldoc/html/directories.erb +27 -0
  47. data/lib/templates/default/fulldoc/html/full_list_featuredirectories.erb +11 -0
  48. data/lib/templates/default/fulldoc/html/full_list_features.erb +37 -0
  49. data/lib/templates/default/fulldoc/html/full_list_stepdefinitions.erb +20 -0
  50. data/lib/templates/default/fulldoc/html/full_list_steps.erb +20 -0
  51. data/lib/templates/default/fulldoc/html/full_list_tags.erb +16 -0
  52. data/lib/templates/default/fulldoc/html/js/cucumber.js +331 -0
  53. data/lib/templates/default/fulldoc/html/setup.rb +208 -0
  54. data/lib/templates/default/layout/html/setup.rb +131 -0
  55. data/lib/templates/default/requirements/html/alpha_table.erb +26 -0
  56. data/lib/templates/default/requirements/html/requirements.erb +50 -0
  57. data/lib/templates/default/requirements/html/setup.rb +51 -0
  58. data/lib/templates/default/steptransformers/html/header.erb +12 -0
  59. data/lib/templates/default/steptransformers/html/index.erb +10 -0
  60. data/lib/templates/default/steptransformers/html/placeholders.erb +79 -0
  61. data/lib/templates/default/steptransformers/html/setup.rb +107 -0
  62. data/lib/templates/default/steptransformers/html/step_definitions.erb +79 -0
  63. data/lib/templates/default/steptransformers/html/undefined_steps.erb +26 -0
  64. data/lib/templates/default/tag/html/alpha_table.erb +33 -0
  65. data/lib/templates/default/tag/html/setup.rb +27 -0
  66. data/lib/templates/default/tag/html/tag.erb +35 -0
  67. data/lib/yard-gherkin-turnip.rb +42 -0
  68. data/lib/yard-gherkin-turnip/version.rb +3 -0
  69. data/lib/yard/code_objects/cucumber/base.rb +24 -0
  70. data/lib/yard/code_objects/cucumber/feature.rb +16 -0
  71. data/lib/yard/code_objects/cucumber/namespace_object.rb +55 -0
  72. data/lib/yard/code_objects/cucumber/scenario.rb +22 -0
  73. data/lib/yard/code_objects/cucumber/scenario_outline.rb +68 -0
  74. data/lib/yard/code_objects/cucumber/step.rb +46 -0
  75. data/lib/yard/code_objects/cucumber/tag.rb +31 -0
  76. data/lib/yard/code_objects/placeholder.rb +45 -0
  77. data/lib/yard/code_objects/step_definition.rb +46 -0
  78. data/lib/yard/code_objects/step_transformer.rb +32 -0
  79. data/lib/yard/handlers/cucumber/base.rb +21 -0
  80. data/lib/yard/handlers/cucumber/feature_handler.rb +96 -0
  81. data/lib/yard/handlers/placeholder_handler.rb +28 -0
  82. data/lib/yard/handlers/placeholder_match_handler.rb +17 -0
  83. data/lib/yard/handlers/step_definition_handler.rb +55 -0
  84. data/lib/yard/parser/cucumber/feature.rb +72 -0
  85. data/lib/yard/server/adapter.rb +43 -0
  86. data/lib/yard/server/commands/list_command.rb +31 -0
  87. data/lib/yard/server/router.rb +31 -0
  88. data/lib/yard/templates/helpers/base_helper.rb +26 -0
  89. data/yard-gherkin-turnip.gemspec +67 -0
  90. metadata +216 -0
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+
3
+ task :default => :gendoc
4
+
5
+ desc "Clean out any existing documentation"
6
+ task :clean do
7
+ `rm -rf doc`
8
+ `rm -rf .yardoc`
9
+ end
10
+
11
+ desc "Generate documentation from the example data"
12
+ task :gendoc => :clean do
13
+ puts `yardoc -e ./lib/yard-gherkin-turnip.rb 'example/**/*' --debug`
14
+ end
15
+
16
+ desc "Run the YARD Server"
17
+ task :server => :gendoc do
18
+ puts `yard server -e ./lib/yard-gherkin-turnip.rb`
19
+ end
20
+
21
+ desc "Create the yard-gherkin-turnip gem"
22
+ task :gem do
23
+ puts `gem build yard-gherkin-turnip.gemspec`
24
+ end
@@ -0,0 +1,8 @@
1
+ This collection of features are really just to test some of the varied cases
2
+ that CITY may come in contact with while parsing a series of features. All
3
+ of the features, tags, and directories displayed here are contained in this
4
+ directory and all subdirectories.
5
+
6
+ * First, the features which are broken down alphabetically and displayed
7
+ * Second, the tags, used by all the features and scenarios
8
+ * Third, the subdirectories, contained in this directory and in the subdirectories.
@@ -0,0 +1,21 @@
1
+ Child Features
2
+ ==============
3
+
4
+ Synopsis
5
+ --------
6
+
7
+ This collection of features are contained in this folder. The description README
8
+ allows a user to place a description for entire directory of the features and appears
9
+ in the output to assist with understanding about the collection of features.
10
+
11
+ Resources
12
+ ---------
13
+
14
+ Links to particular resources like the links to the stories, tasks, or other areas
15
+ can also be represented.
16
+
17
+ The implemented example has been deployed at [http://recursivegames.com/cukes/](http://recursivegames.com/cukes/).
18
+
19
+ **1. An Item** [example](http://recursivegames.com/cukes/requirements/)
20
+
21
+
@@ -0,0 +1,11 @@
1
+ @scenarios
2
+ Feature: Child Feature
3
+ As a reader of the documentation I expect that scenario are documented correctly
4
+
5
+ Background:
6
+ Given this background step
7
+
8
+ Scenario: Child Scenario
9
+ Given this first step
10
+ When this second step
11
+ Then this third step
@@ -0,0 +1,12 @@
1
+ @scenarios
2
+ Feature: Grandchild Feature
3
+ As a reader of the documentation I expect that scenario are documented correctly
4
+
5
+ Background:
6
+ Given this background step
7
+
8
+ @first
9
+ Scenario: Grandchild Scenario
10
+ Given this first step
11
+ When this second step
12
+ Then this third step
@@ -0,0 +1,2 @@
1
+ # This is a feature that has not been written
2
+ # However, I don't want parser to fail when reaching this file
@@ -0,0 +1,34 @@
1
+ @scenarios @bvt
2
+ Feature: Step Placeholders
3
+ As a developer of the test suite I expect that step placeholders are documented correctly
4
+
5
+ @first
6
+ Scenario: Step with custom placeholders
7
+ Given this scenario step
8
+ Then I expect that the step, on the step transformer page, will link to the step transform
9
+
10
+ @first
11
+ Scenario: Step with custom placeholders
12
+ Given the first step
13
+ Then I expect that the step, on the step transformer page, will link to the step transform
14
+
15
+ @second
16
+ Scenario: Step with simple placeholders
17
+ Given the step called 'bob'
18
+ And the steps called 'pop'
19
+ Then I expect that the step, on the step transformer page, will link to the step transform
20
+
21
+ @third
22
+ Scenario: Step with multiple custom placeholders
23
+ Given the second scenario step on 12/04/2020
24
+ Then I expect that the step, on the step transformer page, will link to the step transform
25
+
26
+ @third
27
+ Scenario: Step with both simple and custom placeholders
28
+ Given the second 'bob' step
29
+ Then I expect that the step, on the step transformer page, will link to the step transform
30
+
31
+ Scenario: Step with complex placeholder reges
32
+ Given the step at the [0 + 1] index
33
+ Then I expect that the step, on the step transformer page, will link to the step transform
34
+
@@ -0,0 +1,63 @@
1
+ # Comments that appear before the feature are associated with the feature
2
+ @scenarios
3
+ Feature: Displaying Scenarios
4
+ As a reader of the documentation I expect that scenario are documented correctly
5
+
6
+ # Comments after the feature description belong to the background or first scenario
7
+ Background:
8
+ Given this background step
9
+
10
+ @first @bvt
11
+ Scenario: No Step Scenario
12
+
13
+ @second @bvt
14
+ Scenario: Scenario With Steps
15
+ Given this first step
16
+ When this second step
17
+ Then this third step
18
+
19
+ @third @optional_parameters
20
+ Scenario: Optional Parameter Step Definition
21
+ # This step definition has some optional parameters
22
+ Given a project
23
+ And an inactive project
24
+ And a project with the name 'optional', start date 10/26/2010, nicknamed 'norman'
25
+
26
+ @fourth @highlight
27
+ Scenario: Matched Term Highlighting
28
+ Given a duck that has a bill
29
+ Then I expect the duck to quack
30
+
31
+ @fifth @table
32
+ Scenario: Scenario With Table
33
+ Given the following table:
34
+ | column 1 | column 2 | column 3 |
35
+ | value 1 | value 2 | value 3 |
36
+
37
+ @sixth @text
38
+ Scenario: Scenario With Text
39
+ Given the following text:
40
+ """
41
+ Oh what a bother!
42
+ That this text has to take up two lines
43
+ This line should be indented 2 spaces
44
+ This line should be indented 4 spaces
45
+ """
46
+
47
+ # Comments before the scenario
48
+ @seventh @comments
49
+ Scenario: Scenario with comments and a description
50
+ There once was a need for information to be displayed alongside all the
51
+ entities that I hoped to test
52
+ # First Comment
53
+ Given this first step
54
+ # Second Comment that
55
+ # spans a few lines
56
+ And this second step
57
+ # Third Comment
58
+ And this third step
59
+ # Comments after the last step, where do they go?
60
+
61
+ Scenario: Step ending with a match with double-quotes
62
+ When searching the log for the exact match of the message "Entering application."
63
+ When the step definition has HTML escaped characters like: "<>&"
@@ -0,0 +1,100 @@
1
+ @scenario_outlines @bvt @duplicate
2
+ Feature: Displaying Scenario Outlines
3
+ As a reader of the documentation I expect that scenario outlines are documented correctly
4
+
5
+ @first
6
+ Scenario Outline: Three Examples
7
+ Given that <Customer> is a valid customer
8
+ And that the product, named '<Product>', is a valid product
9
+ When the customer has purchased the product
10
+ Then I expect the customer to be a member of the '<Product>' group
11
+
12
+ @tagged_examples
13
+ Examples:
14
+ | Customer | Product |
15
+ | Customer A | Product A |
16
+ | Customer A | Product B |
17
+ | Customer A | Product C |
18
+
19
+ @second
20
+ Scenario Outline: Step contains a text block
21
+ Given the following text:
22
+ """
23
+ The <noun> jumped over the <place>
24
+ """
25
+ When I click on an example row
26
+ Then I expect <noun> to be replaced by the example noun
27
+ And I expect <place> to be replaced by the example place
28
+
29
+ Examples:
30
+ | noun | place |
31
+ | cow | moon |
32
+ | horse | spoon |
33
+
34
+ @third
35
+ Scenario Outline: Step contains a table
36
+ Given the following table:
37
+ | name | price | quantity |
38
+ | <name> | <price> | 100000 |
39
+ When I click on an example row
40
+ Then I expect <name> to be replaced by the example name
41
+ And I expect <price> to be replaced by the example price
42
+
43
+ Examples:
44
+ | name | price |
45
+ | toy | $99 |
46
+ | game | $49 |
47
+
48
+ @fourth
49
+ Scenario Outline: Step contains a table; table header uses an example
50
+ Given the following table:
51
+ | name | <denomination> | quantity |
52
+ | <name> | <price> | 100000 |
53
+ When I click on an example row
54
+ Then I expect <name> to be replaced by the example name
55
+ And I expect <price> to be replaced by the example price
56
+ And I expect <denomination> to be replaced by the example denomination
57
+
58
+ Examples:
59
+ | name | price | denomination |
60
+ | toy | 99 | cost in euros |
61
+ | game | 49 | cost in dollars |
62
+
63
+ # This is an example of a scenario outline in development.
64
+ # The example table has not been defined yet
65
+ @fifth
66
+ Scenario Outline: Example Table Missing
67
+ When I click on an example row
68
+ Then I expect <name> to be replaced by the example name
69
+ And I expect <price> to be replaced by the example price
70
+ And I expect <denomination> to be replaced by the example denomination
71
+
72
+
73
+ # This is an example of a scenario outline in development.
74
+ # The examples table has been defined, but is missing data.
75
+ @sixth
76
+ Scenario Outline: Empty Example Table
77
+ When I click on an example row
78
+ Then I expect <name> to be replaced by the example name
79
+ And I expect <price> to be replaced by the example price
80
+ And I expect <denomination> to be replaced by the example denomination
81
+
82
+ Examples:
83
+ | name | price | denomination |
84
+
85
+ @seventh @duplicate
86
+ Scenario Outline: Multiple Example Table
87
+ Given that <Customer> is a valid customer
88
+ And that the product, named '<Product>', is a valid product
89
+ When the customer has purchased the product
90
+ Then I expect the customer to be a member of the '<Product>' group
91
+
92
+ @groupA @duplicate
93
+ Examples: Example group A
94
+ | Customer | Product |
95
+ | Customer A | Product A |
96
+
97
+ @groupB
98
+ Examples: Example group B
99
+ | Customer | Product |
100
+ | Customer B | Product A |
@@ -0,0 +1,15 @@
1
+ Feature: My Feature
2
+
3
+ Scenario Outline: Multiple Example Table
4
+ Given that <Customer> is a valid customer
5
+ And that the product, named '<Product>', is a valid product
6
+ When the customer has purchased the product
7
+ Then I expect the customer to be a member of the '<Product>' group
8
+
9
+ Examples: Example group A
10
+ | Customer | Product |
11
+ | Customer A | Product A |
12
+
13
+ Examples: Example group B
14
+ | Customer | Product |
15
+ | Customer B | Product A |
@@ -0,0 +1,16 @@
1
+ step "that Customer :customer_number is a valid customer" do
2
+ end
3
+
4
+ step "that the product, named ':product', is a valid product" do
5
+ end
6
+
7
+ step "the customer has purchased the product" do
8
+ skip # pending step
9
+ end
10
+
11
+ step "I expect the customer to be a member of the ':product_type' group" do
12
+ end
13
+
14
+ placeholder :product_type do
15
+ match %r{(Product A|Product B|Product C)}
16
+ end
@@ -0,0 +1,6 @@
1
+ step "a duck that "\
2
+ "has a bill" do
3
+ end
4
+
5
+ step "I expect the " + "duck to quack" do
6
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # This step definition uses a placeholder custom
3
+ #
4
+ step "the/this :step_type step" do |step_type|
5
+ puts "step type #{step_type}"
6
+ end
7
+
8
+ #
9
+ # This step definition uses a custom placeholder with forward slashes
10
+ #
11
+ step "the :step_number :step_type step on :date" do |step_number, step_type, day, month, year|
12
+ puts "step type #{step_type}"
13
+ end
14
+
15
+ #
16
+ # This step definition uses alternative text syntax
17
+ #
18
+ step "the/this :step_number step" do |step_type|
19
+ puts "step number #{step_type}"
20
+ end
21
+
22
+ #
23
+ # This step definition uses a default placeholder with optional text
24
+ #
25
+ step "the step(s) called :name" do |name|
26
+ puts "name #{name}"
27
+ end
28
+
29
+
30
+ #
31
+ # This step definition uses both default and custom placeholders
32
+ #
33
+ step "the :step_number :name step" do |step_number, name|
34
+ puts "step_number #{step_number} and name #{name}"
35
+ end
36
+
37
+ #
38
+ # This step definition uses a custom placeholder with regex special chars
39
+ #
40
+ step "the step at the [:first_term :arithmetic_operation :first_term] index" do
41
+ end
42
+
43
+ #
44
+ # Shouldn't match our handler as it's not inside a placeholder
45
+ #
46
+ match /blob/ do
47
+ end
48
+
49
+ #
50
+ # This placeholder matches the type of step
51
+ #
52
+ placeholder :step_type do
53
+ match /(?:background|scenario)/
54
+ end
55
+
56
+ #
57
+ # This placeholder matches the step number using 2 separate calls to match
58
+ #
59
+ placeholder :step_number do
60
+ match(/(?:first|second)/)
61
+ match(/third/)
62
+ end
63
+
64
+ placeholder :date do
65
+ match /(\d\d)\/(\d\d)\/(\d\d\d\d)/
66
+ end
67
+
68
+ placeholder :arithmetic_operation do
69
+ match /\+|-|\*|\//
70
+ end
@@ -0,0 +1,11 @@
1
+ step :symbol, "the following text:"
2
+
3
+ step "I click on an example row" do
4
+ end
5
+
6
+ step "I expect :noun to be replaced by the example noun" do
7
+ end
8
+
9
+ step "I expect :place to be replaced by the example place" do
10
+ end
11
+
@@ -0,0 +1,18 @@
1
+ @tags
2
+ Feature: Tags
3
+ As a developer of the test suite I expect that various tags will be supported
4
+
5
+ @tag
6
+ Scenario: Basic Tag
7
+
8
+ @tag123456
9
+ Scenario: Tag With Numbers
10
+
11
+ @tag_with_underscore
12
+ Scenario: Tag With Underscore
13
+
14
+ @tag-with-dash
15
+ Scenario: Tag With Dash
16
+
17
+ @tag+with+plus
18
+ Scenario: Tag With Plus
@@ -0,0 +1,412 @@
1
+ module Cucumber
2
+ module Parser
3
+ class CityBuilder < ::Gherkin::AstBuilder
4
+
5
+ #
6
+ # The Gherkin Parser is going to call the various methods within this
7
+ # class as it finds items. This is similar to how Cucumber generates
8
+ # it's Abstract Syntax Tree (AST). Here instead this generates the
9
+ # various YARD::CodeObjects defined within this template.
10
+ #
11
+ # A namespace is specified and that is the place in the YARD namespacing
12
+ # where all cucumber features generated will reside. The namespace specified
13
+ # is the root namespaces.
14
+ #
15
+ # @param [String] file the name of the file which the content belongs
16
+ #
17
+ def initialize(file)
18
+ super()
19
+ @namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
20
+ find_or_create_namespace(file)
21
+ @file = file
22
+ end
23
+
24
+ # Return the feature that has been defined. This method is the final
25
+ # method that is called when all the work is done. It is called by
26
+ # the feature parser to return the complete Feature object that was created
27
+ #
28
+ # @return [YARD::CodeObject::Cucumber::Feature] the completed feature
29
+ #
30
+ # @see YARD::Parser::Cucumber::FeatureParser
31
+ def ast
32
+ feature(get_result) unless @feature
33
+ @feature
34
+ end
35
+
36
+ #
37
+ # Feature that are found in sub-directories are considered, in the way
38
+ # that I chose to implement it, in another namespace. This is because
39
+ # when you execute a cucumber test run on a directory any sub-directories
40
+ # of features will be executed with that directory so the file is split
41
+ # and then namespaces are generated if they have not already have been.
42
+ #
43
+ # The other duty that this does is look for a README.md file within the
44
+ # specified directory of the file and loads it as the description for the
45
+ # namespace. This is useful if you want to give a particular directory
46
+ # some flavor or text to describe what is going on.
47
+ #
48
+ def find_or_create_namespace(file)
49
+ @namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
50
+
51
+ File.dirname(file).split('/').each do |directory|
52
+ @namespace = @namespace.children.find {|child| child.is_a?(YARD::CodeObjects::Cucumber::FeatureDirectory) && child.name.to_s == directory } ||
53
+ @namespace = YARD::CodeObjects::Cucumber::FeatureDirectory.new(@namespace,directory) {|dir| dir.add_file(directory)}
54
+ end
55
+
56
+ if @namespace.description == "" && File.exists?("#{File.dirname(file)}/README.md")
57
+ @namespace.description = File.read("#{File.dirname(file)}/README.md")
58
+ end
59
+ end
60
+
61
+ #
62
+ # Find the tag if it exists within the YARD Registry, if it doesn't then
63
+ # create it.
64
+ #
65
+ # We note that the tag was used in this file at the current line.
66
+ #
67
+ # Then we add the tag to the current scenario or feature. We also add the
68
+ # feature or scenario to the tag.
69
+ #
70
+ # @param [String] tag_name the name of the tag
71
+ # @param [parent] parent the scenario or feature that is going to adopt
72
+ # this tag.
73
+ #
74
+ def find_or_create_tag(tag_name,parent)
75
+ #log.debug "Processing tag #{tag_name}"
76
+ tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.value == tag_name } ||
77
+ YARD::CodeObjects::Cucumber::Tag.new(YARD::CodeObjects::Cucumber::CUCUMBER_TAG_NAMESPACE,tag_name.gsub('@','')) {|t| t.owners = [] ; t.value = tag_name ; t.total_scenarios = 0}
78
+
79
+ tag_code_object.add_file(@file,parent.line)
80
+
81
+ parent.tags << tag_code_object unless parent.tags.find {|tag| tag == tag_code_object }
82
+ tag_code_object.owners << parent unless tag_code_object.owners.find {|owner| owner == parent}
83
+ end
84
+
85
+ #
86
+ # Each feature found will call this method, generating the feature object.
87
+ # This is once, as the gherkin parser does not like multiple feature per
88
+ # file.
89
+ #
90
+ def feature(document)
91
+ #log.debug "FEATURE"
92
+ feature = document[:feature]
93
+ return unless document[:feature]
94
+ return if has_exclude_tags?(feature[:tags].map { |t| t[:name].gsub(/^@/, '') })
95
+
96
+ @feature = YARD::CodeObjects::Cucumber::Feature.new(@namespace,File.basename(@file.gsub('.feature','').gsub('.','_'))) do |f|
97
+ f.comments = feature[:comments] ? feature[:comments].map{|comment| comment[:text]}.join("\n") : ''
98
+ f.description = feature[:description] || ''
99
+ f.add_file(@file,feature[:location][:line])
100
+ f.keyword = feature[:keyword]
101
+ f.value = feature[:name]
102
+ f.tags = []
103
+
104
+ feature[:tags].each {|feature_tag| find_or_create_tag(feature_tag[:name],f) }
105
+ end
106
+
107
+ background(feature[:background]) if feature[:background]
108
+
109
+ feature[:children].each do |child|
110
+ case child[:type]
111
+ when :Background
112
+ background(child)
113
+ when :ScenarioOutline
114
+ outline = scenario_outline(child)
115
+ @feature.total_scenarios += outline.scenarios.size
116
+ when :Scenario
117
+ scenario(child)
118
+ end
119
+ end
120
+
121
+ @feature.tags.each do |feature_tag|
122
+ tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == feature_tag[:name].to_s }
123
+ tag_code_object.total_scenarios += @feature.total_scenarios
124
+ end
125
+ end
126
+
127
+ #
128
+ # Called when a background has been found
129
+ #
130
+ # @see #feature
131
+ def background(background)
132
+ #log.debug "BACKGROUND"
133
+
134
+ @background = YARD::CodeObjects::Cucumber::Scenario.new(@feature,"background") do |b|
135
+ b.comments = background[:comments] ? background[:comments].map{|comment| comment.value}.join("\n") : ''
136
+ b.description = background[:description] || ''
137
+ b.keyword = background[:keyword]
138
+ b.value = background[:name]
139
+ b.add_file(@file,background[:location][:line])
140
+ end
141
+
142
+ @feature.background = @background
143
+ @background.feature = @feature
144
+ @step_container = @background
145
+ background[:steps].each { |s|
146
+ step(s)
147
+ }
148
+ end
149
+
150
+ #
151
+ # Called when a scenario has been found
152
+ # - create a scenario
153
+ # - assign the scenario to the feature
154
+ # - assign the feature to the scenario
155
+ # - find or create tags associated with the scenario
156
+ #
157
+ # The scenario is set as the @step_container, which means that any steps
158
+ # found before another scenario is defined belong to this scenario
159
+ #
160
+ # @param [Scenario] statement is a scenario object returned from Gherkin
161
+ # @see #find_or_create_tag
162
+ #
163
+ def scenario(statement)
164
+ #log.debug "SCENARIO"
165
+
166
+ return if has_exclude_tags?(statement[:tags].map { |t| t[:name].gsub(/^@/, '') })
167
+
168
+ scenario = YARD::CodeObjects::Cucumber::Scenario.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s|
169
+ s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : ''
170
+ s.description = statement[:description] || ''
171
+ s.add_file(@file,statement[:location][:line])
172
+ s.keyword = statement[:keyword]
173
+ s.value = statement[:name]
174
+
175
+ statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) }
176
+ end
177
+
178
+ scenario.feature = @feature
179
+ @feature.scenarios << scenario
180
+ @step_container = scenario
181
+ statement[:steps].each { |s|
182
+ step(s)
183
+ }
184
+
185
+ # count scenarios for scenario level tags
186
+ scenario.tags.uniq.each { |scenario_tag|
187
+ if !scenario.feature.tags.include?(scenario_tag)
188
+ tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == scenario_tag[:name].to_s }
189
+ tag_code_object.total_scenarios += 1
190
+ end
191
+ }
192
+ end
193
+
194
+ #
195
+ # Called when a scenario outline is found. Very similar to a scenario,
196
+ # the ScenarioOutline is still a distinct object as it can contain
197
+ # multiple different example groups that can contain different values.
198
+ #
199
+ # @see #scenario
200
+ #
201
+ def scenario_outline(statement)
202
+ #log.debug "SCENARIO OUTLINE"
203
+
204
+ return if has_exclude_tags?(statement[:tags].map { |t| t[:name].gsub(/^@/, '') })
205
+
206
+ outline = YARD::CodeObjects::Cucumber::ScenarioOutline.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s|
207
+ s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : ''
208
+ s.description = statement[:description] || ''
209
+ s.add_file(@file,statement[:location][:line])
210
+ s.keyword = statement[:keyword]
211
+ s.value = statement[:name]
212
+
213
+ statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) }
214
+ end
215
+
216
+ outline.feature = @feature
217
+ @step_container = outline
218
+ statement[:steps].each { |s|
219
+ step(s)
220
+ }
221
+
222
+ statement[:examples].each { |e|
223
+ example = examples(e, outline)
224
+ }
225
+
226
+ @feature.scenarios << outline
227
+
228
+ # count scenarios for scenario outline level tags
229
+ outline.tags.uniq.each { |outline_tag|
230
+ if !outline.feature.tags.include?(outline_tag)
231
+ tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == outline_tag[:name].to_s }
232
+ tag_code_object.total_scenarios += outline.scenarios.size
233
+ end
234
+ }
235
+
236
+ # count scenarios for example table level tags
237
+ outline.examples.each { |example|
238
+ unless !example.tags.any?
239
+ example.tags.uniq.each { |example_tag|
240
+ if !outline.feature.tags.include?(example_tag) && !outline.tags.include?(example_tag)
241
+ tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == example_tag[:name].to_s }
242
+ tag_code_object.total_scenarios += example.data.size
243
+ end
244
+ }
245
+ end
246
+ }
247
+
248
+ return outline
249
+ end
250
+
251
+
252
+ #
253
+ # Examples for a scenario outline are called here. This section differs
254
+ # from the Cucumber parser because here each of the examples are exploded
255
+ # out here as individual scenarios and step definitions. This is so that
256
+ # later we can ensure that we have all the variations of the scenario
257
+ # outline defined to be displayed.
258
+ #
259
+ def examples(examples, outline)
260
+ #log.debug "EXAMPLES"
261
+ return if has_exclude_tags?(examples[:tags].map { |t| t[:name].gsub(/^@/, '') })
262
+ example = YARD::CodeObjects::Cucumber::ScenarioOutline::Examples.new(:keyword => examples[:keyword],
263
+ :name => examples[:name],
264
+ :line => examples[:location][:line],
265
+ :comments => examples[:comments] ? examples.comments.map{|comment| comment.value}.join("\n") : '',
266
+ :rows => [],
267
+ :tags => [],
268
+ :scenario => outline )
269
+
270
+ unless !examples[:tags].any?
271
+ examples[:tags].each {|example_tag| find_or_create_tag(example_tag[:name], example)}
272
+ end
273
+
274
+ example.rows = [examples[:tableHeader][:cells].map{ |c| c[:value] }] if examples[:tableHeader]
275
+ example.rows += matrix(examples[:tableBody]) if examples[:tableBody]
276
+
277
+ # add the example to the step containers list of examples
278
+
279
+ @step_container.examples << example
280
+
281
+ # For each example data row we want to generate a new scenario using our
282
+ # current scenario as the template.
283
+
284
+ example.data.length.times do |row_index|
285
+
286
+ # Generate a copy of the scenario.
287
+
288
+ scenario = YARD::CodeObjects::Cucumber::Scenario.new(@step_container,"example_#{@step_container.scenarios.length + 1}") do |s|
289
+ s.comments = @step_container.comments
290
+ s.description = @step_container.description
291
+ s.add_file(@file,@step_container.line_number)
292
+ s.keyword = @step_container.keyword
293
+ s.value = "#{@step_container.value} (#{@step_container.scenarios.length + 1})"
294
+ end
295
+
296
+ # Generate a copy of the scenario steps.
297
+
298
+ @step_container.steps.each do |step|
299
+ step_instance = YARD::CodeObjects::Cucumber::Step.new(scenario,step.line_number.to_s) do |s|
300
+ s.keyword = step.keyword.dup
301
+ s.value = step.value.dup
302
+ s.add_file(@file,step.line_number)
303
+
304
+ s.text = step.text.dup if step.has_text?
305
+ s.table = clone_table(step.table) if step.has_table?
306
+ end
307
+
308
+ # Look at the particular data for the example row and do a simple
309
+ # find and replace of the <key> with the associated values.
310
+
311
+ example.values_for_row(row_index).each do |key,text|
312
+ text ||= "" #handle empty cells in the example table
313
+ step_instance.value.gsub!("<#{key}>",text)
314
+ step_instance.text.gsub!("<#{key}>",text) if step_instance.has_text?
315
+ step_instance.table.each{|row| row.each{|col| col.gsub!("<#{key}>",text)}} if step_instance.has_table?
316
+ end
317
+
318
+ # Connect these steps that we created to the scenario we created
319
+ # and then add the steps to the scenario created.
320
+
321
+ step_instance.scenario = scenario
322
+ scenario.steps << step_instance
323
+ end
324
+
325
+ # Add the scenario to the list of scenarios maintained by the feature
326
+ # and add the feature to the scenario
327
+
328
+ scenario.feature = @feature
329
+ @step_container.scenarios << scenario
330
+
331
+ end
332
+
333
+ return example
334
+ end
335
+
336
+ #
337
+ # Called when a step is found. The step is refered to a table owner, though
338
+ # not all steps have a table or multliline arguments associated with them.
339
+ #
340
+ # If a multiline string is present with the step it is included as the text
341
+ # of the step. If the step has a table it is added to the step using the
342
+ # same method used by the Cucumber Gherkin model.
343
+ #
344
+ def step(step)
345
+ #log.debug "STEP"
346
+
347
+ @table_owner = YARD::CodeObjects::Cucumber::Step.new(@step_container,"#{step[:location][:line]}") do |s|
348
+ s.keyword = step[:keyword]
349
+ s.value = step[:text]
350
+ s.add_file(@file,step[:location][:line])
351
+ end
352
+
353
+ @table_owner.comments = step[:comments] ? step[:comments].map{|comment| comment.value}.join("\n") : ''
354
+
355
+ multiline_arg = step[:argument]
356
+
357
+ case(multiline_arg[:type])
358
+ when :DocString
359
+ @table_owner.text = multiline_arg[:content]
360
+ when :DataTable
361
+ #log.info "Matrix: #{matrix(multiline_arg).collect{|row| row.collect{|cell| cell.class } }.flatten.join("\n")}"
362
+ @table_owner.table = matrix(multiline_arg[:rows])
363
+ end if multiline_arg
364
+
365
+ @table_owner.scenario = @step_container
366
+ @step_container.steps << @table_owner
367
+ end
368
+
369
+ # Defined in the cucumber version so left here. No events for the end-of-file
370
+ def eof
371
+ end
372
+
373
+ # When a syntax error were to occurr. This parser is not interested in errors
374
+ def syntax_error(state, event, legal_events, line)
375
+ # raise "SYNTAX ERROR"
376
+ end
377
+
378
+ private
379
+ def matrix(gherkin_table)
380
+ gherkin_table.map {|gherkin_row| gherkin_row[:cells].map{ |cell| cell[:value] } }
381
+ end
382
+
383
+ #
384
+ # This helper method is used to deteremine what class is the current
385
+ # Gherkin class.
386
+ #
387
+ # @return [Class] the class that is the current supported Gherkin Model
388
+ # for multiline strings. Prior to Gherkin 2.4.0 this was the PyString
389
+ # class. As of Gherkin 2.4.0 it is the DocString class.
390
+ def gherkin_multiline_string_class
391
+ if defined?(Gherkin::Formatter::Model::PyString)
392
+ Gherkin::Formatter::Model::PyString
393
+ elsif defined?(Gherkin::Formatter::Model::DocString)
394
+ Gherkin::Formatter::Model::DocString
395
+ else
396
+ raise "Unable to find a suitable class in the Gherkin Library to parse the multiline step data."
397
+ end
398
+ end
399
+
400
+ def clone_table(base)
401
+ base.map {|row| row.map {|cell| cell.dup }}
402
+ end
403
+
404
+ def has_exclude_tags?(tags)
405
+ if YARD::Config.options["yard-gherkin-turnip"] and YARD::Config.options["yard-gherkin-turnip"]["exclude_tags"]
406
+ return true unless (YARD::Config.options["yard-gherkin-turnip"]["exclude_tags"] & tags).empty?
407
+ end
408
+ end
409
+
410
+ end
411
+ end
412
+ end