diff --git a/cucumber-expressions/README.md b/cucumber-expressions/README.md index c7adb77441..60de40cfcb 100644 --- a/cucumber-expressions/README.md +++ b/cucumber-expressions/README.md @@ -128,6 +128,16 @@ It would also match this text: I have 42 cucumbers in my belly +If you ever need to match those parentheses literally, you can escape the +first opening parenthesis with a backslash: + + I have {int} cucumber(s) in my belly \(amazing!) + +This expression would match the following examples: + + I have 1 cucumber in my belly (amazing!) + I have 42 cucumbers in my belly (amazing!) + ## Alternative text Sometimes you want to relax your language, to make it flow better. For example: diff --git a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb index cdbe392ea4..43cde897d1 100644 --- a/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb +++ b/cucumber-expressions/ruby/lib/cucumber/cucumber_expressions/cucumber_expression.rb @@ -8,7 +8,8 @@ class CucumberExpression # Does not include (){} characters because they have special meaning ESCAPE_REGEXP = /([\\^\[$.|?*+\]])/ PARAMETER_REGEXP = /{([^}]+)}/ - OPTIONAL_REGEXP = /\(([^)]+)\)/ + # Parentheses will be double-escaped due to ESCAPE_REGEXP + OPTIONAL_REGEXP = /([\\][\\])?\(([^)]+)\)/ ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^\/]+)((\/[^\s^\/]+)+)/ attr_reader :source @@ -19,10 +20,18 @@ def initialize(expression, parameter_type_registry) regexp = '^' match_offset = 0 + # This will cause explicitly-escaped parentheses to be double-escaped expression = expression.gsub(ESCAPE_REGEXP, '\\\\\1') # Create non-capturing, optional capture groups from parenthesis - expression = expression.gsub(OPTIONAL_REGEXP, '(?:\1)?') + expression = expression.gsub(OPTIONAL_REGEXP) do |match| + # look for double-escaped parentheses + if $1 == '\\\\' + "\\(#{$2}\\)" + else + "(?:#{$2})?" + end + end expression = expression.gsub(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP) do |_| "(?:#{$1}#{$2.tr('/', '|')})" diff --git a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb index e48b888fac..511a36ae0b 100644 --- a/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +++ b/cucumber-expressions/ruby/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb @@ -26,7 +26,7 @@ module CucumberExpressions it('matches multiple double quoted strings') do expect(match('three {string} and {string} mice', 'three "blind" and "crippled" mice')).to eq(['blind', 'crippled']) end - + it('matches single quoted string') do expect(match('three {string} mice', "three 'blind' mice")).to eq(['blind']) end @@ -55,6 +55,10 @@ module CucumberExpressions expect(match('three {string} mice', "three 'bl\\'nd' mice")).to eq(["bl'nd"]) end + it 'matches escaped parentheses' do + expect(match('three \\(exceptionally) {string} mice', 'three (exceptionally) "blind" mice')).to eq(['blind']) + end + it "matches int" do expect(match("{int}", "22")).to eq([22]) end