cucumber-expressions-8.0.0/0000755000175000017500000000000013524764173014700 5ustar jamesjamescucumber-expressions-8.0.0/README.md0000644000175000017500000000041013524764173016152 0ustar jamesjames# Cucumber Expressions for Ruby [![Build Status](https://travis-ci.org/cucumber/cucumber-expressions-ruby.svg?branch=master)](https://travis-ci.org/cucumber/cucumber-expressions-ruby) [The docs are here](https://cucumber.io/docs/cucumber/cucumber-expressions/). cucumber-expressions-8.0.0/.github/0000755000175000017500000000000013524764173016240 5ustar jamesjamescucumber-expressions-8.0.0/.github/PULL_REQUEST_TEMPLATE.md0000644000175000017500000000027113524764173022041 0ustar jamesjamesPLEASE DO NOT CREATE PULL REAUESTS IN THIS REPO. THIS REPO IS A READ-ONLY MIRROR. Create your pull request in the Cucumber monorepo instead: https://github.com/cucumber/cucumber/pulls cucumber-expressions-8.0.0/.github/ISSUE_TEMPLATE.md0000644000175000017500000000025413524764173020746 0ustar jamesjamesPLEASE DO NOT CREATE ISSUES IN THIS REPO. THIS REPO IS A READ-ONLY MIRROR. Create your issue in the Cucumber monorepo instead: https://github.com/cucumber/cucumber/issues cucumber-expressions-8.0.0/.subrepo0000644000175000017500000000004313524764173016355 0ustar jamesjamescucumber/cucumber-expressions-ruby cucumber-expressions-8.0.0/LICENSE0000644000175000017500000000206213524764173015705 0ustar jamesjamesThe MIT License (MIT) Copyright (c) Cucumber Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cucumber-expressions-8.0.0/scripts/0000755000175000017500000000000013524764173016367 5ustar jamesjamescucumber-expressions-8.0.0/scripts/update-gemspec0000755000175000017500000000143613524764173021224 0ustar jamesjames#!/usr/bin/env bash # # Updates the *.gemspec in the current directory to use the latest releases of gems # set -uf -o pipefail IFS=$'\n' gemspec=$(find . -type f -maxdepth 1 -name "*.gemspec") add_dependency_lines=$(cat ${gemspec} | grep "s.add_dependency '[^']*', '") if [ $? -ne 0 ]; then # No add_dependency_lines found - nothing to do exit 0 fi set -e gems=$(echo "${add_dependency_lines}" | tr -s ' ' | cut -d ' ' -f3 | cut -d"'" -f 2) while read -r gem; do gem_line=$(gem list "${gem}" --remote --all --no-prerelease | grep "${gem}") latest_version=$(echo "${gem_line}" | cut -d'(' -f2 | cut -d',' -f1) cat "${gemspec}" | sed "s/s.add_dependency '${gem}', .*/s.add_dependency '${gem}', '~> ${latest_version}'/" > ${gemspec}.tmp mv ${gemspec}.tmp ${gemspec} done <<< "${gems}" cucumber-expressions-8.0.0/lib/0000755000175000017500000000000013524764173015446 5ustar jamesjamescucumber-expressions-8.0.0/lib/cucumber/0000755000175000017500000000000013524764173017253 5ustar jamesjamescucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/0000755000175000017500000000000013524764173023522 5ustar jamesjamescucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb0000644000175000017500000000306713524764173030601 0ustar jamesjamesmodule Cucumber module CucumberExpressions class ParameterTypeMatcher attr_reader :parameter_type def initialize(parameter_type, regexp, text, match_position=0) @parameter_type, @regexp, @text = parameter_type, regexp, text @match = @regexp.match(@text, match_position) end def advance_to(new_match_position) (new_match_position...@text.length).each {|advancedPos| matcher = self.class.new(parameter_type, @regexp, @text, advancedPos) if matcher.find && matcher.full_word? return matcher end } self.class.new(parameter_type, @regexp, @text, @text.length) end def find !@match.nil? && !group.empty? end def full_word? space_before_match_or_sentence_start? && space_after_match_or_sentence_end? end def start @match.begin(0) end def group @match.captures[0] end def <=>(other) pos_comparison = start <=> other.start return pos_comparison if pos_comparison != 0 length_comparison = other.group.length <=> group.length return length_comparison if length_comparison != 0 0 end private def space_before_match_or_sentence_start? match_begin = @match.begin(0) match_begin == 0 || @text[match_begin - 1].match(/\s|\p{P}/) end def space_after_match_or_sentence_end? match_end = @match.end(0) match_end == @text.length || @text[match_end].match(/\s|\p{P}/) end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/parameter_type_registry.rb0000644000175000017500000000652213524764173031025 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/errors' require 'cucumber/cucumber_expressions/cucumber_expression_generator' module Cucumber module CucumberExpressions class ParameterTypeRegistry INTEGER_REGEXPS = [/-?\d+/, /\d+/] FLOAT_REGEXP = /(?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][-+]?\d+)?/ WORD_REGEXP = /[^\s]+/ STRING_REGEXP = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/ ANONYMOUS_REGEXP = /.*/ def initialize @parameter_type_by_name = {} @parameter_types_by_regexp = Hash.new {|hash, regexp| hash[regexp] = []} define_parameter_type(ParameterType.new('int', INTEGER_REGEXPS, Integer, lambda {|s = nil| s && s.to_i}, true, true)) define_parameter_type(ParameterType.new('float', FLOAT_REGEXP, Float, lambda {|s = nil| s && s.to_f}, true, false)) define_parameter_type(ParameterType.new('word', WORD_REGEXP, String, lambda {|s = nil| s}, false, false)) define_parameter_type(ParameterType.new('string', STRING_REGEXP, String, lambda {|s = nil| s && s.gsub(/\\"/, '"').gsub(/\\'/, "'")}, true, false)) define_parameter_type(ParameterType.new('', ANONYMOUS_REGEXP, String, lambda {|s = nil| s}, false, true)) end def lookup_by_type_name(name) @parameter_type_by_name[name] end def lookup_by_regexp(parameter_type_regexp, expression_regexp, text) parameter_types = @parameter_types_by_regexp[parameter_type_regexp] return nil if parameter_types.nil? if parameter_types.length > 1 && !parameter_types[0].prefer_for_regexp_match? # We don't do this check on insertion because we only want to restrict # ambiguity when we look up by Regexp. Users of CucumberExpression should # not be restricted. generated_expressions = CucumberExpressionGenerator.new(self).generate_expressions(text) raise AmbiguousParameterTypeError.new(parameter_type_regexp, expression_regexp, parameter_types, generated_expressions) end parameter_types.first end def parameter_types @parameter_type_by_name.values end def define_parameter_type(parameter_type) if parameter_type.name != nil if @parameter_type_by_name.has_key?(parameter_type.name) if parameter_type.name.length == 0 raise CucumberExpressionError.new("The anonymous parameter type has already been defined") else raise CucumberExpressionError.new("There is already a parameter with name #{parameter_type.name}") end end @parameter_type_by_name[parameter_type.name] = parameter_type end parameter_type.regexps.each do |parameter_type_regexp| parameter_types = @parameter_types_by_regexp[parameter_type_regexp] if parameter_types.any? && parameter_types[0].prefer_for_regexp_match? && parameter_type.prefer_for_regexp_match? raise CucumberExpressionError.new("There can only be one preferential parameter type per regexp. The regexp /#{parameter_type_regexp}/ is used for two preferential parameter types, {#{parameter_types[0].name}} and {#{parameter_type.name}}") end parameter_types.push(parameter_type) parameter_types.sort! end end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/group_builder.rb0000644000175000017500000000162713524764173026717 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/group' module Cucumber module CucumberExpressions class GroupBuilder attr_accessor :source def initialize @group_builders = [] @capturing = true end def add(group_builder) @group_builders.push(group_builder) end def build(match, group_indices) group_index = group_indices.next children = @group_builders.map {|gb| gb.build(match, group_indices)} Group.new(match[group_index], match.offset(group_index)[0], match.offset(group_index)[1], children) end def set_non_capturing! @capturing = false end def capturing? @capturing end def move_children_to(group_builder) @group_builders.each do |child| group_builder.add(child) end end def children @group_builders end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/cucumber_expression.rb0000644000175000017500000001002613524764173030132 0ustar jamesjamesrequire '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 cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/group.rb0000644000175000017500000000057513524764173025212 0ustar jamesjamesmodule Cucumber module CucumberExpressions class Group attr_reader :value, :start, :end, :children def initialize(value, start, _end, children) @value = value @start = start @end = _end @children = children end def values (children.empty? ? [self] : children).map(&:value).compact end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/regular_expression.rb0000644000175000017500000000231013524764173027763 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/argument' require 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/tree_regexp' module Cucumber module CucumberExpressions class RegularExpression def initialize(expression_regexp, parameter_type_registry) @expression_regexp = expression_regexp @parameter_type_registry = parameter_type_registry @tree_regexp = TreeRegexp.new(@expression_regexp) end def match(text) parameter_types = @tree_regexp.group_builder.children.map do |group_builder| parameter_type_regexp = group_builder.source @parameter_type_registry.lookup_by_regexp( parameter_type_regexp, @expression_regexp, text ) || ParameterType.new( nil, parameter_type_regexp, String, lambda {|*s| s[0]}, false, false ) end Argument.build(@tree_regexp, text, parameter_types) end def regexp @expression_regexp end def source @expression_regexp.source end def to_s regexp.inspect end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/argument.rb0000644000175000017500000000222213524764173025667 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/group' require 'cucumber/cucumber_expressions/errors' module Cucumber module CucumberExpressions class Argument attr_reader :group def self.build(tree_regexp, text, parameter_types) group = tree_regexp.match(text) return nil if group.nil? arg_groups = group.children if arg_groups.length != parameter_types.length raise CucumberExpressionError.new( "Expression #{tree_regexp.regexp.inspect} has #{arg_groups.length} capture groups (#{arg_groups.map(&:value)}), but there were #{parameter_types.length} parameter types (#{parameter_types.map(&:name)})" ) end parameter_types.zip(arg_groups).map do |parameter_type, arg_group| Argument.new(arg_group, parameter_type) end end def initialize(group, parameter_type) @group, @parameter_type = group, parameter_type end def value(self_obj=:nil) raise "No self_obj" if self_obj == :nil group_values = @group ? @group.values : nil @parameter_type.transform(self_obj, group_values) end end end end ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootcucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rbcucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_fact0000644000175000017500000000311613524764173033563 0ustar jamesjamesrequire('cucumber/cucumber_expressions/generated_expression') module Cucumber module CucumberExpressions class CombinatorialGeneratedExpressionFactory def initialize(expression_template, parameter_type_combinations) @expression_template = expression_template @parameter_type_combinations = parameter_type_combinations end def generate_expressions generated_expressions = [] generate_permutations(generated_expressions, 0, []) generated_expressions end # 256 generated expressions ought to be enough for anybody MAX_EXPRESSIONS = 256 def generate_permutations(generated_expressions, depth, current_parameter_types) if generated_expressions.length >= MAX_EXPRESSIONS return end if depth == @parameter_type_combinations.length generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types) generated_expressions.push(generated_expression) return end (0...@parameter_type_combinations[depth].length).each do |i| # Avoid recursion if no elements can be added. if generated_expressions.length >= MAX_EXPRESSIONS return end new_current_parameter_types = current_parameter_types.dup # clone new_current_parameter_types.push(@parameter_type_combinations[depth][i]) generate_permutations( generated_expressions, depth + 1, new_current_parameter_types ) end end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/parameter_type.rb0000644000175000017500000000554013524764173027074 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/errors' module Cucumber module CucumberExpressions class ParameterType ILLEGAL_PARAMETER_NAME_PATTERN = /([\[\]()$.|?*+])/ UNESCAPE_PATTERN = /(\\([\[$.|?*+\]]))/ attr_reader :name, :type, :regexps def self.check_parameter_type_name(type_name) unescaped_type_name = type_name.gsub(UNESCAPE_PATTERN) do $2 end if ILLEGAL_PARAMETER_NAME_PATTERN =~ unescaped_type_name raise CucumberExpressionError.new("Illegal character '#{$1}' in parameter name {#{unescaped_type_name}}") end end def prefer_for_regexp_match? @prefer_for_regexp_match end def use_for_snippets? @use_for_snippets end # Create a new Parameter # # @param name the name of the parameter type # @param regexp [Array] list of regexps for capture groups. A single regexp can also be used # @param type the return type of the transformed # @param transformer lambda that transforms a String to (possibly) another type # @param use_for_snippets true if this should be used for snippet generation # @param prefer_for_regexp_match true if this should be preferred over similar types # def initialize(name, regexp, type, transformer, use_for_snippets, prefer_for_regexp_match) raise "regexp can't be nil" if regexp.nil? raise "type can't be nil" if type.nil? raise "transformer can't be nil" if transformer.nil? raise "use_for_snippets can't be nil" if use_for_snippets.nil? raise "prefer_for_regexp_match can't be nil" if prefer_for_regexp_match.nil? self.class.check_parameter_type_name(name) unless name.nil? @name, @type, @transformer, @use_for_snippets, @prefer_for_regexp_match = name, type, transformer, use_for_snippets, prefer_for_regexp_match @regexps = string_array(regexp) end def transform(self_obj, group_values) self_obj.instance_exec(*group_values, &@transformer) end def <=>(other) return -1 if prefer_for_regexp_match? && !other.prefer_for_regexp_match? return 1 if other.prefer_for_regexp_match? && !prefer_for_regexp_match? return name <=> other.name end private def string_array(regexps) array = regexps.is_a?(Array) ? regexps : [regexps] array.map {|regexp| regexp.is_a?(String) ? regexp : regexp_source(regexp)} end def regexp_source(regexp) [ 'EXTENDED', 'IGNORECASE', 'MULTILINE' ].each do |option_name| option = Regexp.const_get(option_name) if regexp.options & option != 0 raise CucumberExpressionError.new("ParameterType Regexps can't use option Regexp::#{option_name}") end end regexp.source end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/generated_expression.rb0000644000175000017500000000146313524764173030270 0ustar jamesjamesmodule Cucumber module CucumberExpressions class GeneratedExpression attr_reader :parameter_types def initialize(expression_template, parameters_types) @expression_template, @parameter_types = expression_template, parameters_types end def source sprintf(@expression_template, *@parameter_types.map(&:name)) end def parameter_names usage_by_type_name = Hash.new(0) @parameter_types.map do |t| get_parameter_name(t.name, usage_by_type_name) end end private def get_parameter_name(type_name, usage_by_type_name) count = usage_by_type_name[type_name] count += 1 usage_by_type_name[type_name] = count count == 1 ? type_name : "#{type_name}#{count}" end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/errors.rb0000644000175000017500000000226413524764173025367 0ustar jamesjamesmodule Cucumber module CucumberExpressions class CucumberExpressionError < StandardError end class UndefinedParameterTypeError < CucumberExpressionError def initialize(type_name) super("Undefined parameter type {#{type_name}}") end end class AmbiguousParameterTypeError < CucumberExpressionError def initialize(parameter_type_regexp, expression_regexp, parameter_types, generated_expressions) super(<<-EOM) Your Regular Expression /#{expression_regexp.source}/ matches multiple parameter types with regexp /#{parameter_type_regexp}/: #{parameter_type_names(parameter_types)} I couldn't decide which one to use. You have two options: 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these: #{expressions(generated_expressions)} 2) Make one of the parameter types preferential and continue to use a Regular Expression. EOM end private def parameter_type_names(parameter_types) parameter_types.map{|p| "{#{p.name}}"}.join("\n ") end def expressions(generated_expressions) generated_expressions.map{|ge| ge.source}.join("\n ") end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/tree_regexp.rb0000644000175000017500000000455613524764173026372 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/group_builder' require 'cucumber/cucumber_expressions/errors' module Cucumber module CucumberExpressions class TreeRegexp attr_reader :regexp, :group_builder def initialize(regexp) @regexp = regexp.is_a?(Regexp) ? regexp : Regexp.new(regexp) @stack = [GroupBuilder.new] group_start_stack = [] last = nil escaping = false @non_capturing_maybe = false @name_capturing_maybe = false char_class = false @regexp.source.each_char.with_index do |c, n| if c == '[' && !escaping char_class = true elsif c == ']' && !escaping char_class = false elsif c == '(' && !escaping && !char_class @stack.push(GroupBuilder.new) group_start_stack.push(n+1) @non_capturing_maybe = false elsif c == ')' && !escaping && !char_class gb = @stack.pop group_start = group_start_stack.pop if gb.capturing? gb.source = @regexp.source[group_start...n] @stack.last.add(gb) else gb.move_children_to(@stack.last) end end_group elsif c == '?' && last == '(' @non_capturing_maybe = true elsif (c == '<') && @non_capturing_maybe @name_capturing_maybe = true elsif (c == ':' || c == '!' || c == '=') && last == '?' && @non_capturing_maybe end_non_capturing_group() elsif (c == '=' || c == '!') && last == '<' && @name_capturing_maybe end_non_capturing_group() elsif @name_capturing_maybe raise CucumberExpressionError.new("Named capture groups are not supported. See https://github.com/cucumber/cucumber/issues/329") end escaping = c == '\\' && !escaping last = c end @group_builder = @stack.pop end def match(s) match = @regexp.match(s) return nil if match.nil? group_indices = (0..match.length).to_a.to_enum @group_builder.build(match, group_indices) end private def end_non_capturing_group @stack.last.set_non_capturing! end_group end def end_group @non_capturing_maybe = false @name_capturing_maybe = false end end end end cucumber-expressions-8.0.0/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb0000644000175000017500000000726113524764173032207 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/parameter_type_matcher' require 'cucumber/cucumber_expressions/generated_expression' require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory' module Cucumber module CucumberExpressions class CucumberExpressionGenerator def initialize(parameter_type_registry) @parameter_type_registry = parameter_type_registry end def generate_expression(text) generate_expressions(text)[0] end def generate_expressions(text) parameter_type_combinations = [] parameter_type_matchers = create_parameter_type_matchers(text) expression_template = "" pos = 0 loop do matching_parameter_type_matchers = [] parameter_type_matchers.each do |parameter_type_matcher| advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos) if advanced_parameter_type_matcher.find matching_parameter_type_matchers.push(advanced_parameter_type_matcher) end end if matching_parameter_type_matchers.any? matching_parameter_type_matchers = matching_parameter_type_matchers.sort best_parameter_type_matcher = matching_parameter_type_matchers[0] best_parameter_type_matchers = matching_parameter_type_matchers.select do |m| (m <=> best_parameter_type_matcher).zero? end # Build a list of parameter types without duplicates. The reason there # might be duplicates is that some parameter types have more than one regexp, # which means multiple ParameterTypeMatcher objects will have a reference to the # same ParameterType. # We're sorting the list so prefer_for_regexp_match parameter types are listed first. # Users are most likely to want these, so they should be listed at the top. parameter_types = [] best_parameter_type_matchers.each do |parameter_type_matcher| unless parameter_types.include?(parameter_type_matcher.parameter_type) parameter_types.push(parameter_type_matcher.parameter_type) end end parameter_types.sort! parameter_type_combinations.push(parameter_types) expression_template += escape(text.slice(pos...best_parameter_type_matcher.start)) expression_template += "{%s}" pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length else break end if pos >= text.length break end end expression_template += escape(text.slice(pos..-1)) CombinatorialGeneratedExpressionFactory.new( expression_template, parameter_type_combinations ).generate_expressions end private def create_parameter_type_matchers(text) parameter_matchers = [] @parameter_type_registry.parameter_types.each do |parameter_type| if parameter_type.use_for_snippets? parameter_matchers += create_parameter_type_matchers2(parameter_type, text) end end parameter_matchers end def create_parameter_type_matchers2(parameter_type, text) result = [] regexps = parameter_type.regexps regexps.each do |regexp| regexp = Regexp.new("(#{regexp})") result.push(ParameterTypeMatcher.new(parameter_type, regexp, text, 0)) end result end def escape(s) s.gsub(/%/, '%%') .gsub(/\(/, '\\(') .gsub(/\{/, '\\{') .gsub(/\//, '\\/') end end end end cucumber-expressions-8.0.0/default.mk0000644000175000017500000000150613524764173016657 0ustar jamesjamesSHELL := /usr/bin/env bash RUBY_SOURCE_FILES = $(shell find . -name "*.rb") GEMSPEC = $(shell find . -name "*.gemspec") default: .tested .PHONY: default .deps: Gemfile.lock touch $@ Gemfile.lock: Gemfile $(GEMSPEC) bundle install touch $@ .tested: .deps $(RUBY_SOURCE_FILES) bundle exec rspec --color touch $@ update-dependencies: ./scripts/update-gemspec .PHONY: update-dependencies update-version: ifdef NEW_VERSION sed -i "s/\(s\.version *= *'\)[0-9]*\.[0-9]*\.[0-9]*\('\)/\1$(NEW_VERSION)\2/" $(GEMSPEC) else @echo -e "\033[0;NEW_VERSION is not defined. Can't update version :-(\033[0m" exit 1 endif .PHONY: update-version publish: .deps gem build $(GEMSPEC) gem push $$(find . -name "*.gem") .PHONY: publish clean: clean-ruby .PHONY: clean clean-ruby: rm -f .deps .linked .tested Gemfile.lock .PHONY: clean-ruby cucumber-expressions-8.0.0/Rakefile0000644000175000017500000000072213524764173016346 0ustar jamesjames# encoding: utf-8 require 'rubygems' require 'bundler' Bundler::GemHelper.install_tasks $:.unshift File.expand_path("../lib", __FILE__) require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |t| t.ruby_opts = %w[-r./spec/coverage -w] end require_relative 'spec/capture_warnings' include CaptureWarnings namespace :spec do task :warnings do report_warnings do Rake::Task['spec'].invoke end end end task default: ['spec:warnings'] cucumber-expressions-8.0.0/.rsync0000644000175000017500000000015613524764173016041 0ustar jamesjames../../LICENSE LICENSE ../../.templates/github/ .github/ ../../.templates/ruby/ . ../examples.txt examples.txt cucumber-expressions-8.0.0/cucumber-expressions.gemspec0000644000175000017500000000305413524764173022434 0ustar jamesjames# -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = 'cucumber-expressions' s.version = '8.0.0' s.authors = ["Aslak Hellesøy"] s.description = 'Cucumber Expressions - a simpler alternative to Regular Expressions' s.summary = "cucumber-expressions-#{s.version}" s.email = 'cukes@googlegroups.com' s.homepage = "https://github.com/cucumber/cucumber-expressions-ruby#readme" s.platform = Gem::Platform::RUBY s.license = "MIT" s.required_ruby_version = ">= 2.3" s.metadata = { 'bug_tracker_uri' => 'https://github.com/cucumber/cucumber/issues', 'changelog_uri' => 'https://github.com/cucumber/cucumber/blob/master/cucumber-expressions/CHANGELOG.md', 'documentation_uri' => 'https://cucumber.io/docs/cucumber/cucumber-expressions/', 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/cukes', 'source_code_uri' => 'https://github.com/cucumber/cucumber/blob/master/cucumber-expressions/ruby', } s.add_development_dependency 'bundler' s.add_development_dependency 'rake', '~> 12.3' s.add_development_dependency 'rspec', '~> 3.7' # For coverage reports s.add_development_dependency 'coveralls' s.rubygems_version = ">= 1.6.1" s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ } s.test_files = `git ls-files -- spec/*`.split("\n") s.rdoc_options = ["--charset=UTF-8"] s.require_path = "lib" end cucumber-expressions-8.0.0/examples.txt0000644000175000017500000000111513524764173017255 0ustar jamesjamesI have {int} cuke(s) I have 22 cukes [22] --- I have {int} cuke(s) and some \[]^$.|?*+ I have 1 cuke and some \[]^$.|?*+ [1] --- /I have (\d+) cukes? in my (\w+) now/ I have 22 cukes in my belly now [22,"belly"] --- /I have (-?\d+) cukes? in my (.*) now/ I have 1 cuke in my belly now [1,"belly"] --- /^Something( with an optional argument)?$/ Something [null] --- Привет, {word}! Привет, Мир! ["Мир"] --- /I have (.*) cukes? in my (.*) now/ I have 22 cukes in my belly now ["22","belly"] --- I have {} cuke(s) in my {} now I have 22 cukes in my belly now ["22","belly"] cucumber-expressions-8.0.0/Makefile0000644000175000017500000000002213524764173016332 0ustar jamesjamesinclude default.mkcucumber-expressions-8.0.0/.rspec0000644000175000017500000000001013524764173016004 0ustar jamesjames--color cucumber-expressions-8.0.0/spec/0000755000175000017500000000000013524764173015632 5ustar jamesjamescucumber-expressions-8.0.0/spec/cucumber/0000755000175000017500000000000013524764173017437 5ustar jamesjamescucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/0000755000175000017500000000000013524764173023706 5ustar jamesjamescucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb0000644000175000017500000000327413524764173032711 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions describe CucumberExpression do context "Regexp translation" do def assert_regexp(expression, regexp) cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) expect(regexp).to eq(cucumber_expression.regexp) end it "translates no arguments" do assert_regexp( "I have 10 cukes in my belly now", /^I have 10 cukes in my belly now$/ ) end it "translates alternation" do assert_regexp( "I had/have a great/nice/charming friend", /^I (?:had|have) a (?:great|nice|charming) friend$/ ) end it "translates alternation with non-alpha" do assert_regexp( "I said Alpha1/Beta1", /^I said (?:Alpha1|Beta1)$/ ) end it "translates parameters" do assert_regexp( "I have {float} cukes at {int} o'clock", /^I have ((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][-+]?\d+)?) cukes at ((?:-?\d+)|(?:\d+)) o'clock$/ ) end it "translates parenthesis to non-capturing optional capture group" do assert_regexp( "I have many big(ish) cukes", /^I have many big(?:ish)? cukes$/ ) end it "translates parenthesis with alpha unicode" do assert_regexp( "Привет, Мир(ы)!", /^Привет, Мир(?:ы)?!$/ ) end end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/tree_regexp_spec.rb0000644000175000017500000001133613524764173027562 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/tree_regexp' module Cucumber module CucumberExpressions describe TreeRegexp do it 'exposes group source' do tr = TreeRegexp.new(/(a(?:b)?)(c)/) expect(tr.group_builder.children.map{|gb| gb.source}).to eq(['a(?:b)?', 'c']) end it 'builds tree' do tr = TreeRegexp.new(/(a(?:b)?)(c)/) group = tr.match('ac') expect(group.value).to eq('ac') expect(group.children[0].value).to eq('a') expect(group.children[0].children).to eq([]) expect(group.children[1].value).to eq('c') end it 'ignores `?:` as a non-capturing group' do tr = TreeRegexp.new(/a(?:b)(c)/) group = tr.match('abc') expect(group.value).to eq('abc') expect(group.children.length).to eq 1 end it 'ignores `?!` as a non-capturing group' do tr = TreeRegexp.new(/a(?!b)(.+)/) group = tr.match('aBc') expect(group.value).to eq('aBc') expect(group.children.length).to eq 1 end it 'ignores `?=` as a non-capturing group' do tr = TreeRegexp.new(/a(?=b)(.+)$/) group = tr.match('abc') expect(group.value).to eq('abc') expect(group.children[0].value).to eq('bc') expect(group.children.length).to eq 1 end it 'ignores `?<=` as a non-capturing group' do tr = TreeRegexp.new(/a(.+)(?<=c)$/) group = tr.match('abc') expect(group.value).to eq('abc') expect(group.children[0].value).to eq('bc') expect(group.children.length).to eq 1 end it 'ignores `?.+) (?.+)")?$/) }.to raise_error(/Named capture groups are not supported/) end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb0000644000175000017500000001517013524764173031664 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/regular_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions class Color attr_reader :name ### [color-constructor] def initialize(name) @name = name end ### [color-constructor] def ==(other) other.is_a?(Color) && other.name == name end end class CssColor attr_reader :name def initialize(name) @name = name end def ==(other) other.is_a?(CssColor) && other.name == name end end class Coordinate attr_reader :x, :y, :z def initialize(x, y, z) @x, @y, @z = x, y, z end def ==(other) other.is_a?(Coordinate) && other.x == x && other.y == y && other.z == z end end describe "Custom parameter type" do before do parameter_type_registry = ParameterTypeRegistry.new ### [add-color-parameter-type] parameter_type_registry.define_parameter_type(ParameterType.new( 'color', # name /red|blue|yellow/, # regexp Color, # type lambda {|s| Color.new(s)}, # transform true, # use_for_snippets false # prefer_for_regexp_match )) ### [add-color-parameter-type] @parameter_type_registry = parameter_type_registry end it "throws exception for illegal character in parameter name" do expect do ParameterType.new( '[string]', /.*/, String, lambda {|s| s}, true, false ) end.to raise_error("Illegal character '[' in parameter name {[string]}") end describe CucumberExpression do it "matches parameters with custom parameter type" do expression = CucumberExpression.new("I have a {color} ball", @parameter_type_registry) transformed_argument_value = expression.match("I have a red ball")[0].value(nil) expect(transformed_argument_value).to eq(Color.new('red')) end it "matches parameters with multiple capture groups" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'coordinate', /(\d+),\s*(\d+),\s*(\d+)/, Coordinate, lambda {|x, y, z| Coordinate.new(x.to_i, y.to_i, z.to_i)}, true, false )) expression = CucumberExpression.new( 'A {int} thick line from {coordinate} to {coordinate}', @parameter_type_registry ) args = expression.match('A 5 thick line from 10,20,30 to 40,50,60') thick = args[0].value(nil) expect(thick).to eq(5) from = args[1].value(nil) expect(from).to eq(Coordinate.new(10, 20, 30)) to = args[2].value(nil) expect(to).to eq(Coordinate.new(40, 50, 60)) end it "matches parameters with custom parameter type using optional capture group" do parameter_type_registry = ParameterTypeRegistry.new parameter_type_registry.define_parameter_type(ParameterType.new( 'color', [/red|blue|yellow/, /(?:dark|light) (?:red|blue|yellow)/], Color, lambda {|s| Color.new(s)}, true, false )) expression = CucumberExpression.new("I have a {color} ball", parameter_type_registry) transformed_argument_value = expression.match("I have a dark red ball")[0].value(nil) expect(transformed_argument_value).to eq(Color.new('dark red')) end it "defers transformation until queried from argument" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'throwing', /bad/, CssColor, lambda {|s| raise "Can't transform [#{s}]"}, true, false )) expression = CucumberExpression.new("I have a {throwing} parameter", @parameter_type_registry) args = expression.match("I have a bad parameter") expect {args[0].value(nil)}.to raise_error("Can't transform [bad]") end describe "conflicting parameter type" do it "is detected for type name" do expect { @parameter_type_registry.define_parameter_type(ParameterType.new( 'color', /.*/, CssColor, lambda {|s| CssColor.new(s)}, true, false )) }.to raise_error("There is already a parameter with name color") end it "is not detected for type" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'whatever', /.*/, Color, lambda {|s| Color.new(s)}, false, false )) end it "is not detected for regexp" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'css-color', /red|blue|yellow/, CssColor, lambda {|s| CssColor.new(s)}, true, false )) css_color = CucumberExpression.new("I have a {css-color} ball", @parameter_type_registry) css_color_value = css_color.match("I have a blue ball")[0].value(nil) expect(css_color_value).to eq(CssColor.new("blue")) color = CucumberExpression.new("I have a {color} ball", @parameter_type_registry) color_value = color.match("I have a blue ball")[0].value(nil) expect(color_value).to eq(Color.new("blue")) end end end describe RegularExpression do it "matches arguments with custom parameter type without name" do parameter_type_registry = ParameterTypeRegistry.new parameter_type_registry.define_parameter_type(ParameterType.new( nil, /red|blue|yellow/, Color, lambda {|s| Color.new(s)}, true, false )) expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, parameter_type_registry) value = expression.match("I have a red ball")[0].value(nil) expect(value).to eq(Color.new('red')) end end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb0000644000175000017500000000746213524764173032227 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/parameter_type_registry' require 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/errors' module Cucumber module CucumberExpressions CAPITALISED_WORD = /[A-Z]+\w+/ class Name end class Person end class Place end describe ParameterTypeRegistry do before do @registry = ParameterTypeRegistry.new end it 'does not allow more than one prefer_for_regexp_match parameter type for each regexp' do @registry.define_parameter_type(ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, true)) @registry.define_parameter_type(ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, false)) expect do @registry.define_parameter_type(ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, true)) end.to raise_error( CucumberExpressionError, "There can only be one preferential parameter type per regexp. The regexp /[A-Z]+\\w+/ is used for two preferential parameter types, {name} and {place}" ) end it 'looks up prefer_for_regexp_match parameter type by regexp' do name = ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, false) person = ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, true) place = ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, false) @registry.define_parameter_type(name) @registry.define_parameter_type(person) @registry.define_parameter_type(place) expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, "Lisa and Bob")).to eq(person) end it 'throws ambiguous exception when no parameter types are prefer_for_regexp_match' do name = ParameterType.new("name", CAPITALISED_WORD, Name, lambda {|s| Name.new}, true, false) person = ParameterType.new("person", CAPITALISED_WORD, Person, lambda {|s| Person.new}, true, false) place = ParameterType.new("place", CAPITALISED_WORD, Place, lambda {|s| Place.new}, true, false) @registry.define_parameter_type(name) @registry.define_parameter_type(person) @registry.define_parameter_type(place) expect do expect(@registry.lookup_by_regexp(CAPITALISED_WORD.source, /([A-Z]+\w+) and ([A-Z]+\w+)/, "Lisa and Bob")).to eq(person) end.to raise_error( CucumberExpressionError, "Your Regular Expression /([A-Z]+\\w+) and ([A-Z]+\\w+)/\n" + "matches multiple parameter types with regexp /[A-Z]+\\w+/:\n" + " {name}\n" + " {person}\n" + " {place}\n" + "\n" + "I couldn't decide which one to use. You have two options:\n" + "\n" + "1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:\n" + " {name} and {name}\n" + " {name} and {person}\n" + " {name} and {place}\n" + " {person} and {name}\n" + " {person} and {person}\n" + " {person} and {place}\n" + " {place} and {name}\n" + " {place} and {person}\n" + " {place} and {place}\n" + "\n" + "2) Make one of the parameter types preferential and continue to use a Regular Expression.\n" + "\n" ) end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb0000644000175000017500000001601513524764173031334 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions describe CucumberExpression do it "documents match arguments" do parameter_registry = ParameterTypeRegistry.new ### [capture-match-arguments] expr = "I have {int} cuke(s)" expression = CucumberExpression.new(expr, parameter_registry) args = expression.match("I have 7 cukes") expect(args[0].value(nil)).to eq(7) ### [capture-match-arguments] end it "matches word" do expect(match("three {word} mice", "three blind mice")).to eq(['blind']) end it('matches double quoted string') do expect(match('three {string} mice', 'three "blind" mice')).to eq(['blind']) end 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 it('matches multiple single quoted strings') do expect(match('three {string} and {string} mice', "three 'blind' and 'crippled' mice")).to eq(['blind', 'crippled']) end it('does not match misquoted string') do expect(match('three {string} mice', 'three "blind\' mice')).to eq(nil) end it('matches single quoted string with double quotes') do expect(match('three {string} mice', 'three \'"blind"\' mice')).to eq(['"blind"']) end it('matches double quoted string with single quotes') do expect(match('three {string} mice', 'three "\'blind\'" mice')).to eq(["'blind'"]) end it('matches double quoted string with escaped double quote') do expect(match('three {string} mice', 'three "bl\\"nd" mice')).to eq(['bl"nd']) end it('matches single quoted string with escaped single quote') do 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 escaped slash" do expect(match("12\\/2020", "12/2020")).to eq([]) end it "matches int" do expect(match("{int}", "22")).to eq([22]) end it "doesn't match float as int" do expect(match("{int}", "1.22")).to be_nil end it "matches int as float" do expect(match("{float}", "0")).to eq([0.0]) end it "matches float" do expect(match("{float}", "")).to eq(nil) expect(match("{float}", ".")).to eq(nil) expect(match("{float}", ",")).to eq(nil) expect(match("{float}", "-")).to eq(nil) expect(match("{float}", "E")).to eq(nil) expect(match("{float}", "1,")).to eq(nil) expect(match("{float}", ",1")).to eq(nil) expect(match("{float}", "1.")).to eq(nil) expect(match("{float}", "1")).to eq([1]) expect(match("{float}", "-1")).to eq([-1]) expect(match("{float}", "1.1")).to eq([1.1]) expect(match("{float}", "1,000")).to eq(nil) expect(match("{float}", "1,000,0")).to eq(nil) expect(match("{float}", "1,000.1")).to eq(nil) expect(match("{float}", "1,000,10")).to eq(nil) expect(match("{float}", "1,0.1")).to eq(nil) expect(match("{float}", "1,000,000.1")).to eq(nil) expect(match("{float}", "-1.1")).to eq([-1.1]) expect(match("{float}", ".1")).to eq([0.1]) expect(match("{float}", "-.1")).to eq([-0.1]) expect(match("{float}", "-.1000001")).to eq([-0.1000001]) expect(match("{float}", "1E1")).to eq([10.0]) expect(match("{float}", ".1E1")).to eq([1]) expect(match("{float}", "E1")).to eq(nil) expect(match("{float}", "-.1E-1")).to eq([-0.01]) expect(match("{float}", "-.1E-2")).to eq([-0.001]) expect(match("{float}", "-.1E+1")).to eq([-1]) expect(match("{float}", "-.1E+2")).to eq([-10]) expect(match("{float}", "-.1E1")).to eq([-1]) expect(match("{float}", "-.1E2")).to eq([-10]) end it "matches anonymous" do expect(match("{}", "0.22")).to eq(["0.22"]) end '[]()$.|?*+'.split('').each do |char| it "does not allow parameter type with #{char}" do expect {match("{#{char}string}", "something")}.to raise_error("Illegal character '#{char}' in parameter name {#{char}string}") end end it "throws unknown parameter type" do expect {match("{unknown}", "something")}.to raise_error('Undefined parameter type {unknown}') end it "does not allow optional parameter types" do expect {match("({int})", "3")}.to raise_error('Parameter types cannot be optional: ({int})') end it "does allow escaped optional parameter types" do expect(match("\\({int})", "(3)")).to eq([3]) end it "does not allow text/parameter type alternation" do expect {match("x/{int}", "3")}.to raise_error('Parameter types cannot be alternative: x/{int}') end it "does not allow parameter type/text alternation" do expect {match("{int}/x", "3")}.to raise_error('Parameter types cannot be alternative: {int}/x') end it "exposes source" do expr = "I have {int} cuke(s)" expect(CucumberExpression.new(expr, ParameterTypeRegistry.new).source).to eq(expr) end it "delegates transform to custom object" do parameter_type_registry = ParameterTypeRegistry.new parameter_type_registry.define_parameter_type( ParameterType.new( 'widget', /\w+/, Object, -> (s) { self.create_widget(s) }, false, true ) ) expression = CucumberExpression.new( 'I have a {widget}', parameter_type_registry ) class World def create_widget(s) "widget:#{s}" end end args = expression.match("I have a bolt") expect(args[0].value(World.new)).to eq('widget:bolt') end describe "escapes special characters" do %w(\\ [ ] ^ $ . | ? * +).each do |character| it "escapes #{character}" do expr = "I have {int} cuke(s) and #{character}" expression = CucumberExpression.new(expr, ParameterTypeRegistry.new) arg1 = expression.match("I have 800 cukes and #{character}")[0] expect(arg1.value(nil)).to eq(800) end end end def match(expression, text) cucumber_expression = CucumberExpression.new(expression, ParameterTypeRegistry.new) args = cucumber_expression.match(text) return nil if args.nil? args.map {|arg| arg.value(nil)} end end end end ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootcucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rbcucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_fac0000644000175000017500000000320413524764173033561 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory' module Cucumber module CucumberExpressions class Color; end class CssColor; end class Date; end class DateTime; end class Timestamp; end describe CombinatorialGeneratedExpressionFactory do it 'generates multiple expressions' do parameter_type_combinations = [ [ ParameterType.new('color', /red|blue|yellow/, Color, lambda {|s| Color.new}, true, false), ParameterType.new('csscolor', /red|blue|yellow/, CssColor, lambda {|s| CssColor.new}, true, false) ], [ ParameterType.new('date', /\d{4}-\d{2}-\d{2}/, Date, lambda {|s| Date.new}, true, false), ParameterType.new('datetime', /\d{4}-\d{2}-\d{2}/, DateTime, lambda {|s| DateTime.new}, true, false), ParameterType.new('timestamp', /\d{4}-\d{2}-\d{2}/, Timestamp, lambda {|s| Timestamp.new}, true, false) ] ] factory = CombinatorialGeneratedExpressionFactory.new( 'I bought a {%s} ball on {%s}', parameter_type_combinations ) expressions = factory.generate_expressions.map {|ge| ge.source} expect(expressions).to eq([ 'I bought a {color} ball on {date}', 'I bought a {color} ball on {datetime}', 'I bought a {color} ball on {timestamp}', 'I bought a {csscolor} ball on {date}', 'I bought a {csscolor} ball on {datetime}', 'I bought a {csscolor} ball on {timestamp}', ]) end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/regular_expression_spec.rb0000644000175000017500000000554213524764173031173 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/regular_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions describe RegularExpression do it "documents match arguments" do parameter_type_registry = ParameterTypeRegistry.new ### [capture-match-arguments] expr = /I have (\d+) cukes? in my (\w*) now/ expression = RegularExpression.new(expr, parameter_type_registry) args = expression.match("I have 7 cukes in my belly now") expect( args[0].value(nil) ).to eq(7) expect( args[1].value(nil) ).to eq("belly") ### [capture-match-arguments] end it "does no transform by default" do expect( match(/(\d\d)/, "22") ).to eq(["22"]) end it "does not transform anonymous" do expect( match(/(.*)/, "22") ).to eq(["22"]) end it "transforms negative int" do expect( match(/(-?\d+)/, "-22") ).to eq([-22]) end it "transforms positive int" do expect( match(/(\d+)/, "22") ).to eq([22]) end it "returns nil when there is no match" do expect( match(/hello/, "world") ).to be_nil end it "matches nested capture group without match" do expect( match(/^a user( named "([^"]*)")?$/, 'a user') ).to eq([nil]) end it "matches nested capture group with match" do expect( match(/^a user( named "([^"]*)")?$/, 'a user named "Charlie"') ).to eq(['Charlie']) end it "ignores non capturing groups" do expect( match( /(\S+) ?(can|cannot) (?:delete|cancel) the (\d+)(?:st|nd|rd|th) (attachment|slide) ?(?:upload)?/, "I can cancel the 1st slide upload") ).to eq(["I", "can", 1, "slide"]) end it "matches capture group nested in optional one" do regexp = /^a (pre-commercial transaction |pre buyer fee model )?purchase(?: for \$(\d+))?$/ expect( match(regexp, 'a purchase') ).to eq([nil, nil]) expect( match(regexp, 'a purchase for $33') ).to eq([nil, 33]) expect( match(regexp, 'a pre buyer fee model purchase') ).to eq(['pre buyer fee model ', nil]) end it "works with escaped parenthesis" do expect( match(/Across the line\(s\)/, 'Across the line(s)') ).to eq([]) end it "exposes source and regexp" do regexp = /I have (\d+) cukes? in my (\+) now/ expression = RegularExpression.new(regexp, ParameterTypeRegistry.new) expect(expression.regexp).to eq(regexp) expect(expression.source).to eq(regexp.source) end def match(expression, text) regular_expression = RegularExpression.new(expression, ParameterTypeRegistry.new) arguments = regular_expression.match(text) return nil if arguments.nil? arguments.map { |arg| arg.value(nil) } end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb0000644000175000017500000002065713524764173033411 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/cucumber_expression_generator' require 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/parameter_type' require 'cucumber/cucumber_expressions/parameter_type_registry' module Cucumber module CucumberExpressions describe CucumberExpressionGenerator do class Currency end before do @parameter_type_registry = ParameterTypeRegistry.new @generator = CucumberExpressionGenerator.new(@parameter_type_registry) end it "documents expression generation" do parameter_registry = ParameterTypeRegistry.new ### [generate-expression] generator = CucumberExpressionGenerator.new(parameter_registry) undefined_step_text = "I have 2 cucumbers and 1.5 tomato" generated_expression = generator.generate_expression(undefined_step_text) expect(generated_expression.source).to eq("I have {int} cucumbers and {float} tomato") expect(generated_expression.parameter_types[1].type).to eq(Float) ### [generate-expression] end it "generates expression for no args" do assert_expression("hello", [], "hello") end it "generates expression with escaped left parenthesis" do assert_expression( "\\(iii)", [], "(iii)") end it "generates expression with escaped left curly brace" do assert_expression( "\\{iii}", [], "{iii}") end it "generates expression with escaped slashes" do assert_expression( "The {int}\\/{int}\\/{int} hey", ["int", "int2", "int3"], "The 1814/05/17 hey") end it "generates expression for int float arg" do assert_expression( "I have {int} cukes and {float} euro", ["int", "float"], "I have 2 cukes and 1.5 euro") end it "generates expression for strings" do assert_expression( "I like {string} and {string}", ["string", "string2"], 'I like "bangers" and \'mash\'') end it "generates expression with % sign" do assert_expression( "I am {int}% foobar", ["int"], 'I am 20% foobar') end it "generates expression for just int" do assert_expression( "{int}", ["int"], "99999") end it "numbers only second argument when builtin type is not reserved keyword" do assert_expression( "I have {int} cukes and {int} euro", ["int", "int2"], "I have 2 cukes and 5 euro") end it "numbers only second argument when type is not reserved keyword" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'currency', '[A-Z]{3}', Currency, lambda {|s| Currency.new(s)}, true, true )) assert_expression( "I have a {currency} account and a {currency} account", ["currency", "currency2"], "I have a EUR account and a GBP account") end it "exposes parameters in generated expression" do expression = @generator.generate_expression("I have 2 cukes and 1.5 euro") types = expression.parameter_types.map(&:type) expect(types).to eq([Integer, Float]) end it "matches parameter types with optional capture groups" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'optional-flight', /(1st flight)?/, String, lambda {|s| s}, true, false )) @parameter_type_registry.define_parameter_type(ParameterType.new( 'optional-hotel', /(1 hotel)?/, String, lambda {|s| s}, true, false )) expression = @generator.generate_expressions("I reach Stage 4: 1st flight -1 hotel")[0] # While you would expect this to be `I reach Stage {int}: {optional-flight} -{optional-hotel}` # the `-1` causes {int} to match just before {optional-hotel}. expect(expression.source).to eq("I reach Stage {int}: {optional-flight} {int} hotel") end it "generates at most 256 expressions" do for i in 0..3 @parameter_type_registry.define_parameter_type(ParameterType.new( "my-type-#{i}", /([a-z] )*?[a-z]/, String, lambda {|s| s}, true, false )) end # This would otherwise generate 4^11=4194300 expressions and consume just shy of 1.5GB. expressions = @generator.generate_expressions("a s i m p l e s t e p") expect(expressions.length).to eq(256) end it "prefers expression with longest non empty match" do @parameter_type_registry.define_parameter_type(ParameterType.new( 'zero-or-more', /[a-z]*/, String, lambda {|s| s}, true, false )) @parameter_type_registry.define_parameter_type(ParameterType.new( 'exactly-one', /[a-z]/, String, lambda {|s| s}, true, false )) expressions = @generator.generate_expressions("a simple step") expect(expressions.length).to eq(2) expect(expressions[0].source).to eq("{exactly-one} {zero-or-more} {zero-or-more}") expect(expressions[1].source).to eq("{zero-or-more} {zero-or-more} {zero-or-more}") end context "does not suggest parameter when match is" do before do @parameter_type_registry.define_parameter_type(ParameterType.new( 'direction', /(up|down)/, String, lambda {|s| s}, true, false )) end it "at the beginning of a word" do expect(@generator.generate_expression("When I download a picture").source).not_to eq("When I {direction}load a picture") expect(@generator.generate_expression("When I download a picture").source).to eq("When I download a picture") end it "inside a word" do expect(@generator.generate_expression("When I watch the muppet show").source).not_to eq("When I watch the m{direction}pet show") expect(@generator.generate_expression("When I watch the muppet show").source).to eq("When I watch the muppet show") end it "at the end of a word" do expect(@generator.generate_expression("When I create a group").source).not_to eq("When I create a gro{direction}") expect(@generator.generate_expression("When I create a group").source).to eq("When I create a group") end end context "does suggest parameter when match is" do before do @parameter_type_registry.define_parameter_type(ParameterType.new( 'direction', /(up|down)/, String, lambda {|s| s}, true, false )) end it "a full word" do expect(@generator.generate_expression("When I go down the road").source).to eq("When I go {direction} the road") expect(@generator.generate_expression("When I walk up the hill").source).to eq("When I walk {direction} the hill") expect(@generator.generate_expression("up the hill, the road goes down").source).to eq("{direction} the hill, the road goes {direction}") end it 'wrapped around punctuation characters' do expect(@generator.generate_expression("When direction is:down").source).to eq("When direction is:{direction}") expect(@generator.generate_expression("Then direction is down.").source).to eq("Then direction is {direction}.") end end def assert_expression(expected_expression, expected_argument_names, text) generated_expression = @generator.generate_expression(text) expect(generated_expression.parameter_names).to eq(expected_argument_names) expect(generated_expression.source).to eq(expected_expression) cucumber_expression = CucumberExpression.new(generated_expression.source, @parameter_type_registry) match = cucumber_expression.match(text) if match.nil? raise "Expected text '#{text}' to match generated expression '#{generated_expression.source}'" end expect(match.length).to eq(expected_argument_names.length) end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/expression_examples_spec.rb0000644000175000017500000000207013524764173031341 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/cucumber_expression' require 'cucumber/cucumber_expressions/regular_expression' require 'cucumber/cucumber_expressions/parameter_type_registry' require 'json' module Cucumber module CucumberExpressions describe 'examples.txt' do def match(expression_text, text) expression = expression_text =~ /\/(.*)\// ? RegularExpression.new(Regexp.new($1), ParameterTypeRegistry.new) : CucumberExpression.new(expression_text, ParameterTypeRegistry.new) arguments = expression.match(text) return nil if arguments.nil? arguments.map { |arg| arg.value(nil) } end File.open(File.expand_path("../../../../examples.txt", __FILE__), "r:utf-8") do |io| chunks = io.read.split(/^---/m) chunks.each do |chunk| expression_text, text, expected_args = *chunk.strip.split(/\n/m) it "Works with: #{expression_text}" do expect( match(expression_text, text).to_json ).to eq(expected_args) end end end end end end cucumber-expressions-8.0.0/spec/cucumber/cucumber_expressions/parameter_type_spec.rb0000644000175000017500000000071113524764173030265 0ustar jamesjamesrequire 'cucumber/cucumber_expressions/parameter_type' module Cucumber module CucumberExpressions describe ParameterType do it 'does not allow ignore flag on regexp' do expect do ParameterType.new("case-insensitive", /[a-z]+/i, String, lambda {|s| s}, true, true) end.to raise_error( CucumberExpressionError, "ParameterType Regexps can't use option Regexp::IGNORECASE") end end end end cucumber-expressions-8.0.0/spec/coverage.rb0000644000175000017500000000034113524764173017750 0ustar jamesjames# frozen_string_literal: true require 'simplecov' formatters = [ SimpleCov::Formatter::HTMLFormatter ] SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(*formatters) SimpleCov.add_filter 'spec/' SimpleCov.start cucumber-expressions-8.0.0/spec/capture_warnings.rb0000644000175000017500000000361213524764173021534 0ustar jamesjames# frozen_string_literal: true # With thanks to @myronmarston # https://github.com/vcr/vcr/blob/master/spec/capture_warnings.rb module CaptureWarnings def report_warnings(&block) current_dir = Dir.pwd warnings, errors = capture_error(&block).partition { |line| line.include?('warning') } project_warnings, other_warnings = warnings.uniq.partition { |line| line.include?(current_dir) } if errors.any? puts errors.join("\n") end if other_warnings.any? puts "#{ other_warnings.count } warnings detected, set VIEW_OTHER_WARNINGS=true to see them." print_warnings('other', other_warnings) if ENV['VIEW_OTHER_WARNINGS'] end # Until they fix https://bugs.ruby-lang.org/issues/10661 if RUBY_VERSION == "2.2.0" project_warnings = project_warnings.reject { |w| w =~ /warning: possible reference to past scope/ } end if project_warnings.any? puts "#{ project_warnings.count } warnings detected" print_warnings('cucumber-expressions', project_warnings) fail "Please remove all cucumber-expressions warnings." end ensure_system_exit_if_required end def capture_error(&block) old_stderr = STDERR.clone pipe_r, pipe_w = IO.pipe pipe_r.sync = true error = String.new reader = Thread.new do begin loop do error << pipe_r.readpartial(1024) end rescue EOFError end end STDERR.reopen(pipe_w) block.call ensure capture_system_exit STDERR.reopen(old_stderr) pipe_w.close reader.join return error.split("\n") end def print_warnings(type, warnings) puts puts "-" * 30 + " #{type} warnings: " + "-" * 30 puts puts warnings.join("\n") puts puts "-" * 75 puts end def ensure_system_exit_if_required raise @system_exit if @system_exit end def capture_system_exit @system_exit = $! end end cucumber-expressions-8.0.0/Gemfile0000644000175000017500000000010413524764173016166 0ustar jamesjames# frozen_string_literal: true source "https://rubygems.org" gemspec