module Cucumber module Parser # TIP: When you hack on the grammar, just delete feature.rb in this directory. # Also make sure you have uninstalled all cucumber gems (don't forget xxx-cucumber # github gems). # # Treetop will then generate the parser in-memory. When you're happy, just generate # the rb file with tt feature.tt grammar Feature rule feature_sub white comment white tags white header:(!(scenario_outline / scenario / background) .)* bg:background? feature_elements comment? { def build(filter) if(filter.nil? || feature_elements.accept?(filter) || (!bg.empty? && filter.accept?(bg))) background = bg.respond_to?(:build) ? bg.build : nil Ast::Feature.new( background, comment.build, tags.build, header.text_value, feature_elements.build(background, filter) ) end end } end rule tags white ts:(tag (space/eol)+)* { def at_line?(line) ts.elements.detect{|e| e.tag.line == line} end def build Ast::Tags.new(ts.line, tag_names) end def tag_names @tag_names ||= ts.elements.map{|e| e.tag.text_value} end } end rule tag '@' [^@\r\n\t ]+ end rule comment (comment_line white)* { def build Ast::Comment.new(text_value) end } end rule comment_line space* '#' line_to_eol end rule background comment white background_keyword space* name:lines_to_keyword? (eol+ / eof) steps { def matches_name?(regexp_to_match) name.build =~ regexp_to_match end def at_line?(line) background_keyword.line == line || steps.at_line?(line) end def matches_tags?(tag_names) Ast::Tags.matches?(self.parent.tags.tag_names, tag_names) end def build Ast::Background.new( comment.build, background_keyword.line, background_keyword.text_value, name.build, steps.build ) end } end rule feature_elements (scenario / scenario_outline)* { def accept?(filter) filter.nil? || elements.empty? || elements.detect{|feature_element| filter.accept?(feature_element)} end def build(background, filter) elements.map do |feature_element| if filter.nil? || filter.accept?(feature_element) feature_element.build(background, filter) end end.compact end } end rule scenario comment tags white scenario_keyword space* name:lines_to_keyword white steps white { def at_line?(line) scenario_keyword.line == line || steps.at_line?(line) || tags.at_line?(line) end def matches_tags?(tag_names) feature_tag_names = self.parent.parent.tags.tag_names source_tag_names = (feature_tag_names + tags.tag_names).uniq Ast::Tags.matches?(source_tag_names, tag_names) end def matches_name?(regexp_to_match) name.build =~ regexp_to_match end def build(background, filter) Ast::Scenario.new( background, comment.build, tags.build, scenario_keyword.line, scenario_keyword.text_value, name.build, steps.build ) end } end rule scenario_outline comment tags white scenario_outline_keyword space* name:lines_to_keyword white steps examples_sections white { def at_line?(line) outline_at_line?(line) || examples_sections.at_line?(line) || tags.at_line?(line) end def outline_at_line?(line) scenario_outline_keyword.line == line || steps.at_line?(line) end def matches_tags?(tag_names) feature_tag_names = self.parent.parent.tags.tag_names source_tag_names = (feature_tag_names + tags.tag_names).uniq Ast::Tags.matches?(source_tag_names, tag_names) end def matches_name?(regexp_to_match) outline_matches_name?(regexp_to_match) || examples_sections.matches_name?(regexp_to_match) end def outline_matches_name?(regexp_to_match) name.build =~ regexp_to_match end def build(background, filter) Ast::ScenarioOutline.new( background, comment.build, tags.build, scenario_outline_keyword.line, scenario_outline_keyword.text_value, name.build, steps.build, examples_sections.build(filter, self) ) end } end rule steps step* { def at_line?(line) elements.detect{|e| e.at_line?(line)} end def build elements.map{|e| e.build} end } end rule step comment space* step_keyword keyword_space name:line_to_eol (eol+ / eof) multi:multiline_arg? white { def at_line?(line) step_keyword.line == line || (multi.respond_to?(:at_line?) && multi.at_line?(line)) end def build if multi.respond_to?(:build) Ast::Step.new(step_keyword.line, step_keyword.text_value, name.text_value.strip, multi.build) else Ast::Step.new(step_keyword.line, step_keyword.text_value, name.text_value.strip) end end } end rule examples_sections examples* { def at_line?(line) elements.detect { |e| e.at_line?(line) } end def matches_name?(regexp_to_match) elements.detect { |e| e.matches_name?(regexp_to_match) } end def build(filter, scenario_outline) elements.map do |e| if(filter.nil? || filter.accept_example?(e, scenario_outline)) e.build(filter, scenario_outline) end end.compact end } end rule examples comment space* examples_keyword space* name:lines_to_keyword? eol table white { def at_line?(line) examples_keyword.line == line || table.at_line?(line) end def matches_tags?(tag_names) true end def outline_at_line?(line) true end def matches_name?(regexp_to_match) name.build =~ regexp_to_match end def build(filter, scenario_outline) [comment.build, examples_keyword.line, examples_keyword.text_value, name.build, table.raw(filter, scenario_outline)] end } end rule multiline_arg table / py_string end rule line_to_eol (!eol .)* end rule lines_to_keyword (!(eol space* reserved_words_and_symbols) .)* { def build self.text_value.split("\n").map{|s| s.strip}.join("\n") end } end rule reserved_words_and_symbols (step_keyword keyword_space) / scenario_keyword / scenario_outline_keyword / table / tag / comment_line end rule py_string open_py_string s:(!close_py_string .)* close_py_string { def at_line?(line) line >= open_py_string.line && line <= close_py_string.line end def build Ast::PyString.new(open_py_string.line, close_py_string.line, s.text_value, open_py_string.indentation) end } end rule open_py_string indent:space* '"""' space* eol { def indentation indent.text_value.length end def line indent.line end } end rule close_py_string eol space* quotes:'"""' white { def line quotes.line end } end rule white (space / eol)* end end end end