cucumber-cucumber-expressions 8.3.1 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/lib/cucumber/cucumber_expressions/argument.rb +8 -4
- data/lib/cucumber/cucumber_expressions/ast.rb +166 -0
- data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +6 -13
- data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +82 -80
- data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +14 -26
- data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +203 -0
- data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +90 -0
- data/lib/cucumber/cucumber_expressions/errors.rb +205 -13
- data/lib/cucumber/cucumber_expressions/expression_factory.rb +24 -0
- data/lib/cucumber/cucumber_expressions/generated_expression.rb +2 -0
- data/lib/cucumber/cucumber_expressions/group.rb +7 -1
- data/lib/cucumber/cucumber_expressions/group_builder.rb +9 -2
- data/lib/cucumber/cucumber_expressions/parameter_type.rb +14 -21
- data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +11 -9
- data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +28 -16
- data/lib/cucumber/cucumber_expressions/regular_expression.rb +3 -2
- data/lib/cucumber/cucumber_expressions/tree_regexp.rb +54 -46
- metadata +76 -77
- data/.github/ISSUE_TEMPLATE.md +0 -5
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -5
- data/.rspec +0 -1
- data/.rsync +0 -4
- data/.subrepo +0 -1
- data/Gemfile +0 -3
- data/Makefile +0 -1
- data/README.md +0 -5
- data/Rakefile +0 -27
- data/cucumber-cucumber-expressions.gemspec +0 -33
- data/default.mk +0 -70
- data/examples.txt +0 -31
- data/scripts/update-gemspec +0 -32
- data/spec/capture_warnings.rb +0 -74
- data/spec/coverage.rb +0 -7
- data/spec/cucumber/cucumber_expressions/argument_spec.rb +0 -17
- data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +0 -43
- data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +0 -231
- data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +0 -57
- data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +0 -212
- data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +0 -202
- data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +0 -30
- data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +0 -86
- data/spec/cucumber/cucumber_expressions/parameter_type_spec.rb +0 -15
- data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +0 -80
- data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +0 -133
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7399365cce63cde9618bcffe3d2b02832df27da08aac3e498697f509cfcc7b9b
|
|
4
|
+
data.tar.gz: 44f194f914c855adc3a670c9d3e0298c1ca9b57878672d056c322ddc19b9f149
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66465f00c20d167435a1d25c708a972b4389f674412c4789e9d6748c9a207f9c598d1b3258a6d9ca8be6177b0d166ea96f916d1e7e68cb1f61e0437df9121136
|
|
7
|
+
data.tar.gz: 1e75e3525394a05944c5e23cf4ea6371a97fa4238a805f518e822799ed11631b4c2bfdcbfe1f3088d8580b9ba87ea0bcdab610ca7cb82d8792364ba9f151b978
|
data/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) Cucumber Ltd
|
|
3
|
+
Copyright (c) 2016 Cucumber Ltd and contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'cucumber/cucumber_expressions/group'
|
|
2
4
|
require 'cucumber/cucumber_expressions/errors'
|
|
3
5
|
|
|
@@ -10,11 +12,12 @@ module Cucumber
|
|
|
10
12
|
group = tree_regexp.match(text)
|
|
11
13
|
return nil if group.nil?
|
|
12
14
|
|
|
13
|
-
arg_groups = group.children
|
|
15
|
+
arg_groups = group.children.nil? ? [] : group.children
|
|
14
16
|
|
|
15
17
|
if arg_groups.length != parameter_types.length
|
|
16
18
|
raise CucumberExpressionError.new(
|
|
17
|
-
|
|
19
|
+
"Expression #{tree_regexp.regexp.inspect} has #{arg_groups.length} capture groups (#{arg_groups.map(&:value)}), " \
|
|
20
|
+
"but there were #{parameter_types.length} parameter types (#{parameter_types.map(&:name)})"
|
|
18
21
|
)
|
|
19
22
|
end
|
|
20
23
|
|
|
@@ -27,8 +30,9 @@ module Cucumber
|
|
|
27
30
|
@group, @parameter_type = group, parameter_type
|
|
28
31
|
end
|
|
29
32
|
|
|
30
|
-
def value(self_obj
|
|
31
|
-
raise
|
|
33
|
+
def value(self_obj = :nil)
|
|
34
|
+
raise 'No self_obj' if self_obj == :nil
|
|
35
|
+
|
|
32
36
|
group_values = @group ? @group.values : nil
|
|
33
37
|
@parameter_type.transform(self_obj, group_values)
|
|
34
38
|
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cucumber
|
|
4
|
+
module CucumberExpressions
|
|
5
|
+
ESCAPE_CHARACTER = '\\'
|
|
6
|
+
ALTERNATION_CHARACTER = '/'
|
|
7
|
+
BEGIN_PARAMETER_CHARACTER = '{'
|
|
8
|
+
END_PARAMETER_CHARACTER = '}'
|
|
9
|
+
BEGIN_OPTIONAL_CHARACTER = '('
|
|
10
|
+
END_OPTIONAL_CHARACTER = ')'
|
|
11
|
+
|
|
12
|
+
class Node
|
|
13
|
+
attr_reader :type, :nodes, :token, :start, :end
|
|
14
|
+
|
|
15
|
+
def initialize(type, nodes, token, start, ending)
|
|
16
|
+
raise 'Either nodes or token must be defined' if nodes.nil? && token.nil?
|
|
17
|
+
|
|
18
|
+
@type = type
|
|
19
|
+
@nodes = nodes
|
|
20
|
+
@token = token
|
|
21
|
+
@start = start
|
|
22
|
+
@end = ending
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def text
|
|
26
|
+
return @nodes.map { |value| value.text }.join('') if @token.nil?
|
|
27
|
+
|
|
28
|
+
@token
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_hash
|
|
32
|
+
hash = Hash.new
|
|
33
|
+
hash['type'] = @type
|
|
34
|
+
hash['nodes'] = @nodes.map { |node| node.to_hash } unless @nodes.nil?
|
|
35
|
+
hash['token'] = @token unless @token.nil?
|
|
36
|
+
hash['start'] = @start
|
|
37
|
+
hash['end'] = @end
|
|
38
|
+
hash
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module NodeType
|
|
43
|
+
TEXT = 'TEXT_NODE'
|
|
44
|
+
OPTIONAL = 'OPTIONAL_NODE'
|
|
45
|
+
ALTERNATION = 'ALTERNATION_NODE'
|
|
46
|
+
ALTERNATIVE = 'ALTERNATIVE_NODE'
|
|
47
|
+
PARAMETER = 'PARAMETER_NODE'
|
|
48
|
+
EXPRESSION = 'EXPRESSION_NODE'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Token
|
|
52
|
+
attr_reader :type, :text, :start, :end
|
|
53
|
+
|
|
54
|
+
def initialize(type, text, start, ending)
|
|
55
|
+
@type, @text, @start, @end = type, text, start, ending
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.is_escape_character(codepoint)
|
|
59
|
+
codepoint.chr(Encoding::UTF_8) == ESCAPE_CHARACTER
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.can_escape(codepoint)
|
|
63
|
+
c = codepoint.chr(Encoding::UTF_8)
|
|
64
|
+
if c == ' '
|
|
65
|
+
# TODO: Unicode whitespace?
|
|
66
|
+
return true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
case c
|
|
70
|
+
when ESCAPE_CHARACTER
|
|
71
|
+
true
|
|
72
|
+
when ALTERNATION_CHARACTER
|
|
73
|
+
true
|
|
74
|
+
when BEGIN_PARAMETER_CHARACTER
|
|
75
|
+
true
|
|
76
|
+
when END_PARAMETER_CHARACTER
|
|
77
|
+
true
|
|
78
|
+
when BEGIN_OPTIONAL_CHARACTER
|
|
79
|
+
true
|
|
80
|
+
when END_OPTIONAL_CHARACTER
|
|
81
|
+
true
|
|
82
|
+
else
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.type_of(codepoint)
|
|
88
|
+
c = codepoint.chr(Encoding::UTF_8)
|
|
89
|
+
if c == ' '
|
|
90
|
+
# TODO: Unicode whitespace?
|
|
91
|
+
return TokenType::WHITE_SPACE
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
case c
|
|
95
|
+
when ALTERNATION_CHARACTER
|
|
96
|
+
TokenType::ALTERNATION
|
|
97
|
+
when BEGIN_PARAMETER_CHARACTER
|
|
98
|
+
TokenType::BEGIN_PARAMETER
|
|
99
|
+
when END_PARAMETER_CHARACTER
|
|
100
|
+
TokenType::END_PARAMETER
|
|
101
|
+
when BEGIN_OPTIONAL_CHARACTER
|
|
102
|
+
TokenType::BEGIN_OPTIONAL
|
|
103
|
+
when END_OPTIONAL_CHARACTER
|
|
104
|
+
TokenType::END_OPTIONAL
|
|
105
|
+
else
|
|
106
|
+
TokenType::TEXT
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.symbol_of(token)
|
|
111
|
+
case token
|
|
112
|
+
when TokenType::BEGIN_OPTIONAL
|
|
113
|
+
return BEGIN_OPTIONAL_CHARACTER
|
|
114
|
+
when TokenType::END_OPTIONAL
|
|
115
|
+
return END_OPTIONAL_CHARACTER
|
|
116
|
+
when TokenType::BEGIN_PARAMETER
|
|
117
|
+
return BEGIN_PARAMETER_CHARACTER
|
|
118
|
+
when TokenType::END_PARAMETER
|
|
119
|
+
return END_PARAMETER_CHARACTER
|
|
120
|
+
when TokenType::ALTERNATION
|
|
121
|
+
return ALTERNATION_CHARACTER
|
|
122
|
+
else
|
|
123
|
+
return ''
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.purpose_of(token)
|
|
128
|
+
case token
|
|
129
|
+
when TokenType::BEGIN_OPTIONAL
|
|
130
|
+
return 'optional text'
|
|
131
|
+
when TokenType::END_OPTIONAL
|
|
132
|
+
return 'optional text'
|
|
133
|
+
when TokenType::BEGIN_PARAMETER
|
|
134
|
+
return 'a parameter'
|
|
135
|
+
when TokenType::END_PARAMETER
|
|
136
|
+
return 'a parameter'
|
|
137
|
+
when TokenType::ALTERNATION
|
|
138
|
+
return 'alternation'
|
|
139
|
+
else
|
|
140
|
+
return ''
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def to_hash
|
|
145
|
+
{
|
|
146
|
+
'type' => @type,
|
|
147
|
+
'text' => @text,
|
|
148
|
+
'start' => @start,
|
|
149
|
+
'end' => @end
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
module TokenType
|
|
155
|
+
START_OF_LINE = 'START_OF_LINE'
|
|
156
|
+
END_OF_LINE = 'END_OF_LINE'
|
|
157
|
+
WHITE_SPACE = 'WHITE_SPACE'
|
|
158
|
+
BEGIN_OPTIONAL = 'BEGIN_OPTIONAL'
|
|
159
|
+
END_OPTIONAL = 'END_OPTIONAL'
|
|
160
|
+
BEGIN_PARAMETER = 'BEGIN_PARAMETER'
|
|
161
|
+
END_PARAMETER = 'END_PARAMETER'
|
|
162
|
+
ALTERNATION = 'ALTERNATION'
|
|
163
|
+
TEXT = 'TEXT'
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require('cucumber/cucumber_expressions/generated_expression')
|
|
2
4
|
|
|
3
5
|
module Cucumber
|
|
4
6
|
module CucumberExpressions
|
|
5
|
-
|
|
6
7
|
class CombinatorialGeneratedExpressionFactory
|
|
7
8
|
def initialize(expression_template, parameter_type_combinations)
|
|
8
9
|
@expression_template = expression_template
|
|
@@ -19,9 +20,7 @@ module Cucumber
|
|
|
19
20
|
MAX_EXPRESSIONS = 256
|
|
20
21
|
|
|
21
22
|
def generate_permutations(generated_expressions, depth, current_parameter_types)
|
|
22
|
-
if generated_expressions.length >= MAX_EXPRESSIONS
|
|
23
|
-
return
|
|
24
|
-
end
|
|
23
|
+
return if generated_expressions.length >= MAX_EXPRESSIONS
|
|
25
24
|
|
|
26
25
|
if depth == @parameter_type_combinations.length
|
|
27
26
|
generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types)
|
|
@@ -31,19 +30,13 @@ module Cucumber
|
|
|
31
30
|
|
|
32
31
|
(0...@parameter_type_combinations[depth].length).each do |i|
|
|
33
32
|
# Avoid recursion if no elements can be added.
|
|
34
|
-
if generated_expressions.length >= MAX_EXPRESSIONS
|
|
35
|
-
|
|
36
|
-
end
|
|
33
|
+
break if generated_expressions.length >= MAX_EXPRESSIONS
|
|
34
|
+
|
|
37
35
|
new_current_parameter_types = current_parameter_types.dup # clone
|
|
38
36
|
new_current_parameter_types.push(@parameter_type_combinations[depth][i])
|
|
39
|
-
generate_permutations(
|
|
40
|
-
generated_expressions,
|
|
41
|
-
depth + 1,
|
|
42
|
-
new_current_parameter_types
|
|
43
|
-
)
|
|
37
|
+
generate_permutations(generated_expressions, depth + 1, new_current_parameter_types)
|
|
44
38
|
end
|
|
45
39
|
end
|
|
46
40
|
end
|
|
47
|
-
|
|
48
41
|
end
|
|
49
42
|
end
|
|
@@ -1,118 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'cucumber/cucumber_expressions/argument'
|
|
2
4
|
require 'cucumber/cucumber_expressions/tree_regexp'
|
|
3
5
|
require 'cucumber/cucumber_expressions/errors'
|
|
6
|
+
require 'cucumber/cucumber_expressions/cucumber_expression_parser'
|
|
4
7
|
|
|
5
8
|
module Cucumber
|
|
6
9
|
module CucumberExpressions
|
|
7
10
|
class CucumberExpression
|
|
8
|
-
|
|
9
|
-
ESCAPE_REGEXP = /([\\^\[$.|?*+\]])/
|
|
10
|
-
PARAMETER_REGEXP = /(\\\\)?{([^}]*)}/
|
|
11
|
-
OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/
|
|
12
|
-
ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^\/]+)((\/[^\s^\/]+)+)/
|
|
13
|
-
DOUBLE_ESCAPE = '\\\\'
|
|
14
|
-
PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE = 'Parameter types cannot be alternative: '
|
|
15
|
-
PARAMETER_TYPES_CANNOT_BE_OPTIONAL = 'Parameter types cannot be optional: '
|
|
16
|
-
|
|
17
|
-
attr_reader :source
|
|
11
|
+
ESCAPE_PATTERN = /([\\^\[({$.|?*+})\]])/.freeze
|
|
18
12
|
|
|
19
13
|
def initialize(expression, parameter_type_registry)
|
|
20
|
-
@
|
|
14
|
+
@expression = expression
|
|
15
|
+
@parameter_type_registry = parameter_type_registry
|
|
21
16
|
@parameter_types = []
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
expression = process_parameters(expression, parameter_type_registry)
|
|
27
|
-
expression = "^#{expression}$"
|
|
28
|
-
|
|
29
|
-
@tree_regexp = TreeRegexp.new(expression)
|
|
17
|
+
parser = CucumberExpressionParser.new
|
|
18
|
+
ast = parser.parse(expression)
|
|
19
|
+
pattern = rewrite_to_regex(ast)
|
|
20
|
+
@tree_regexp = TreeRegexp.new(pattern)
|
|
30
21
|
end
|
|
31
22
|
|
|
32
23
|
def match(text)
|
|
33
24
|
Argument.build(@tree_regexp, text, @parameter_types)
|
|
34
25
|
end
|
|
35
26
|
|
|
27
|
+
def source
|
|
28
|
+
@expression
|
|
29
|
+
end
|
|
30
|
+
|
|
36
31
|
def regexp
|
|
37
32
|
@tree_regexp.regexp
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
def to_s
|
|
41
|
-
|
|
36
|
+
source.inspect
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
private
|
|
45
40
|
|
|
46
|
-
def
|
|
47
|
-
|
|
41
|
+
def rewrite_to_regex(node)
|
|
42
|
+
case node.type
|
|
43
|
+
when NodeType::TEXT
|
|
44
|
+
return escape_regex(node.text)
|
|
45
|
+
when NodeType::OPTIONAL
|
|
46
|
+
return rewrite_optional(node)
|
|
47
|
+
when NodeType::ALTERNATION
|
|
48
|
+
return rewrite_alternation(node)
|
|
49
|
+
when NodeType::ALTERNATIVE
|
|
50
|
+
return rewrite_alternative(node)
|
|
51
|
+
when NodeType::PARAMETER
|
|
52
|
+
return rewrite_parameter(node)
|
|
53
|
+
when NodeType::EXPRESSION
|
|
54
|
+
return rewrite_expression(node)
|
|
55
|
+
else
|
|
56
|
+
# Can't happen as long as the switch case is exhaustive
|
|
57
|
+
raise "#{node.type}"
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
expression.gsub(OPTIONAL_REGEXP) do
|
|
53
|
-
g2 = $2
|
|
54
|
-
# When using Parameter Types, the () characters are used to represent an optional
|
|
55
|
-
# item such as (a ) which would be equivalent to (?:a )? in regex
|
|
56
|
-
#
|
|
57
|
-
# You cannot have optional Parameter Types i.e. ({int}) as this causes
|
|
58
|
-
# problems during the conversion phase to regex. So we check for that here
|
|
59
|
-
#
|
|
60
|
-
# One exclusion to this rule is if you actually want the brackets i.e. you
|
|
61
|
-
# want to capture (3) then we still permit this as an individual rule
|
|
62
|
-
# See: https://github.com/cucumber/cucumber-ruby/issues/1337 for more info
|
|
63
|
-
# look for double-escaped parentheses
|
|
64
|
-
if $1 == DOUBLE_ESCAPE
|
|
65
|
-
"\\(#{g2}\\)"
|
|
66
|
-
else
|
|
67
|
-
check_no_parameter_type(g2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL)
|
|
68
|
-
"(?:#{g2})?"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
61
|
+
def escape_regex(expression)
|
|
62
|
+
expression.gsub(ESCAPE_PATTERN, '\\\\\1')
|
|
71
63
|
end
|
|
72
64
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
replacement.split(/\|/).each do |part|
|
|
80
|
-
check_no_parameter_type(part, PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE)
|
|
81
|
-
end
|
|
82
|
-
"(?:#{replacement})"
|
|
83
|
-
else
|
|
84
|
-
replacement
|
|
85
|
-
end
|
|
86
|
-
end
|
|
65
|
+
def rewrite_optional(node)
|
|
66
|
+
assert_no_parameters(node) { |ast_node| raise ParameterIsNotAllowedInOptional.new(ast_node, @expression) }
|
|
67
|
+
assert_no_optionals(node) { |ast_node| raise OptionalIsNotAllowedInOptional.new(ast_node, @expression) }
|
|
68
|
+
assert_not_empty(node) { |ast_node| raise OptionalMayNotBeEmpty.new(ast_node, @expression) }
|
|
69
|
+
regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('')
|
|
70
|
+
"(?:#{regex})?"
|
|
87
71
|
end
|
|
88
72
|
|
|
89
|
-
def
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
raise UndefinedParameterTypeError.new(type_name) if parameter_type.nil?
|
|
99
|
-
@parameter_types.push(parameter_type)
|
|
100
|
-
|
|
101
|
-
build_capture_regexp(parameter_type.regexps)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
73
|
+
def rewrite_alternation(node)
|
|
74
|
+
# Make sure the alternative parts aren't empty and don't contain parameter types
|
|
75
|
+
node.nodes.each { |alternative|
|
|
76
|
+
raise AlternativeMayNotBeEmpty.new(alternative, @expression) if alternative.nodes.length == 0
|
|
77
|
+
|
|
78
|
+
assert_not_empty(alternative) { |ast_node| raise AlternativeMayNotExclusivelyContainOptionals.new(ast_node, @expression) }
|
|
79
|
+
}
|
|
80
|
+
regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('|')
|
|
81
|
+
"(?:#{regex})"
|
|
104
82
|
end
|
|
105
83
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
capture_groups = regexps.map { |group| "(?:#{group})" }
|
|
109
|
-
"(#{capture_groups.join('|')})"
|
|
84
|
+
def rewrite_alternative(node)
|
|
85
|
+
node.nodes.map { |last_node| rewrite_to_regex(last_node) }.join('')
|
|
110
86
|
end
|
|
111
87
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
def rewrite_parameter(node)
|
|
89
|
+
name = node.text
|
|
90
|
+
parameter_type = @parameter_type_registry.lookup_by_type_name(name)
|
|
91
|
+
raise UndefinedParameterTypeError.new(node, @expression, name) if parameter_type.nil?
|
|
92
|
+
|
|
93
|
+
@parameter_types.push(parameter_type)
|
|
94
|
+
regexps = parameter_type.regexps
|
|
95
|
+
return "(#{regexps[0]})" if regexps.length == 1
|
|
96
|
+
|
|
97
|
+
"((?:#{regexps.join(')|(?:')}))"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def rewrite_expression(node)
|
|
101
|
+
regex = node.nodes.map { |n| rewrite_to_regex(n) }.join('')
|
|
102
|
+
"^#{regex}$"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def assert_not_empty(node, &raise_error)
|
|
106
|
+
text_nodes = node.nodes.select { |ast_node| NodeType::TEXT == ast_node.type }
|
|
107
|
+
raise_error.call(node) if text_nodes.length == 0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def assert_no_parameters(node, &raise_error)
|
|
111
|
+
nodes = node.nodes.select { |ast_node| NodeType::PARAMETER == ast_node.type }
|
|
112
|
+
raise_error.call(nodes[0]) if nodes.length > 0
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def assert_no_optionals(node, &raise_error)
|
|
116
|
+
nodes = node.nodes.select { |ast_node| NodeType::OPTIONAL == ast_node.type }
|
|
117
|
+
raise_error.call(nodes[0]) if nodes.length > 0
|
|
116
118
|
end
|
|
117
119
|
end
|
|
118
120
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'cucumber/cucumber_expressions/parameter_type_matcher'
|
|
2
4
|
require 'cucumber/cucumber_expressions/generated_expression'
|
|
3
5
|
require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'
|
|
@@ -9,23 +11,17 @@ module Cucumber
|
|
|
9
11
|
@parameter_type_registry = parameter_type_registry
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def generate_expression(text)
|
|
13
|
-
generate_expressions(text)[0]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
14
|
def generate_expressions(text)
|
|
17
15
|
parameter_type_combinations = []
|
|
18
16
|
parameter_type_matchers = create_parameter_type_matchers(text)
|
|
19
|
-
expression_template =
|
|
17
|
+
expression_template = +''
|
|
20
18
|
pos = 0
|
|
21
19
|
|
|
22
20
|
loop do
|
|
23
21
|
matching_parameter_type_matchers = []
|
|
24
22
|
parameter_type_matchers.each do |parameter_type_matcher|
|
|
25
23
|
advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos)
|
|
26
|
-
if advanced_parameter_type_matcher.find
|
|
27
|
-
matching_parameter_type_matchers.push(advanced_parameter_type_matcher)
|
|
28
|
-
end
|
|
24
|
+
matching_parameter_type_matchers.push(advanced_parameter_type_matcher) if advanced_parameter_type_matcher.find
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
if matching_parameter_type_matchers.any?
|
|
@@ -43,25 +39,21 @@ module Cucumber
|
|
|
43
39
|
# Users are most likely to want these, so they should be listed at the top.
|
|
44
40
|
parameter_types = []
|
|
45
41
|
best_parameter_type_matchers.each do |parameter_type_matcher|
|
|
46
|
-
unless parameter_types.include?(parameter_type_matcher.parameter_type)
|
|
47
|
-
parameter_types.push(parameter_type_matcher.parameter_type)
|
|
48
|
-
end
|
|
42
|
+
parameter_types.push(parameter_type_matcher.parameter_type) unless parameter_types.include?(parameter_type_matcher.parameter_type)
|
|
49
43
|
end
|
|
50
44
|
parameter_types.sort!
|
|
51
45
|
|
|
52
46
|
parameter_type_combinations.push(parameter_types)
|
|
53
47
|
|
|
54
48
|
expression_template += escape(text.slice(pos...best_parameter_type_matcher.start))
|
|
55
|
-
expression_template +=
|
|
49
|
+
expression_template += '{%s}'
|
|
56
50
|
|
|
57
51
|
pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length
|
|
58
52
|
else
|
|
59
53
|
break
|
|
60
54
|
end
|
|
61
55
|
|
|
62
|
-
if pos >= text.length
|
|
63
|
-
break
|
|
64
|
-
end
|
|
56
|
+
break if pos >= text.length
|
|
65
57
|
end
|
|
66
58
|
|
|
67
59
|
expression_template += escape(text.slice(pos..-1))
|
|
@@ -72,33 +64,29 @@ module Cucumber
|
|
|
72
64
|
).generate_expressions
|
|
73
65
|
end
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
private
|
|
76
68
|
|
|
77
69
|
def create_parameter_type_matchers(text)
|
|
78
70
|
parameter_matchers = []
|
|
79
71
|
@parameter_type_registry.parameter_types.each do |parameter_type|
|
|
80
|
-
if parameter_type.use_for_snippets
|
|
81
|
-
parameter_matchers += create_parameter_type_matchers2(parameter_type, text)
|
|
82
|
-
end
|
|
72
|
+
parameter_matchers += create_parameter_type_matchers2(parameter_type, text) if parameter_type.use_for_snippets
|
|
83
73
|
end
|
|
84
74
|
parameter_matchers
|
|
85
75
|
end
|
|
86
76
|
|
|
87
77
|
def create_parameter_type_matchers2(parameter_type, text)
|
|
88
|
-
result = []
|
|
89
78
|
regexps = parameter_type.regexps
|
|
90
|
-
regexps.
|
|
79
|
+
regexps.map do |regexp|
|
|
91
80
|
regexp = Regexp.new("(#{regexp})")
|
|
92
|
-
|
|
81
|
+
ParameterTypeMatcher.new(parameter_type, regexp, text, 0)
|
|
93
82
|
end
|
|
94
|
-
result
|
|
95
83
|
end
|
|
96
84
|
|
|
97
85
|
def escape(s)
|
|
98
86
|
s.gsub(/%/, '%%')
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
87
|
+
.gsub(/\(/, '\\(')
|
|
88
|
+
.gsub(/{/, '\\{')
|
|
89
|
+
.gsub(/\//, '\\/')
|
|
102
90
|
end
|
|
103
91
|
end
|
|
104
92
|
end
|