module CukeModeler # An adapter that can convert the output of version 4.x and 5.x of the *gherkin* gem into input that is consumable by this gem. class Gherkin4Adapter # Adapts the given AST into the shape that this gem expects def adapt(parsed_ast) # Saving off the original data parsed_ast['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_ast)) # Removing parsed data for child elements in order to avoid duplicating data parsed_ast['cuke_modeler_parsing_data'][:feature] = nil parsed_ast['cuke_modeler_parsing_data'][:comments] = nil # Comments are stored on the feature file in gherkin 4.x parsed_ast['comments'] = [] parsed_ast[:comments].each do |comment| adapt_comment!(comment) end parsed_ast['comments'].concat(parsed_ast.delete(:comments)) adapt_feature!(parsed_ast[:feature]) if parsed_ast[:feature] parsed_ast['feature'] = parsed_ast.delete(:feature) [parsed_ast] end # Adapts the AST sub-tree that is rooted at the given feature node. def adapt_feature!(parsed_feature) # Saving off the original data parsed_feature['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_feature)) # Removing parsed data for child elements in order to avoid duplicating data parsed_feature['cuke_modeler_parsing_data'][:tags] = nil parsed_feature['cuke_modeler_parsing_data'][:children] = nil parsed_feature['keyword'] = parsed_feature.delete(:keyword) parsed_feature['name'] = parsed_feature.delete(:name) parsed_feature['description'] = parsed_feature.delete(:description) || '' parsed_feature['line'] = parsed_feature.delete(:location)[:line] parsed_feature['elements'] = [] adapt_child_elements!(parsed_feature[:children]) parsed_feature['elements'].concat(parsed_feature.delete(:children)) parsed_feature['tags'] = [] parsed_feature[:tags].each do |tag| adapt_tag!(tag) end parsed_feature['tags'].concat(parsed_feature.delete(:tags)) end # Adapts the AST sub-tree that is rooted at the given background node. def adapt_background!(parsed_background) # Saving off the original data parsed_background['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_background)) # Removing parsed data for child elements in order to avoid duplicating data parsed_background['cuke_modeler_parsing_data'][:steps] = nil parsed_background['type'] = parsed_background.delete(:type).to_s parsed_background['keyword'] = parsed_background.delete(:keyword).to_s parsed_background['name'] = parsed_background.delete(:name) parsed_background['description'] = parsed_background.delete(:description) || '' parsed_background['line'] = parsed_background.delete(:location)[:line] parsed_background['steps'] = [] parsed_background[:steps].each do |step| adapt_step!(step) end parsed_background['steps'].concat(parsed_background.delete(:steps)) end # Adapts the AST sub-tree that is rooted at the given scenario node. def adapt_scenario!(parsed_test) # Removing parsed data for child elements in order to avoid duplicating data parsed_test['cuke_modeler_parsing_data'][:tags] = nil parsed_test['cuke_modeler_parsing_data'][:steps] = nil parsed_test['name'] = parsed_test.delete(:name) parsed_test['description'] = parsed_test.delete(:description) || '' parsed_test['line'] = parsed_test.delete(:location)[:line] parsed_test['tags'] = [] parsed_test[:tags].each do |tag| adapt_tag!(tag) end parsed_test['tags'].concat(parsed_test.delete(:tags)) parsed_test['steps'] = [] parsed_test[:steps].each do |step| adapt_step!(step) end parsed_test['steps'].concat(parsed_test.delete(:steps)) end # Adapts the AST sub-tree that is rooted at the given outline node. def adapt_outline!(parsed_test) # Removing parsed data for child elements in order to avoid duplicating data parsed_test['cuke_modeler_parsing_data'][:tags] = nil parsed_test['cuke_modeler_parsing_data'][:steps] = nil parsed_test['cuke_modeler_parsing_data'][:examples] = nil parsed_test['name'] = parsed_test.delete(:name) parsed_test['description'] = parsed_test.delete(:description) || '' parsed_test['line'] = parsed_test.delete(:location)[:line] parsed_test['tags'] = [] parsed_test[:tags].each do |tag| adapt_tag!(tag) end parsed_test['tags'].concat(parsed_test.delete(:tags)) parsed_test['steps'] = [] parsed_test[:steps].each do |step| adapt_step!(step) end parsed_test['steps'].concat(parsed_test.delete(:steps)) parsed_test['examples'] = [] parsed_test[:examples].each do |step| adapt_example!(step) end parsed_test['examples'].concat(parsed_test.delete(:examples)) end # Adapts the AST sub-tree that is rooted at the given example node. def adapt_example!(parsed_example) # Saving off the original data parsed_example['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_example)) # Removing parsed data for child elements in order to avoid duplicating data parsed_example['cuke_modeler_parsing_data'][:tags] = nil parsed_example['cuke_modeler_parsing_data'][:tableHeader] = nil parsed_example['cuke_modeler_parsing_data'][:tableBody] = nil parsed_example['keyword'] = parsed_example.delete(:keyword) parsed_example['name'] = parsed_example.delete(:name) parsed_example['line'] = parsed_example.delete(:location)[:line] parsed_example['description'] = parsed_example.delete(:description) || '' parsed_example['rows'] = [] if parsed_example[:tableHeader] adapt_table_row!(parsed_example[:tableHeader]) parsed_example['rows'] << parsed_example.delete(:tableHeader) end if parsed_example[:tableBody] parsed_example[:tableBody].each do |row| adapt_table_row!(row) end parsed_example['rows'].concat(parsed_example.delete(:tableBody)) end parsed_example['tags'] = [] parsed_example[:tags].each do |tag| adapt_tag!(tag) end parsed_example['tags'].concat(parsed_example.delete(:tags)) end # Adapts the AST sub-tree that is rooted at the given tag node. def adapt_tag!(parsed_tag) # Saving off the original data parsed_tag['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_tag)) parsed_tag['name'] = parsed_tag.delete(:name) parsed_tag['line'] = parsed_tag.delete(:location)[:line] end # Adapts the AST sub-tree that is rooted at the given comment node. def adapt_comment!(parsed_comment) # Saving off the original data parsed_comment['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_comment)) parsed_comment['text'] = parsed_comment.delete(:text) parsed_comment['line'] = parsed_comment.delete(:location)[:line] end # Adapts the AST sub-tree that is rooted at the given step node. def adapt_step!(parsed_step) # Saving off the original data parsed_step['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step)) # Removing parsed data for child elements in order to avoid duplicating data parsed_step['cuke_modeler_parsing_data'][:argument] = nil parsed_step['keyword'] = parsed_step.delete(:keyword) parsed_step['name'] = parsed_step.delete(:text) parsed_step['line'] = parsed_step.delete(:location)[:line] step_argument = parsed_step[:argument] if step_argument case step_argument[:type] when :DocString adapt_doc_string!(step_argument) parsed_step['doc_string'] = parsed_step.delete(:argument) when :DataTable adapt_step_table!(step_argument) parsed_step['table'] = parsed_step.delete(:argument) else raise(ArgumentError, "Unknown step argument type: #{step_argument[:type]}") end end end # Adapts the AST sub-tree that is rooted at the given doc string node. def adapt_doc_string!(parsed_doc_string) # Saving off the original data parsed_doc_string['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_doc_string)) parsed_doc_string['value'] = parsed_doc_string.delete(:content) parsed_doc_string['content_type'] = parsed_doc_string.delete(:contentType) parsed_doc_string['line'] = parsed_doc_string.delete(:location)[:line] end # Adapts the AST sub-tree that is rooted at the given table node. def adapt_step_table!(parsed_step_table) # Saving off the original data parsed_step_table['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_step_table)) # Removing parsed data for child elements in order to avoid duplicating data parsed_step_table['cuke_modeler_parsing_data'][:rows] = nil parsed_step_table['rows'] = [] parsed_step_table[:rows].each do |row| adapt_table_row!(row) end parsed_step_table['rows'].concat(parsed_step_table.delete(:rows)) parsed_step_table['line'] = parsed_step_table.delete(:location)[:line] end # Adapts the AST sub-tree that is rooted at the given row node. def adapt_table_row!(parsed_table_row) # Saving off the original data parsed_table_row['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_table_row)) # Removing parsed data for child elements in order to avoid duplicating data which the child elements will themselves include parsed_table_row['cuke_modeler_parsing_data'][:cells] = nil parsed_table_row['line'] = parsed_table_row.delete(:location)[:line] parsed_table_row['cells'] = [] parsed_table_row[:cells].each do |row| adapt_table_cell!(row) end parsed_table_row['cells'].concat(parsed_table_row.delete(:cells)) end # Adapts the AST sub-tree that is rooted at the given cell node. def adapt_table_cell!(parsed_cell) # Saving off the original data parsed_cell['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_cell)) parsed_cell['value'] = parsed_cell.delete(:value) parsed_cell['line'] = parsed_cell.delete(:location)[:line] end private def adapt_child_elements!(parsed_children) return if parsed_children.empty? if parsed_children.first[:type] == :Background adapt_background!(parsed_children.first) remaining_children = parsed_children[1..-1] end adapt_tests!(remaining_children || parsed_children) end def adapt_tests!(parsed_tests) return unless parsed_tests parsed_tests.each do |test| adapt_test!(test) end end def adapt_test!(parsed_test) # Saving off the original data parsed_test['cuke_modeler_parsing_data'] = Marshal::load(Marshal.dump(parsed_test)) parsed_test['keyword'] = parsed_test.delete(:keyword) parsed_test['type'] = parsed_test.delete(:type).to_s case parsed_test['type'] when 'Scenario' adapt_scenario!(parsed_test) when 'ScenarioOutline' adapt_outline!(parsed_test) else raise(ArgumentError, "Unknown test type: #{parsed_test['type']}") end end end end