1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
require 'cucumber/cucumber_expressions/argument'
require 'cucumber/cucumber_expressions/tree_regexp'
require 'cucumber/cucumber_expressions/errors'
module Cucumber
module CucumberExpressions
class CucumberExpression
# Does not include (){} characters because they have special meaning
ESCAPE_REGEXP = /([\\^\[$.|?*+\]])/
PARAMETER_REGEXP = /(\\\\)?{([^}]*)}/
OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/
ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^\/]+)((\/[^\s^\/]+)+)/
DOUBLE_ESCAPE = '\\\\'
PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = 'Parameter types cannot be alternative: '
PARAMETER_TYPES_CANNOT_BE_OPTIONAL = 'Parameter types cannot be optional: '
attr_reader :source
def initialize(expression, parameter_type_registry)
@source = expression
@parameter_types = []
expression = process_escapes(expression)
expression = process_optional(expression)
expression = process_alternation(expression)
expression = process_parameters(expression, parameter_type_registry)
expression = "^#{expression}$"
@tree_regexp = TreeRegexp.new(expression)
end
def match(text)
Argument.build(@tree_regexp, text, @parameter_types)
end
def regexp
@tree_regexp.regexp
end
def to_s
@source.inspect
end
private
def process_escapes(expression)
expression.gsub(ESCAPE_REGEXP, '\\\\\1')
end
def process_optional(expression)
# Create non-capturing, optional capture groups from parenthesis
expression.gsub(OPTIONAL_REGEXP) do
g2 = $2
# When using Parameter Types, the () characters are used to represent an optional
# item such as (a ) which would be equivalent to (?:a )? in regex
#
# You cannot have optional Parameter Types i.e. ({int}) as this causes
# problems during the conversion phase to regex. So we check for that here
#
# One exclusion to this rule is if you actually want the brackets i.e. you
# want to capture (3) then we still permit this as an individual rule
# See: https://github.com/cucumber/cucumber-ruby/issues/1337 for more info
# look for double-escaped parentheses
if $1 == DOUBLE_ESCAPE
"\\(#{g2}\\)"
else
check_no_parameter_type(g2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL)
"(?:#{g2})?"
end
end
end
def process_alternation(expression)
expression.gsub(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP) do
# replace \/ with /
# replace / with |
replacement = $&.tr('/', '|').gsub(/\\\|/, '/')
if replacement.include?('|')
replacement.split(/\|/).each do |part|
check_no_parameter_type(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE)
end
"(?:#{replacement})"
else
replacement
end
end
end
def process_parameters(expression, parameter_type_registry)
# Create non-capturing, optional capture groups from parenthesis
expression.gsub(PARAMETER_REGEXP) do
if ($1 == DOUBLE_ESCAPE)
"\\{#{$2}\\}"
else
type_name = $2
ParameterType.check_parameter_type_name(type_name)
parameter_type = parameter_type_registry.lookup_by_type_name(type_name)
raise UndefinedParameterTypeError.new(type_name) if parameter_type.nil?
@parameter_types.push(parameter_type)
build_capture_regexp(parameter_type.regexps)
end
end
end
def build_capture_regexp(regexps)
return "(#{regexps[0]})" if regexps.size == 1
capture_groups = regexps.map { |group| "(?:#{group})" }
"(#{capture_groups.join('|')})"
end
def check_no_parameter_type(s, message)
if PARAMETER_REGEXP =~ s
raise CucumberExpressionError.new("#{message}#{source}")
end
end
end
end
end
|