# frozen_string_literal: true require 'cucumber/multiline_argument' module Cucumber # Represents the match found between a Test Step and its activation class StepMatch #:nodoc: attr_reader :step_definition, :step_arguments def initialize(step_definition, step_name, step_arguments) raise "step_arguments can't be nil (but it can be an empty array)" if step_arguments.nil? @step_definition = step_definition @name_to_match = step_name @step_arguments = step_arguments end def args current_world = @step_definition.registry.current_world @step_arguments.map do |arg| arg.value(current_world) end end def activate(test_step) test_step.with_action(@step_definition.location) do invoke(MultilineArgument.from_core(test_step.multiline_arg)) end end def invoke(multiline_arg) all_args = deep_clone_args multiline_arg.append_to(all_args) @step_definition.invoke(all_args) end # Formats the matched arguments of the associated Step. This method # is usually called from visitors, which render output. # # The +format+ can either be a String or a Proc. # # If it is a String it should be a format string according to # Kernel#sprinf, for example: # # '%s' # # If it is a Proc, it should take one argument and return the formatted # argument, for example: # # lambda { |param| "[#{param}]" } # def format_args(format = ->(a) { a }, &proc) replace_arguments(@name_to_match, @step_arguments, format, &proc) end def location @step_definition.location end def file_colon_line location.to_s end def backtrace_line "#{file_colon_line}:in `#{@step_definition.expression}'" end def text_length @step_definition.expression.source.to_s.unpack('U*').length end def replace_arguments(string, step_arguments, format) s = string.dup offset = past_offset = 0 step_arguments.each do |step_argument| group = step_argument.group next if group.value.nil? || group.start < past_offset replacement = if block_given? yield(group.value) elsif Proc == format.class format.call(group.value) else format % group.value end s[group.start + offset, group.value.length] = replacement offset += replacement.unpack('U*').length - group.value.unpack('U*').length past_offset = group.start + group.value.length end s end def inspect #:nodoc: "#<#{self.class}: #{location}>" end private def deep_clone_args Marshal.load(Marshal.dump(args)) end end class SkippingStepMatch def activate(test_step) test_step.with_action { raise Core::Test::Result::Skipped } end end class NoStepMatch #:nodoc: attr_reader :step_definition, :name def initialize(step, name) @step = step @name = name end def format_args(*_args) @name end def location raise "No location for #{@step}" unless @step.location @step.location end def file_colon_line location.to_s end def backtrace_line @step.backtrace_line end def text_length @step.text.length end def step_arguments [] end def activate(test_step) # noop test_step end end class AmbiguousStepMatch def initialize(error) @error = error end def activate(test_step) test_step.with_action { raise @error } end end end