yard-nrser-cucumber 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +61 -0
  3. data/.rspec +3 -0
  4. data/.yardopts +2 -0
  5. data/Gemfile +6 -0
  6. data/History.txt +288 -0
  7. data/LICENSE.txt +22 -0
  8. data/NAME +1 -0
  9. data/README.md +198 -0
  10. data/Rakefile +27 -0
  11. data/VERSION +1 -0
  12. data/example/README.md +8 -0
  13. data/example/child_feature/README.md +21 -0
  14. data/example/child_feature/child.feature +11 -0
  15. data/example/child_feature/grandchild_feature/grandchild.feature +12 -0
  16. data/example/empty.feature +2 -0
  17. data/example/french.feature +18 -0
  18. data/example/scenario.feature +63 -0
  19. data/example/scenario_outline.feature +100 -0
  20. data/example/scenario_outline_multi.feature +15 -0
  21. data/example/step_definitions/example.step.rb +122 -0
  22. data/example/step_definitions/first.step.rb +21 -0
  23. data/example/step_definitions/french_steps.rb +32 -0
  24. data/example/step_definitions/struct.rb +11 -0
  25. data/example/step_definitions/support/env.rb +7 -0
  26. data/example/step_definitions/support/env_support.rb +12 -0
  27. data/example/step_definitions/support/support.rb +6 -0
  28. data/example/tags.feature +18 -0
  29. data/example/transform.feature +18 -0
  30. data/lib/cucumber/city_builder.rb +412 -0
  31. data/lib/docserver/default/fulldoc/html/js/cucumber.js +85 -0
  32. data/lib/docserver/default/layout/html/headers.erb +13 -0
  33. data/lib/docserver/doc_server/full_list/html/full_list.erb +39 -0
  34. data/lib/docserver/doc_server/full_list/html/setup.rb +18 -0
  35. data/lib/templates/default/feature/html/feature.erb +39 -0
  36. data/lib/templates/default/feature/html/no_steps_defined.erb +1 -0
  37. data/lib/templates/default/feature/html/outline.erb +56 -0
  38. data/lib/templates/default/feature/html/pystring.erb +3 -0
  39. data/lib/templates/default/feature/html/scenario.erb +57 -0
  40. data/lib/templates/default/feature/html/setup.rb +51 -0
  41. data/lib/templates/default/feature/html/steps.erb +39 -0
  42. data/lib/templates/default/feature/html/table.erb +20 -0
  43. data/lib/templates/default/featuredirectory/html/alpha_table.erb +30 -0
  44. data/lib/templates/default/featuredirectory/html/directory.erb +32 -0
  45. data/lib/templates/default/featuredirectory/html/setup.rb +37 -0
  46. data/lib/templates/default/featuretags/html/namespace.erb +159 -0
  47. data/lib/templates/default/featuretags/html/setup.rb +34 -0
  48. data/lib/templates/default/fulldoc/html/css/cucumber.css +226 -0
  49. data/lib/templates/default/fulldoc/html/directories.erb +27 -0
  50. data/lib/templates/default/fulldoc/html/full_list_featuredirectories.erb +11 -0
  51. data/lib/templates/default/fulldoc/html/full_list_features.erb +37 -0
  52. data/lib/templates/default/fulldoc/html/full_list_stepdefinitions.erb +20 -0
  53. data/lib/templates/default/fulldoc/html/full_list_steps.erb +20 -0
  54. data/lib/templates/default/fulldoc/html/full_list_tags.erb +16 -0
  55. data/lib/templates/default/fulldoc/html/js/cucumber.js +333 -0
  56. data/lib/templates/default/fulldoc/html/setup.rb +208 -0
  57. data/lib/templates/default/layout/html/setup.rb +131 -0
  58. data/lib/templates/default/requirements/html/alpha_table.erb +26 -0
  59. data/lib/templates/default/requirements/html/requirements.erb +50 -0
  60. data/lib/templates/default/requirements/html/setup.rb +51 -0
  61. data/lib/templates/default/steptransformers/html/header.erb +12 -0
  62. data/lib/templates/default/steptransformers/html/index.erb +10 -0
  63. data/lib/templates/default/steptransformers/html/setup.rb +94 -0
  64. data/lib/templates/default/steptransformers/html/transformers.erb +80 -0
  65. data/lib/templates/default/steptransformers/html/undefinedsteps.erb +26 -0
  66. data/lib/templates/default/tag/html/alpha_table.erb +33 -0
  67. data/lib/templates/default/tag/html/setup.rb +27 -0
  68. data/lib/templates/default/tag/html/tag.erb +35 -0
  69. data/lib/yard-nrser-cucumber.rb +49 -0
  70. data/lib/yard/code_objects/cucumber/base.rb +24 -0
  71. data/lib/yard/code_objects/cucumber/feature.rb +16 -0
  72. data/lib/yard/code_objects/cucumber/namespace_object.rb +55 -0
  73. data/lib/yard/code_objects/cucumber/scenario.rb +25 -0
  74. data/lib/yard/code_objects/cucumber/scenario_outline.rb +68 -0
  75. data/lib/yard/code_objects/cucumber/step.rb +41 -0
  76. data/lib/yard/code_objects/cucumber/tag.rb +31 -0
  77. data/lib/yard/code_objects/step_definition.rb +4 -0
  78. data/lib/yard/code_objects/step_transform.rb +4 -0
  79. data/lib/yard/code_objects/step_transformer.rb +94 -0
  80. data/lib/yard/handlers/constant_transform_handler.rb +98 -0
  81. data/lib/yard/handlers/cucumber/base.rb +21 -0
  82. data/lib/yard/handlers/cucumber/feature_handler.rb +131 -0
  83. data/lib/yard/handlers/legacy/step_definition_handler.rb +45 -0
  84. data/lib/yard/handlers/legacy/step_transform_handler.rb +24 -0
  85. data/lib/yard/handlers/step_definition_handler.rb +86 -0
  86. data/lib/yard/handlers/step_transform_handler.rb +50 -0
  87. data/lib/yard/nrser/cucumber/version.rb +103 -0
  88. data/lib/yard/parser/cucumber/feature.rb +72 -0
  89. data/lib/yard/server/adapter.rb +43 -0
  90. data/lib/yard/server/commands/list_command.rb +31 -0
  91. data/lib/yard/server/router.rb +32 -0
  92. data/lib/yard/templates/helpers/base_helper.rb +26 -0
  93. data/lib/yard/templates/helpers/html_helper.rb +153 -0
  94. data/yard-nrser-cucumber.gemspec +75 -0
  95. metadata +220 -0
@@ -0,0 +1,25 @@
1
+
2
+ require 'active_support/core_ext/string/filters'
3
+
4
+ module YARD::CodeObjects::Cucumber
5
+
6
+ class Scenario < NamespaceObject
7
+
8
+ attr_accessor :value, :comments, :keyword, :description, :steps, :tags, :feature
9
+
10
+ def initialize(namespace, name)
11
+ super(namespace, name.to_s.strip)
12
+ @comments = @description = @keyword = @value = @feature = nil
13
+ @steps = []
14
+ @tags = []
15
+ end
16
+
17
+ def background?
18
+ @keyword == "Background"
19
+ end
20
+
21
+ def outline?
22
+ false
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,68 @@
1
+ module YARD::CodeObjects::Cucumber
2
+ class ScenarioOutline < NamespaceObject
3
+
4
+ attr_accessor :value, :comments, :keyword, :description, :steps, :tags, :feature
5
+ attr_accessor :scenarios, :examples
6
+
7
+ def initialize(namespace, name)
8
+ super(namespace, name.to_s.strip)
9
+ @comments = @description = @value = @feature = nil
10
+ @steps = []
11
+ @tags = []
12
+ @scenarios = []
13
+ @examples = []
14
+ end
15
+
16
+ def background?
17
+ false
18
+ end
19
+
20
+ def outline?
21
+ true
22
+ end
23
+
24
+ def examples?
25
+ @examples.find { |example| example.rows }
26
+ end
27
+
28
+ class Examples
29
+
30
+ attr_accessor :name, :line, :keyword, :comments, :rows, :tags, :scenario
31
+
32
+ # The first row of the rows contains the headers for the table
33
+ def headers
34
+ rows.first
35
+ end
36
+
37
+ # The data of the table starts at the second row. When there is no data then
38
+ # return a empty string.
39
+ def data
40
+ rows ? rows[1..-1] : ""
41
+ end
42
+
43
+ def values_for_row(row)
44
+ hash = {}
45
+
46
+ headers.each_with_index do |header, index|
47
+ hash[header] = data[row][index]
48
+ end
49
+
50
+ hash
51
+ end
52
+
53
+ def to_hash
54
+ hash = {}
55
+
56
+ rows.each_with_index do |header, index|
57
+ hash[header] = rows.collect { |row| row[index] }
58
+ end
59
+
60
+ hash
61
+ end
62
+
63
+ def initialize(parameters = {})
64
+ parameters.each { |key, value| send("#{key.to_sym}=", value) if respond_to? "#{key.to_sym}=" }
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ module YARD::CodeObjects::Cucumber
2
+ class Step < Base
3
+
4
+ attr_accessor :comments,
5
+ :definition,
6
+ :examples,
7
+ :keyword,
8
+ :scenario,
9
+ :table,
10
+ :text,
11
+ :transforms,
12
+ :value
13
+
14
+ def initialize(namespace, name)
15
+ super(namespace, name.to_s.strip)
16
+ @comments = @definition = @description = @keyword = @table = @text = @value = nil
17
+ @examples = {}
18
+ @transforms = []
19
+ end
20
+
21
+ def has_table?
22
+ !@table.nil?
23
+ end
24
+
25
+ def has_text?
26
+ !@text.nil?
27
+ end
28
+
29
+ def definition=(stepdef)
30
+ @definition = stepdef
31
+
32
+ unless stepdef.steps.map(&:files).include?(files)
33
+ stepdef.steps << self
34
+ end
35
+ end
36
+
37
+ def transformed?
38
+ !@transforms.empty?
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ module YARD::CodeObjects::Cucumber
2
+ class Tag < NamespaceObject
3
+
4
+ attr_accessor :value, :owners, :total_scenarios
5
+
6
+ def features
7
+ @owners.find_all { |owner| owner.is_a?(Feature) }
8
+ end
9
+
10
+ def scenarios
11
+ all = @owners.find_all do |owner|
12
+ owner.is_a?(Scenario) || owner.is_a?(ScenarioOutline) || ()
13
+ end
14
+
15
+ @owners.each do |owner|
16
+ if owner.is_a?(ScenarioOutline::Examples) && !all.include?(owner.scenario)
17
+ all << owner.scenario
18
+ end
19
+ end
20
+ all
21
+ end
22
+
23
+ def indirect_scenarios
24
+ @owners.find_all { |owner| owner.is_a?(Feature) }.collect { |feature| feature.scenarios }.flatten
25
+ end
26
+
27
+ def all_scenarios
28
+ scenarios + indirect_scenarios
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ module YARD::CodeObjects
2
+ class StepDefinitionObject < StepTransformerObject;
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module YARD::CodeObjects
2
+ class StepTransformObject < StepTransformerObject;
3
+ end
4
+ end
@@ -0,0 +1,94 @@
1
+ module YARD::CodeObjects
2
+ class StepTransformerObject < Base
3
+
4
+ include Cucumber::LocationHelper
5
+
6
+ attr_reader :constants, :keyword, :source, :value, :literal_value
7
+ attr_accessor :steps, :pending, :substeps
8
+
9
+ # This defines an escape pattern within a string or regex:
10
+ # /^the first #{CONSTANT} step$/
11
+ #
12
+ # This is used below in the value to process it if there happen to be
13
+ # constants defined here.
14
+ #
15
+ # @note this does not handle the result of method calls
16
+ # @note this does not handle multiple constants within the same escaped area
17
+ #
18
+ def escape_pattern
19
+ /#\{\s*(\w+)\s*\}/
20
+ end
21
+
22
+ #
23
+ # When requesting a step tranformer object value, process it, if it hasn't
24
+ # alredy been processed, replacing any constants that may be lurking within
25
+ # the value.
26
+ #
27
+ # Processing it means looking for any escaped characters that happen to be
28
+ # CONSTANTS that could be matched and then replaced. This is done recursively
29
+ # as CONSTANTS can be defined with more CONSTANTS.
30
+ #
31
+ def value
32
+ unless @processed
33
+ @processed = true
34
+ until (nested = constants_from_value).empty?
35
+ nested.each {|n| @value.gsub!(value_regex(n),find_value_for_constant(n)) }
36
+ end
37
+ end
38
+
39
+ @value
40
+ end
41
+
42
+ #
43
+ # Set the literal value and the value of the step definition.
44
+ #
45
+ # The literal value is as it appears in the step definition file with any
46
+ # constants. The value, when retrieved will attempt to replace those
47
+ # constants with their regex or string equivalents to hopefully match more
48
+ # steps and step definitions.
49
+ #
50
+ #
51
+ def value=(value)
52
+ @literal_value = format_source(value)
53
+ @value = format_source(value)
54
+
55
+ @steps = []
56
+ value
57
+ end
58
+
59
+ # Generate a regex with the step transformers value
60
+ def regex
61
+ @regex ||= /#{strip_regex_from(value)}/
62
+ end
63
+
64
+ # Look through the specified data for the escape pattern and return an array
65
+ # of those constants found. This defaults to the @value within step transformer
66
+ # as it is used internally, however, it can be called externally if it's
67
+ # needed somewhere.
68
+ def constants_from_value(data=@value)
69
+ data.scan(escape_pattern).flatten.collect { |value| value.strip }
70
+ end
71
+
72
+ protected
73
+
74
+ #
75
+ # Looking through all the constants in the registry and returning the value
76
+ # with the regex items replaced from the constnat if present
77
+ #
78
+ def find_value_for_constant(name)
79
+ constant = YARD::Registry.all(:constant).find{|c| c.name == name.to_sym }
80
+ log.warn "StepTransformer#find_value_for_constant : Could not find the CONSTANT [#{name}] using the string value." unless constant
81
+ constant ? strip_regex_from(constant.value) : name
82
+ end
83
+
84
+ # Return a regex of the value
85
+ def value_regex(value)
86
+ /#\{\s*#{value}\s*\}/
87
+ end
88
+
89
+ # Step the regex starting / and ending / from the value
90
+ def strip_regex_from(value)
91
+ value.gsub(/^\/|\/$/,'')
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,98 @@
1
+ # There might be a nicer way to decorate this class but with my limited knowledge could only get this handler
2
+ # to be applied after the default constant handler by inheriting from the default constant handler.
3
+ # This is required so that the value assigned from the transform is not overridden in the registry by the
4
+ # default handler
5
+ class YARD::Handlers::Ruby::ConstantTransformHandler < YARD::Handlers::Ruby::ConstantHandler
6
+ include YARD::Handlers::Ruby::StructHandlerMethods
7
+ handles :assign
8
+
9
+ namespace_only
10
+
11
+ process do
12
+ begin
13
+ if statement[1][0][0] == "Transform"
14
+ name = statement[0][0][0]
15
+ # Move the docstring to the transform statement
16
+ statement[1].docstring = statement.docstring
17
+ # Set the docstring on the constant to reference the transform that will be processed
18
+ statement.docstring = "Reference to {#{YARD::CodeObjects::Cucumber::CUCUMBER_STEPTRANSFORM_NAMESPACE}::#{name} transform}"
19
+ value = statement[1][1].source
20
+ value = substitute(value)
21
+ value = convert_captures(strip_anchors(value))
22
+ instance = register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value }
23
+ # specify the owner so that the transform can use the registered constant's name
24
+ parse_block(statement[1], {:owner => instance})
25
+ elsif statement[1][0][0] == "ParameterType"
26
+ name = statement[0][0][0]
27
+ # Move the docstring to the transform statement
28
+ statement[1].docstring = statement.docstring
29
+ # Set the docstring on the constant to reference the transform that will be processed
30
+ statement.docstring = "Reference to {#{YARD::CodeObjects::Cucumber::CUCUMBER_STEPTRANSFORM_NAMESPACE}::#{name} transform}"
31
+
32
+ value = find(statement, :label, 'regexp:').parent.children[1].source
33
+
34
+ value = substitute(value)
35
+ value = convert_captures(strip_anchors(value))
36
+ instance = register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value }
37
+ # specify the owner so that the transform can use the registered constant's name
38
+ parse_block(statement[1], {:owner => instance})
39
+ end
40
+ rescue
41
+ # This supresses any errors where any of the statement elements are out of bounds.
42
+ # In this case the or in cases where the object being assigned is not a Transform
43
+ # the default constant handler will already have performed the relevant action
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def find(node, node_type, value)
50
+ node.traverse { |child| return(child) if node_type == child.type && child.source == value }
51
+ self
52
+ end
53
+
54
+ # Cucumber's Transform object overrides the to_s function and strips
55
+ # the anchor tags ^$ and any captures so that id it is interpolated in a step definition
56
+ # the it can appear anywhere in the step without being effected by position or captures
57
+ def convert_captures(regexp_source)
58
+ regexp_source
59
+ .gsub(/(\()(?!\?[<:=!])/,'(?:')
60
+ .gsub(/(\(\?<)(?![=!])/,'(?:<')
61
+ end
62
+
63
+ def strip_anchors(regexp_source)
64
+ regexp_source.
65
+ gsub(/(^\(\/|\/\)$)/, '').
66
+ gsub(/(^\^|\$$)/, '')
67
+ end
68
+
69
+ # Support for interpolation in the Transform's Regex
70
+ def substitute(data)
71
+ until (nested = constants_from_value(data)).empty?
72
+ nested.each {|n| data.gsub!(value_regex(n),find_value_for_constant(n)) }
73
+ end
74
+ data
75
+ end
76
+
77
+ def constants_from_value(data)
78
+ escape_pattern = /#\{\s*(\w+)\s*\}/
79
+ data.scan(escape_pattern).flatten.collect { |value| value.strip }
80
+ end
81
+
82
+ # Return a regex of the value
83
+ def value_regex(value)
84
+ /#\{\s*#{value}\s*\}/
85
+ end
86
+
87
+ def find_value_for_constant(name)
88
+ constant = YARD::Registry.all(:constant).find{|c| c.name == name.to_sym }
89
+ log.warn "ConstantTransformHandler#find_value_for_constant : Could not find the CONSTANT [#{name}] using the string value." unless constant
90
+ constant ? strip_regex_from(constant.value) : name
91
+ end
92
+
93
+ # Step the regex starting / and ending / from the value
94
+ def strip_regex_from(value)
95
+ value.gsub(/^\/|\/$/,'')
96
+ end
97
+
98
+ end
@@ -0,0 +1,21 @@
1
+ module YARD
2
+ module Handlers
3
+ module Cucumber
4
+
5
+ class Base < Handlers::Base
6
+ class << self
7
+ include Parser::Cucumber
8
+ def handles?(node)
9
+ handlers.any? do |a_handler|
10
+ #log.debug "YARD::Handlers::Cucumber::Base#handles?(#{node.class})"
11
+ node.class == a_handler
12
+ end
13
+ end
14
+ include Parser::Cucumber
15
+ end
16
+ end
17
+
18
+ Processor.register_handler_namespace :feature, Cucumber
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,131 @@
1
+ module YARD
2
+ module Handlers
3
+ module Cucumber
4
+ class FeatureHandler < Base
5
+
6
+ handles CodeObjects::Cucumber::Feature
7
+
8
+ def process
9
+ #
10
+ # Features have already been created when they were parsed. So there
11
+ # is no need to process the feature further. Previously this is where
12
+ # feature steps were matched to step definitions and step definitions
13
+ # were matched to step transforms. This only worked if the feature
14
+ # files were were assured to be processed last which was accomplished
15
+ # by overriding YARD::SourceParser to make it load file in a similar
16
+ # order as Cucumber.
17
+ #
18
+ # As of YARD 0.7.0 this is no longer necessary as there are callbacks
19
+ # that can be registered after all the files have been loaded. That
20
+ # callback _after_parse_list_ is defined below and performs the
21
+ # functionality described above.
22
+ #
23
+ end
24
+
25
+ #
26
+ # Register, once, when that when all files are finished to perform
27
+ # the final matching of feature steps to step definitions and step
28
+ # definitions to step transforms.
29
+ #
30
+ YARD::Parser::SourceParser.after_parse_list do |files,globals|
31
+ # For every feature found in the Registry, find their steps and step
32
+ # definitions...
33
+ YARD::Registry.all(:feature).each do |feature|
34
+ log.debug "Finding #{feature.file} - steps, step definitions, and step transforms"
35
+ FeatureHandler.match_steps_to_step_definitions(feature)
36
+ end
37
+
38
+ end
39
+
40
+ class << self
41
+
42
+ @@step_definitions = nil
43
+ @@step_transforms = nil
44
+
45
+ def match_steps_to_step_definitions(statement)
46
+ # Create a cache of all of the step definitions and the step transforms
47
+ @@step_definitions = cache(:stepdefinition) unless @@step_definitions
48
+ @@step_transforms = cache(:steptransform) unless @@step_transforms
49
+
50
+ if statement
51
+ # For the background and the scenario, find the steps that have definitions
52
+ process_scenario(statement.background) if statement.background
53
+
54
+ statement.scenarios.each do |scenario|
55
+ if scenario.outline?
56
+ #log.info "Scenario Outline: #{scenario.value}"
57
+ scenario.scenarios.each_with_index do |example,index|
58
+ #log.info " * Processing Example #{index + 1}"
59
+ process_scenario(example)
60
+ end
61
+ else
62
+ process_scenario(scenario)
63
+ end
64
+ end
65
+
66
+
67
+ else
68
+ log.warn "Empty feature file. A feature failed to process correctly or contains no feature"
69
+ end
70
+
71
+ rescue YARD::Handlers::NamespaceMissingError
72
+ rescue Exception => exception
73
+ log.error "Skipping feature because an error has occurred."
74
+ log.debug "\n#{exception}\n#{exception.backtrace.join("\n")}\n"
75
+ end
76
+
77
+ #
78
+ # Store all comparable items with their compare_value as the key and the item as the value
79
+ # - Reject any compare values that contain escapes #{} as that means they have unpacked constants
80
+ #
81
+ def cache(type)
82
+ YARD::Registry.all(type).inject({}) do |hash,item|
83
+ hash[item.regex] = item if item.regex
84
+ hash
85
+ end
86
+ end
87
+
88
+ # process a scenario
89
+ def process_scenario(scenario)
90
+ scenario.steps.each {|step| process_step(step) }
91
+ end
92
+
93
+ # process a step
94
+ def process_step(step)
95
+ match_step_to_step_definition_and_transforms(step)
96
+ end
97
+
98
+ #
99
+ # Given a step object, attempt to match that step to a step
100
+ # transformation
101
+ #
102
+ def match_step_to_step_definition_and_transforms(step)
103
+ @@step_definitions.each_pair do |stepdef,stepdef_object|
104
+ stepdef_matches = step.value.match(stepdef)
105
+
106
+ if stepdef_matches
107
+ step.definition = stepdef_object
108
+ stepdef_matches[1..-1].each do |match|
109
+ @@step_transforms.each do |steptrans,steptransform_object|
110
+ if steptrans.match(match)
111
+ step.transforms << steptransform_object
112
+ steptransform_object.steps << step
113
+ end
114
+ end
115
+ end
116
+
117
+ # Step has been matched to step definition and step transforms
118
+ # TODO: If the step were to match again then we would be able to display ambigous step definitions
119
+ break
120
+
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end