Class: Lucid::Parser::GherkinRepr
- Inherits:
-
Gherkin::AstBuilder
- Object
- Gherkin::AstBuilder
- Lucid::Parser::GherkinRepr
- Defined in:
- lib/lucid/gherkin_repr.rb
Instance Method Summary collapse
-
#ast ⇒ Object
This method returns the feature that has been defined.
-
#background(background) ⇒ Object
Called when a Background has been found.
-
#eof ⇒ Object
This is necessary because it is defined in many of the tooling that supports Gherkin, but there are no events for the end-of-file.
-
#examples(examples) ⇒ Object
Examples for a scenario outline are found.
-
#feature(document) ⇒ Object
Each feature found will call this method, generating the feature object.
-
#find_or_create_namespace(file) ⇒ Object
Features that are found in sub-directories are considered to be in another namespace.
-
#find_or_create_tag(tag_name, parent) ⇒ Object
A given tag can be searched for, within the YARD Registry, to see if it exists and, if it doesn’t, to create it.
-
#initialize(file) ⇒ GherkinRepr
constructor
This class serves as the Gherkin representation for each feature file that is found.
-
#scenario(statement) ⇒ Object
Called when a scenario has been found.
-
#scenario_outline(statement) ⇒ Object
Called when a scenario outline is found.
-
#step(step) ⇒ Object
Called when a step is found.
-
#syntax_error(state, event, legal_events, line) ⇒ Object
This method exists when there is a syntax error.
Constructor Details
#initialize(file) ⇒ GherkinRepr
This class serves as the Gherkin representation for each feature file that is found. Specifically, the top level Feature object of each such file is given a representation. The Gherkin parser calls the various methods within in this class as it finds Gherkin-style elements.
This process is similar to how most Gherkin tools generate an Abstract Syntax Tree. Here what gets generated are various YARD::CodeObjects.
A namespace is specified and that is the place in the YARD namespacing where all features generated will reside. The namespace specified is the root namespace. This is the equivalent of the top-level directory holding all of the feature files.
16 17 18 19 20 21 |
# File 'lib/lucid/gherkin_repr.rb', line 16 def initialize(file) super() @namespace = YARD::CodeObjects::Lucid::LUCID_NAMESPACE find_or_create_namespace(file) @file = file end |
Instance Method Details
#ast ⇒ Object
This method returns the feature that has been defined. This is the final method that is called when all the work is done. What gets returned is the complete Feature object that was built.
26 27 28 29 |
# File 'lib/lucid/gherkin_repr.rb', line 26 def ast feature(get_result) unless @feature @feature end |
#background(background) ⇒ Object
Called when a Background has been found. Note that to Gherkin a Background is really just another type of Scenario. The difference is that backgrounds get special processing during execution.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/lucid/gherkin_repr.rb', line 102 def background(background) @background = YARD::CodeObjects::Lucid::Scenario.new(@feature,"background") do |b| b.comments = background[:comments] ? background[:comments].map{|comment| comment.value}.join("\n") : '' b.description = background[:description] || '' b.keyword = background[:keyword] b.value = background[:name] b.add_file(@file,background[:location][:line]) end @feature.background = @background @background.feature = @feature @step_container = @background background[:steps].each { |s| step(s) } end |
#eof ⇒ Object
This is necessary because it is defined in many of the tooling that supports Gherkin, but there are no events for the end-of-file.
270 271 |
# File 'lib/lucid/gherkin_repr.rb', line 270 def eof end |
#examples(examples) ⇒ Object
Examples for a scenario outline are found. From a parsing perspective, the logic differs here from how a Gherkin-supporting tool parses for execution. For the needs of being lucid, each of the examples are expanded out as individual scenarios and step definitions. This is done so that it is possible to ensure that all variations of the scenario outline defined are displayed.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/lucid/gherkin_repr.rb', line 174 def examples(examples) example = YARD::CodeObjects::Lucid::ScenarioOutline::Examples.new(:keyword => examples[:keyword], :name => examples[:name], :line => examples[:location][:line], :comments => examples[:comments] ? examples.comments.map{|comment| comment.value}.join("\n") : '', :rows => [] ) example.rows = [examples[:tableHeader][:cells].map{ |c| c[:value] }] if examples[:tableHeader] example.rows += matrix(examples[:tableBody]) if examples[:tableBody] @step_container.examples << example # For each example data row, a new scenario must be generated using the # current scenario as the template. example.data.length.times do |row_index| # Generate a copy of the scenario. scenario = YARD::CodeObjects::Lucid::Scenario.new(@step_container,"example_#{@step_container.scenarios.length + 1}") do |s| s.comments = @step_container.comments s.description = @step_container.description s.add_file(@file,@step_container.line_number) s.keyword = @step_container.keyword s.value = "#{@step_container.value} (#{@step_container.scenarios.length + 1})" end # Generate a copy of the scenario steps. @step_container.steps.each do |step| step_instance = YARD::CodeObjects::Lucid::Step.new(scenario,step.line_number) do |s| s.keyword = step.keyword.dup s.value = step.value.dup s.add_file(@file,step.line_number) s.text = step.text.dup if step.has_text? s.table = clone_table(step.table) if step.has_table? end # Look at the particular data for the example row and do a simple # find and replace of the <key> with the associated values. It's # necessary ot handle empty cells in an example table. example.values_for_row(row_index).each do |key,text| text ||= "" step_instance.value.gsub!("<#{key}>",text) step_instance.text.gsub!("<#{key}>",text) if step_instance.has_text? step_instance.table.each{ |row| row.each { |col| col.gsub!("<#{key}>",text) } } if step_instance.has_table? end # Connect the steps that have been created to the scenario that was # created and then add the steps to the scenario. step_instance.scenario = scenario scenario.steps << step_instance end # Add the scenario to the list of scenarios maintained by the feature # and add the feature to the scenario. scenario.feature = @feature @step_container.scenarios << scenario end end |
#feature(document) ⇒ Object
Each feature found will call this method, generating the feature object. This happens only once, as the Gherkin parser does not allow for multiple features per feature file.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/lucid/gherkin_repr.rb', line 71 def feature(document) #log.debug "FEATURE" feature = document[:feature] return unless document[:feature] return if (feature[:tags].map { |t| t[:name].gsub(/^@/, '') }) @feature = YARD::CodeObjects::Lucid::Feature.new(@namespace,File.basename(@file.gsub('.feature','').gsub('.','_'))) do |f| f.comments = feature[:comments] ? feature[:comments].map{|comment| comment[:text]}.join("\n") : '' f.description = feature[:description] || '' f.add_file(@file,feature[:location][:line]) f.keyword = feature[:keyword] f.value = feature[:name] f. = [] feature[:tags].each {|feature_tag| find_or_create_tag(feature_tag[:name],f) } end feature[:children].each { |s| case s[:type] when :Background background(s) when :ScenarioOutline scenario_outline(s) when :Scenario scenario(s) end } end |
#find_or_create_namespace(file) ⇒ Object
Features that are found in sub-directories are considered to be in another namespace. The rationale is that with Gherkin-supporting test tools, when you execute a test run on a directory, any sub-directories of features will be executed with that directory.
Part of the process involves the discovery of a README.md file within the specified directory of the feature file and loads that file as the description for the namespace. This is useful if you want to give a particular directory some supporting documentation.
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/lucid/gherkin_repr.rb', line 40 def find_or_create_namespace(file) @namespace = YARD::CodeObjects::Lucid::LUCID_NAMESPACE File.dirname(file).split('/').each do |directory| @namespace = @namespace.children.find { |child| child.is_a?(YARD::CodeObjects::Lucid::FeatureDirectory) && child.name.to_s == directory } || @namespace = YARD::CodeObjects::Lucid::FeatureDirectory.new(@namespace,directory) { |dir| dir.add_file(directory) } end if @namespace.description == "" && File.exists?("#{File.dirname(file)}/README.md") @namespace.description = File.read("#{File.dirname(file)}/README.md") end end |
#find_or_create_tag(tag_name, parent) ⇒ Object
A given tag can be searched for, within the YARD Registry, to see if it exists and, if it doesn’t, to create it. The logic will note that the tag was used in the given file at whatever the current line is and then add the tag to the current scenario or feature. It’s also necessary to add the feature or scenario to the tag.
58 59 60 61 62 63 64 65 66 |
# File 'lib/lucid/gherkin_repr.rb', line 58 def find_or_create_tag(tag_name, parent) tag_code_object = YARD::Registry.all(:tag).find { |tag| tag.value == tag_name } || YARD::CodeObjects::Lucid::Tag.new(YARD::CodeObjects::Lucid::LUCID_TAG_NAMESPACE,tag_name.gsub('@','')) { |t| t.owners = [] ; t.value = tag_name } tag_code_object.add_file(@file,parent.line) parent. << tag_code_object unless parent..find { |tag| tag == tag_code_object } tag_code_object.owners << parent unless tag_code_object.owners.find { |owner| owner == parent } end |
#scenario(statement) ⇒ Object
Called when a scenario has been found. This will create a scenario object, assign the scenario object to the feature object (and also assigne the feature object to the scenario object), as well as find or create tags that are associated with the scenario.
The scenario is set as a type called a @step_container. This means that any steps found before another scenario is defined belong to this scenario.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/lucid/gherkin_repr.rb', line 125 def scenario(statement) return if (statement[:tags].map { |t| t[:name].gsub(/^@/, '') }) scenario = YARD::CodeObjects::Lucid::Scenario.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s| s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : '' s.description = statement[:description] || '' s.add_file(@file,statement[:location][:line]) s.keyword = statement[:keyword] s.value = statement[:name] statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) } end scenario.feature = @feature @feature.scenarios << scenario @step_container = scenario statement[:steps].each { |s| step(s) } end |
#scenario_outline(statement) ⇒ Object
Called when a scenario outline is found. This is very similar to a scenario but, to Gherkin, the ScenarioOutline is still a distinct object. The reason for this is because it can contain multiple different example groups that can contain different values.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/lucid/gherkin_repr.rb', line 148 def scenario_outline(statement) return if (statement[:tags].map { |t| t[:name].gsub(/^@/, '') }) outline = YARD::CodeObjects::Lucid::ScenarioOutline.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s| s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : '' s.description = statement[:description] || '' s.add_file(@file,statement[:location][:line]) s.keyword = statement[:keyword] s.value = statement[:name] statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) } end outline.feature = @feature @feature.scenarios << outline @step_container = outline statement[:steps].each { |s| step(s) } statement[:examples].each { |e| examples(e) } end |
#step(step) ⇒ Object
Called when a step is found. The logic here is that each step is referred to a table owner. This is the case even though not all steps have a table or multliline arguments associated with them.
If a multiline string is present with the step it is included as the text of the step. If the step has a table it is added to the step using the same method used by the standard Gherkin model.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/lucid/gherkin_repr.rb', line 246 def step(step) @table_owner = YARD::CodeObjects::Lucid::Step.new(@step_container,"#{step[:location][:line]}") do |s| s.keyword = step[:keyword] s.value = step[:text] s.add_file(@file,step[:location][:line]) end @table_owner.comments = step[:comments] ? step[:comments].map{|comment| comment.value}.join("\n") : '' multiline_arg = step[:argument] case(multiline_arg[:type]) when :DocString @table_owner.text = multiline_arg[:content] when :DataTable @table_owner.table = matrix(multiline_arg[:rows]) end if multiline_arg @table_owner.scenario = @step_container @step_container.steps << @table_owner end |
#syntax_error(state, event, legal_events, line) ⇒ Object
This method exists when there is a syntax error. That matters for Gherkin execution but not for the parsing being done here.
275 276 |
# File 'lib/lucid/gherkin_repr.rb', line 275 def syntax_error(state, event, legal_events, line) end |