require 'rubygems' gem 'ruby_parser' # >= 2.11.8 because of https://github.com/cucumber/gherkin/commit/90121f513000b89fce4d24c4e7dfdae00a5b177f gem 'gherkin', '>= 2.11.8', '< 4.0.0' require 'ruby_parser' require 'yaml' require 'gherkin' require 'gherkin/formatter/json_formatter' class StepExtractor < Gherkin::Formatter::JSONFormatter def initialize super(StringIO.new) end # returns a Hash describing step at the line or nil if nothing # executable found def step_at(line) @feature_hashes[0]['elements'].each do |element| element['steps'].each do |step| if step['line'] == line if element['examples'] rows = element['examples'][0]['rows'].each header = rows.next['cells'] examples = [] loop do row = rows.next['cells'] example = {} row.each_with_index do |val, idx| example[header[idx]] = val end examples.push(example) end step['examples'] = examples end return step end end end nil end end class Step attr_reader :file, :line, :regexp def initialize(regexp, file, line) @file, @line = file, line self.regexp = regexp end def regexp=(value) @regexp = case value when String pieces, regexp = [], value.dup regexp.gsub!(/\$\w+/) { |match| pieces << match; "TOKEN" } regexp.gsub!(/(?=\{)(.*?)(\})/, "TOKEN") regexp = Regexp.escape(regexp) regexp.gsub!(/TOKEN/) { |match| "(.*)" } Regexp.new("^#{regexp}$") when Regexp value else STDERR.puts "Warning: invalid parameter to Given/When/Then on #{file}:#{line}. Expected Regexp or String, got #{value.class} #{value.inspect}" Regexp.new(/^INVALID PARAM$/) end end def match?(text) @regexp.match(text) end end class StepParser attr_accessor :steps, :file def initialize(file, keywords) @file = file @steps = [] @keywords = keywords extract_steps(RubyParser.new.parse(File.read(file))) end def extract_steps(sexp) return unless sexp.is_a?(Sexp) case sexp.first when :block sexp[1..-1].each do |child_sexp| extract_steps(child_sexp) end when :iter child_sexp = sexp[1] return unless child_sexp[0] == :call && @keywords.include?(child_sexp[2]) regexp = child_sexp[3][1] @steps << Step.new(regexp, file, child_sexp.line) else sexp.each do |child| extract_steps(child) end end end end iso_code, feature_path, line, step_search_path, step_search_path_extra, = ARGV extractor = StepExtractor.new parser = Gherkin::Parser::Parser.new(extractor, true, 'root', false, iso_code) parser.parse(IO.read(feature_path), feature_path, 0) step_info = extractor.step_at(line.to_i) inputs = if step_info['examples'] # If the step has example placeholders, we should substitute them # and provide user a choice placeholders = step_info['examples'].first.keys.map { |key| %r(<(#{key})>) } step_info['examples'].map do |example| step_info['name'].gsub(Regexp.union(placeholders)) do |p| example[p.gsub(/[<>]/, '')] end end.uniq else [step_info['name']] end quick_exit = inputs.size == 1 def results_to_alist(results) results.uniq! { |(_, file, line)| [file, line]} pairs = results.reduce("") do |acc, (input, file, line)| acc << sprintf("(%s . %s)", input.inspect, [file, line].join(":").inspect) end "(#{pairs})" end results = [] keywords = Gherkin::I18n.get(iso_code).step_keywords[1..-1].map { |k| k.strip.to_sym } files = Dir[step_search_path] files.push(*Dir[step_search_path_extra]) if !step_search_path_extra.nil? files.each_with_index do |file, i| StepParser.new(file, keywords).steps.each do |step| inputs.each do |input_text| if step.match?(input_text) results.push([input_text, step.file, step.line]) if quick_exit print results_to_alist(results) exit end end end end end print results_to_alist(results)