cucumber-cucumber-expressions 10.2.0 → 11.0.1

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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rsync +1 -0
  3. data/VERSION +1 -1
  4. data/cucumber-cucumber-expressions.gemspec +2 -2
  5. data/default.mk +12 -0
  6. data/examples.txt +13 -1
  7. data/lib/cucumber/cucumber_expressions/ast.rb +201 -0
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +80 -76
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +4 -8
  10. data/lib/cucumber/cucumber_expressions/cucumber_expression_parser.rb +219 -0
  11. data/lib/cucumber/cucumber_expressions/cucumber_expression_tokenizer.rb +95 -0
  12. data/lib/cucumber/cucumber_expressions/errors.rb +175 -4
  13. data/lib/cucumber/cucumber_expressions/group.rb +1 -1
  14. data/lib/cucumber/cucumber_expressions/parameter_type.rb +12 -7
  15. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +2 -2
  16. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +1 -1
  17. data/lib/cucumber/cucumber_expressions/tree_regexp.rb +53 -46
  18. data/spec/cucumber/cucumber_expressions/cucumber_expression_parser_spec.rb +24 -0
  19. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +73 -124
  20. data/spec/cucumber/cucumber_expressions/cucumber_expression_tokenizer_spec.rb +24 -0
  21. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +1 -1
  22. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +1 -1
  23. data/spec/cucumber/cucumber_expressions/expression_factory_spec.rb +0 -4
  24. data/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb +58 -5
  25. data/testdata/ast/alternation-followed-by-optional.yaml +17 -0
  26. data/testdata/ast/alternation-phrase.yaml +16 -0
  27. data/testdata/ast/alternation-with-parameter.yaml +27 -0
  28. data/testdata/ast/alternation-with-unused-end-optional.yaml +15 -0
  29. data/testdata/ast/alternation-with-unused-start-optional.yaml +8 -0
  30. data/testdata/ast/alternation-with-white-space.yaml +12 -0
  31. data/testdata/ast/alternation.yaml +12 -0
  32. data/testdata/ast/anonymous-parameter.yaml +5 -0
  33. data/testdata/ast/closing-brace.yaml +5 -0
  34. data/testdata/ast/closing-parenthesis.yaml +5 -0
  35. data/testdata/ast/empty-alternation.yaml +8 -0
  36. data/testdata/ast/empty-alternations.yaml +9 -0
  37. data/testdata/ast/empty-string.yaml +3 -0
  38. data/testdata/ast/escaped-alternation.yaml +5 -0
  39. data/testdata/ast/escaped-backslash.yaml +5 -0
  40. data/testdata/ast/escaped-opening-parenthesis.yaml +5 -0
  41. data/testdata/ast/escaped-optional-followed-by-optional.yaml +15 -0
  42. data/testdata/ast/escaped-optional-phrase.yaml +10 -0
  43. data/testdata/ast/escaped-optional.yaml +7 -0
  44. data/testdata/ast/opening-brace.yaml +8 -0
  45. data/testdata/ast/opening-parenthesis.yaml +8 -0
  46. data/testdata/ast/optional-containing-nested-optional.yaml +15 -0
  47. data/testdata/ast/optional-phrase.yaml +12 -0
  48. data/testdata/ast/optional.yaml +7 -0
  49. data/testdata/ast/parameter.yaml +7 -0
  50. data/testdata/ast/phrase.yaml +9 -0
  51. data/testdata/ast/unfinished-parameter.yaml +8 -0
  52. data/testdata/expression/allows-escaped-optional-parameter-types.yaml +4 -0
  53. data/testdata/expression/allows-parameter-type-in-alternation-1.yaml +4 -0
  54. data/testdata/expression/allows-parameter-type-in-alternation-2.yaml +4 -0
  55. data/testdata/expression/does-allow-parameter-adjacent-to-alternation.yaml +5 -0
  56. data/testdata/expression/does-not-allow-alternation-in-optional.yaml +9 -0
  57. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-left-parameter.yaml +10 -0
  58. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-optional.yaml +9 -0
  59. data/testdata/expression/does-not-allow-alternation-with-empty-alternative-by-adjacent-right-parameter.yaml +9 -0
  60. data/testdata/expression/does-not-allow-alternation-with-empty-alternative.yaml +9 -0
  61. data/testdata/expression/does-not-allow-empty-optional.yaml +9 -0
  62. data/testdata/expression/does-not-allow-nested-optional.yaml +8 -0
  63. data/testdata/expression/does-not-allow-optional-parameter-types.yaml +9 -0
  64. data/testdata/expression/does-not-allow-parameter-name-with-reserved-characters.yaml +10 -0
  65. data/testdata/expression/does-not-allow-unfinished-parenthesis-1.yaml +8 -0
  66. data/testdata/expression/does-not-allow-unfinished-parenthesis-2.yaml +8 -0
  67. data/testdata/expression/does-not-allow-unfinished-parenthesis-3.yaml +8 -0
  68. data/testdata/expression/does-not-match-misquoted-string.yaml +4 -0
  69. data/testdata/expression/doesnt-match-float-as-int.yaml +5 -0
  70. data/testdata/expression/matches-alternation.yaml +4 -0
  71. data/testdata/expression/matches-anonymous-parameter-type.yaml +5 -0
  72. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  73. data/testdata/expression/matches-double-quoted-empty-string-as-empty-string.yaml +4 -0
  74. data/testdata/expression/matches-double-quoted-string-with-escaped-double-quote.yaml +4 -0
  75. data/testdata/expression/matches-double-quoted-string-with-single-quotes.yaml +4 -0
  76. data/testdata/expression/matches-double-quoted-string.yaml +4 -0
  77. data/testdata/expression/matches-doubly-escaped-parenthesis.yaml +4 -0
  78. data/testdata/expression/matches-doubly-escaped-slash-1.yaml +4 -0
  79. data/testdata/expression/matches-doubly-escaped-slash-2.yaml +4 -0
  80. data/testdata/expression/matches-escaped-parenthesis-1.yaml +4 -0
  81. data/testdata/expression/matches-escaped-parenthesis-2.yaml +4 -0
  82. data/testdata/expression/matches-escaped-parenthesis-3.yaml +4 -0
  83. data/testdata/expression/matches-escaped-slash.yaml +4 -0
  84. data/testdata/expression/matches-float-1.yaml +5 -0
  85. data/testdata/expression/matches-float-2.yaml +5 -0
  86. data/testdata/expression/matches-int.yaml +5 -0
  87. data/testdata/expression/matches-multiple-double-quoted-strings.yaml +4 -0
  88. data/testdata/expression/matches-multiple-single-quoted-strings.yaml +4 -0
  89. data/testdata/expression/matches-optional-before-alternation-1.yaml +4 -0
  90. data/testdata/expression/matches-optional-before-alternation-2.yaml +4 -0
  91. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-1.yaml +4 -0
  92. data/testdata/expression/matches-optional-before-alternation-with-regex-characters-2.yaml +4 -0
  93. data/testdata/expression/matches-optional-in-alternation-1.yaml +5 -0
  94. data/testdata/expression/matches-optional-in-alternation-2.yaml +5 -0
  95. data/testdata/expression/matches-optional-in-alternation-3.yaml +5 -0
  96. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string-along-with-other-strings.yaml +4 -0
  97. data/testdata/expression/matches-single-quoted-empty-string-as-empty-string.yaml +4 -0
  98. data/testdata/expression/matches-single-quoted-string-with-double-quotes.yaml +4 -0
  99. data/testdata/expression/matches-single-quoted-string-with-escaped-single-quote.yaml +4 -0
  100. data/testdata/expression/matches-single-quoted-string.yaml +4 -0
  101. data/testdata/expression/matches-word.yaml +4 -0
  102. data/testdata/expression/throws-unknown-parameter-type.yaml +10 -0
  103. data/testdata/regex/alternation-with-optional.yaml +2 -0
  104. data/testdata/regex/alternation.yaml +2 -0
  105. data/testdata/regex/empty.yaml +2 -0
  106. data/testdata/regex/escape-regex-characters.yaml +2 -0
  107. data/testdata/regex/optional.yaml +2 -0
  108. data/testdata/regex/parameter.yaml +2 -0
  109. data/testdata/regex/text.yaml +2 -0
  110. data/testdata/regex/unicode.yaml +2 -0
  111. data/testdata/tokens/alternation-phrase.yaml +13 -0
  112. data/testdata/tokens/alternation.yaml +9 -0
  113. data/testdata/tokens/empty-string.yaml +6 -0
  114. data/testdata/tokens/escape-non-reserved-character.yaml +8 -0
  115. data/testdata/tokens/escaped-alternation.yaml +9 -0
  116. data/testdata/tokens/escaped-char-has-start-index-of-text-token.yaml +9 -0
  117. data/testdata/tokens/escaped-end-of-line.yaml +8 -0
  118. data/testdata/tokens/escaped-optional.yaml +7 -0
  119. data/testdata/tokens/escaped-parameter.yaml +7 -0
  120. data/testdata/tokens/escaped-space.yaml +7 -0
  121. data/testdata/tokens/optional-phrase.yaml +13 -0
  122. data/testdata/tokens/optional.yaml +9 -0
  123. data/testdata/tokens/parameter-phrase.yaml +13 -0
  124. data/testdata/tokens/parameter.yaml +9 -0
  125. data/testdata/tokens/phrase.yaml +11 -0
  126. metadata +118 -13
  127. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +0 -57
@@ -11,7 +11,7 @@ module Cucumber
11
11
  end
12
12
 
13
13
  def values
14
- (children.empty? ? [self] : children).map(&:value).compact
14
+ (children.empty? ? [self] : children).map(&:value)
15
15
  end
16
16
  end
17
17
  end
@@ -9,12 +9,16 @@ module Cucumber
9
9
  attr_reader :name, :type, :regexps
10
10
 
11
11
  def self.check_parameter_type_name(type_name)
12
+ unless is_valid_parameter_type_name(type_name)
13
+ raise CucumberExpressionError.new("Illegal character in parameter name {#{type_name}}. Parameter names may not contain '[]()$.|?*+'")
14
+ end
15
+ end
16
+
17
+ def self.is_valid_parameter_type_name(type_name)
12
18
  unescaped_type_name = type_name.gsub(UNESCAPE_PATTERN) do
13
19
  $2
14
20
  end
15
- if ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name
16
- raise CucumberExpressionError.new("Illegal character '#{$1}' in parameter name {#{unescaped_type_name}}")
17
- end
21
+ !(ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name)
18
22
  end
19
23
 
20
24
  def prefer_for_regexp_match?
@@ -58,16 +62,17 @@ module Cucumber
58
62
 
59
63
  private
60
64
 
65
+
61
66
  def string_array(regexps)
62
67
  array = regexps.is_a?(Array) ? regexps : [regexps]
63
- array.map {|regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp)}
68
+ array.map { |regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp) }
64
69
  end
65
70
 
66
71
  def regexp_source(regexp)
67
72
  [
68
- 'EXTENDED',
69
- 'IGNORECASE',
70
- 'MULTILINE'
73
+ 'EXTENDED',
74
+ 'IGNORECASE',
75
+ 'MULTILINE'
71
76
  ].each do |option_name|
72
77
  option = Regexp.const_get(option_name)
73
78
  if regexp.options & option != 0
@@ -47,12 +47,12 @@ module Cucumber
47
47
 
48
48
  def space_before_match_or_sentence_start?
49
49
  match_begin = @match.begin(0)
50
- match_begin == 0 || @text[match_begin - 1].match(/\s|\p{P}/)
50
+ match_begin == 0 || @text[match_begin - 1].match(/\p{Z}|\p{P}|\p{S}/)
51
51
  end
52
52
 
53
53
  def space_after_match_or_sentence_end?
54
54
  match_end = @match.end(0)
55
- match_end == @text.length || @text[match_end].match(/\s|\p{P}/)
55
+ match_end == @text.length || @text[match_end].match(/\p{Z}|\p{P}|\p{S}/)
56
56
  end
57
57
  end
58
58
  end
@@ -18,7 +18,7 @@ module Cucumber
18
18
  define_parameter_type(ParameterType.new('int', INTEGER_REGEXPS, Integer, lambda {|s = nil| s && s.to_i}, true, true))
19
19
  define_parameter_type(ParameterType.new('float', FLOAT_REGEXP, Float, lambda {|s = nil| s && s.to_f}, true, false))
20
20
  define_parameter_type(ParameterType.new('word', WORD_REGEXP, String, lambda {|s = nil| s}, false, false))
21
- define_parameter_type(ParameterType.new('string', STRING_REGEXP, String, lambda {|s = nil| s && s.gsub(/\\"/, '"').gsub(/\\'/, "'")}, true, false))
21
+ define_parameter_type(ParameterType.new('string', STRING_REGEXP, String, lambda { |s1, s2| arg = s1 != nil ? s1 : s2; arg.gsub(/\\"/, '"').gsub(/\\'/, "'")}, true, false))
22
22
  define_parameter_type(ParameterType.new('', ANONYMOUS_REGEXP, String, lambda {|s = nil| s}, false, true))
23
23
  end
24
24
 
@@ -8,68 +8,75 @@ module Cucumber
8
8
 
9
9
  def initialize(regexp)
10
10
  @regexp = regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp)
11
- @stack = [GroupBuilder.new]
11
+ @group_builder = create_group_builder(@regexp)
12
+ end
13
+
14
+ def match(s)
15
+ match = @regexp.match(s)
16
+ return nil if match.nil?
17
+ group_indices = (0..match.length).to_a.to_enum
18
+ @group_builder.build(match, group_indices)
19
+ end
20
+
21
+ private def is_non_capturing(source, i)
22
+ # Regex is valid. Bounds check not required.
23
+ if source[i+1] != '?'
24
+ # (X)
25
+ return false
26
+ end
27
+
28
+ if source[i+2] != '<'
29
+ # (?:X)
30
+ # (?idmsuxU-idmsuxU)
31
+ # (?idmsux-idmsux:X)
32
+ # (?=X)
33
+ # (?!X)
34
+ # (?>X)
35
+ return true
36
+ end
37
+
38
+ if source[i+3] == '=' || source[i+3] == '!'
39
+ # (?<=X)
40
+ # (?<!X)
41
+ return true
42
+ end
43
+
44
+ # (?<name>X)
45
+ raise CucumberExpressionError.new("Named capture groups are not supported. See https://github.com/cucumber/cucumber/issues/329")
46
+ end
47
+
48
+ private def create_group_builder(regexp)
49
+ source = regexp.source
50
+ stack = [GroupBuilder.new]
12
51
  group_start_stack = []
13
- last = nil
14
52
  escaping = false
15
- @non_capturing_maybe = false
16
- @name_capturing_maybe = false
17
53
  char_class = false
18
-
19
- @regexp.source.each_char.with_index do |c, n|
54
+ source.each_char.with_index do |c, i|
20
55
  if c == '[' && !escaping
21
56
  char_class = true
22
57
  elsif c == ']' && !escaping
23
58
  char_class = false
24
59
  elsif c == '(' && !escaping && !char_class
25
- @stack.push(GroupBuilder.new)
26
- group_start_stack.push(n + 1)
27
- @non_capturing_maybe = false
60
+ group_start_stack.push(i)
61
+ group_builder = GroupBuilder.new
62
+ non_capturing = is_non_capturing(source, i)
63
+ if non_capturing
64
+ group_builder.set_non_capturing!
65
+ end
66
+ stack.push(group_builder)
28
67
  elsif c == ')' && !escaping && !char_class
29
- gb = @stack.pop
68
+ gb = stack.pop
30
69
  group_start = group_start_stack.pop
31
70
  if gb.capturing?
32
- gb.source = @regexp.source[group_start...n]
33
- @stack.last.add(gb)
71
+ gb.source = source[group_start + 1...i]
72
+ stack.last.add(gb)
34
73
  else
35
- gb.move_children_to(@stack.last)
74
+ gb.move_children_to(stack.last)
36
75
  end
37
- end_group
38
- elsif c == '?' && last == '('
39
- @non_capturing_maybe = true
40
- elsif (c == '<') && @non_capturing_maybe
41
- @name_capturing_maybe = true
42
- elsif (c == ':' || c == '!' || c == '=') && last == '?' && @non_capturing_maybe
43
- end_non_capturing_group
44
- elsif (c == '=' || c == '!') && last == '<' && @name_capturing_maybe
45
- end_non_capturing_group
46
- elsif @name_capturing_maybe
47
- raise CucumberExpressionError.new("Named capture groups are not supported. See https://github.com/cucumber/cucumber/issues/329")
48
76
  end
49
-
50
77
  escaping = c == '\\' && !escaping
51
- last = c
52
78
  end
53
- @group_builder = @stack.pop
54
- end
55
-
56
- def match(s)
57
- match = @regexp.match(s)
58
- return nil if match.nil?
59
- group_indices = (0..match.length).to_a.to_enum
60
- @group_builder.build(match, group_indices)
61
- end
62
-
63
- private
64
-
65
- def end_non_capturing_group
66
- @stack.last.set_non_capturing!
67
- end_group
68
- end
69
-
70
- def end_group
71
- @non_capturing_maybe = false
72
- @name_capturing_maybe = false
79
+ stack.pop
73
80
  end
74
81
  end
75
82
  end
@@ -0,0 +1,24 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'cucumber/cucumber_expressions/cucumber_expression_parser'
4
+ require 'cucumber/cucumber_expressions/errors'
5
+
6
+ module Cucumber
7
+ module CucumberExpressions
8
+ describe 'Cucumber expression parser' do
9
+ Dir['testdata/ast/*.yaml'].each do |testcase|
10
+ expectation = YAML.load_file(testcase) # encoding?
11
+ it "#{testcase}" do
12
+ parser = CucumberExpressionParser.new
13
+ if expectation['exception'].nil?
14
+ node = parser.parse(expectation['expression'])
15
+ node_hash = node.to_hash
16
+ expect(node_hash).to eq(JSON.parse(expectation['expected']))
17
+ else
18
+ expect { parser.parse(expectation['expression']) }.to raise_error(expectation['exception'])
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,9 +1,39 @@
1
+ require 'yaml'
2
+ require 'json'
1
3
  require 'cucumber/cucumber_expressions/cucumber_expression'
2
4
  require 'cucumber/cucumber_expressions/parameter_type_registry'
3
5
 
4
6
  module Cucumber
5
7
  module CucumberExpressions
6
8
  describe CucumberExpression do
9
+
10
+ Dir['testdata/expression/*.yaml'].each do |testcase|
11
+ expectation = YAML.load_file(testcase) # encoding?
12
+ it "#{testcase}" do
13
+ parameter_registry = ParameterTypeRegistry.new
14
+ if expectation['exception'].nil?
15
+ cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry)
16
+ matches = cucumber_expression.match(expectation['text'])
17
+ values = matches.nil? ? nil : matches.map { |arg| arg.value(nil) }
18
+ expect(values).to eq(JSON.parse(expectation['expected']))
19
+ else
20
+ expect {
21
+ cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry)
22
+ cucumber_expression.match(expectation['text'])
23
+ }.to raise_error(expectation['exception'])
24
+ end
25
+ end
26
+ end
27
+
28
+ Dir['testdata/regex/*.yaml'].each do |testcase|
29
+ expectation = YAML.load_file(testcase) # encoding?
30
+ it "#{testcase}" do
31
+ parameter_registry = ParameterTypeRegistry.new
32
+ cucumber_expression = CucumberExpression.new(expectation['expression'], parameter_registry)
33
+ expect(cucumber_expression.regexp.source).to eq(expectation['expected'])
34
+ end
35
+ end
36
+
7
37
  it "documents match arguments" do
8
38
  parameter_registry = ParameterTypeRegistry.new
9
39
 
@@ -15,82 +45,6 @@ module Cucumber
15
45
  ### [capture-match-arguments]
16
46
  end
17
47
 
18
- it "matches word" do
19
- expect(match("three {word} mice", "three blind mice")).to eq(['blind'])
20
- end
21
-
22
- it('matches double quoted string') do
23
- expect(match('three {string} mice', 'three "blind" mice')).to eq(['blind'])
24
- end
25
-
26
- it('matches multiple double quoted strings') do
27
- expect(match('three {string} and {string} mice', 'three "blind" and "crippled" mice')).to eq(['blind', 'crippled'])
28
- end
29
-
30
- it('matches single quoted string') do
31
- expect(match('three {string} mice', "three 'blind' mice")).to eq(['blind'])
32
- end
33
-
34
- it('matches multiple single quoted strings') do
35
- expect(match('three {string} and {string} mice', "three 'blind' and 'crippled' mice")).to eq(['blind', 'crippled'])
36
- end
37
-
38
- it('does not match misquoted string') do
39
- expect(match('three {string} mice', 'three "blind\' mice')).to eq(nil)
40
- end
41
-
42
- it('matches single quoted string with double quotes') do
43
- expect(match('three {string} mice', 'three \'"blind"\' mice')).to eq(['"blind"'])
44
- end
45
-
46
- it('matches double quoted string with single quotes') do
47
- expect(match('three {string} mice', 'three "\'blind\'" mice')).to eq(["'blind'"])
48
- end
49
-
50
- it('matches double quoted string with escaped double quote') do
51
- expect(match('three {string} mice', 'three "bl\\"nd" mice')).to eq(['bl"nd'])
52
- end
53
-
54
- it('matches single quoted string with escaped single quote') do
55
- expect(match('three {string} mice', "three 'bl\\'nd' mice")).to eq(["bl'nd"])
56
- end
57
-
58
- it('matches single quoted empty string as empty string') do
59
- expect(match('three {string} mice', "three '' mice")).to eq([''])
60
- end
61
-
62
- it('matches double quoted empty string as empty string') do
63
- expect(match('three {string} mice', 'three "" mice')).to eq([''])
64
- end
65
-
66
- it('matches single quoted empty string as empty string, along with other strings') do
67
- expect(match('three {string} and {string} mice', "three '' and 'handsome' mice")).to eq(['', 'handsome'])
68
- end
69
-
70
- it('matches double quoted empty string as empty string, along with other strings') do
71
- expect(match('three {string} and {string} mice', 'three "" and "handsome" mice')).to eq(['', 'handsome'])
72
- end
73
-
74
- it 'matches escaped parentheses' do
75
- expect(match('three \\(exceptionally) {string} mice', 'three (exceptionally) "blind" mice')).to eq(['blind'])
76
- end
77
-
78
- it "matches escaped slash" do
79
- expect(match("12\\/2020", "12/2020")).to eq([])
80
- end
81
-
82
- it "matches int" do
83
- expect(match("{int}", "22")).to eq([22])
84
- end
85
-
86
- it "doesn't match float as int" do
87
- expect(match("{int}", "1.22")).to be_nil
88
- end
89
-
90
- it "matches int as float" do
91
- expect(match("{float}", "0")).to eq([0.0])
92
- end
93
-
94
48
  it "matches float" do
95
49
  expect(match("{float}", "")).to eq(nil)
96
50
  expect(match("{float}", ".")).to eq(nil)
@@ -126,58 +80,64 @@ module Cucumber
126
80
  expect(match("{float}", "-.1E2")).to eq([-10])
127
81
  end
128
82
 
129
- it "matches anonymous" do
130
- expect(match("{}", "0.22")).to eq(["0.22"])
131
- end
132
-
133
- '[]()$.|?*+'.split('').each do |char|
134
- it "does not allow parameter type with #{char}" do
135
- expect {match("{#{char}string}", "something")}.to raise_error("Illegal character '#{char}' in parameter name {#{char}string}")
136
- end
83
+ it "float with zero" do
84
+ expect(match("{float}", "0")).to eq([0.0])
137
85
  end
138
86
 
139
- it "throws unknown parameter type" do
140
- expect {match("{unknown}", "something")}.to raise_error('Undefined parameter type {unknown}')
87
+ it "matches anonymous" do
88
+ expect(match("{}", "0.22")).to eq(["0.22"])
141
89
  end
142
90
 
143
- it "does not allow optional parameter types" do
144
- expect {match("({int})", "3")}.to raise_error('Parameter types cannot be optional: ({int})')
91
+ it "exposes source" do
92
+ expr = "I have {int} cuke(s)"
93
+ expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr)
145
94
  end
146
95
 
147
- it "does allow escaped optional parameter types" do
148
- expect(match("\\({int})", "(3)")).to eq([3])
149
- end
96
+ it "unmatched optional groups have undefined values" do
97
+ parameter_type_registry = ParameterTypeRegistry.new
98
+ parameter_type_registry.define_parameter_type(
99
+ ParameterType.new(
100
+ 'textAndOrNumber',
101
+ /([A-Z]+)?(?: )?([0-9]+)?/,
102
+ Object,
103
+ -> (s1, s2) {
104
+ [s1, s2]
105
+ },
106
+ false,
107
+ true
108
+ )
109
+ )
110
+ expression = CucumberExpression.new(
111
+ '{textAndOrNumber}',
112
+ parameter_type_registry
113
+ )
150
114
 
151
- it "does not allow text/parameter type alternation" do
152
- expect {match("x/{int}", "3")}.to raise_error('Parameter types cannot be alternative: x/{int}')
153
- end
115
+ class World
116
+ end
154
117
 
155
- it "does not allow parameter type/text alternation" do
156
- expect {match("{int}/x", "3")}.to raise_error('Parameter types cannot be alternative: {int}/x')
118
+ expect(expression.match("TLA")[0].value(World.new)).to eq(["TLA", nil])
119
+ expect(expression.match("123")[0].value(World.new)).to eq([nil, "123"])
157
120
  end
158
121
 
159
- it "exposes source" do
160
- expr = "I have {int} cuke(s)"
161
- expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr)
162
- end
122
+ # Ruby specific
163
123
 
164
124
  it "delegates transform to custom object" do
165
125
  parameter_type_registry = ParameterTypeRegistry.new
166
126
  parameter_type_registry.define_parameter_type(
167
- ParameterType.new(
168
- 'widget',
169
- /\w+/,
170
- Object,
171
- -> (s) {
172
- self.create_widget(s)
173
- },
174
- false,
175
- true
176
- )
127
+ ParameterType.new(
128
+ 'widget',
129
+ /\w+/,
130
+ Object,
131
+ -> (s) {
132
+ self.create_widget(s)
133
+ },
134
+ false,
135
+ true
136
+ )
177
137
  )
178
138
  expression = CucumberExpression.new(
179
- 'I have a {widget}',
180
- parameter_type_registry
139
+ 'I have a {widget}',
140
+ parameter_type_registry
181
141
  )
182
142
 
183
143
  class World
@@ -190,22 +150,11 @@ module Cucumber
190
150
  expect(args[0].value(World.new)).to eq('widget:bolt')
191
151
  end
192
152
 
193
- describe "escapes special characters" do
194
- %w(\\ [ ] ^ $ . | ? * +).each do |character|
195
- it "escapes #{character}" do
196
- expr = "I have {int} cuke(s) and #{character}"
197
- expression = CucumberExpression.new(expr, ParameterTypeRegistry.new)
198
- arg1 = expression.match("I have 800 cukes and #{character}")[0]
199
- expect(arg1.value(nil)).to eq(800)
200
- end
201
- end
202
- end
203
-
204
153
  def match(expression, text)
205
154
  cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new)
206
155
  args = cucumber_expression.match(text)
207
156
  return nil if args.nil?
208
- args.map {|arg| arg.value(nil)}
157
+ args.map { |arg| arg.value(nil) }
209
158
  end
210
159
  end
211
160
  end