cucumber-expressions 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 20e3d4346c870de1268b51167007be59e26ca973
4
+ data.tar.gz: 0ff8f397d739c309bea2a8ee27e87dea441d0320
5
+ SHA512:
6
+ metadata.gz: 4badd619f8dcf0ffe6da3a62515776f3c2d482955e948b7ad47d475e2124638e3aeb3e177ad9e99bfe30c703663bec5511777486f1c666b1764d834371acd473
7
+ data.tar.gz: c707a12743cd5c77e49332b88d9d8b8f99a3f72ee8cd0bd019a85fa067f7eee3d33c4d693132fcba61efa5d8770ff88677815f3c192a56edfa2e680245d83a5b
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ # manyrepo: cucumber/cucumber-expressions-ruby
2
+ language: ruby
3
+ sudo: false
4
+
5
+ rvm:
6
+ - 2.3.1
7
+ - 2.2
8
+ - 2.1
9
+ - 2.0
10
+ - jruby-9.1.2.0
11
+
12
+ before_install:
13
+ - gem update bundler
14
+
15
+ notifications:
16
+ email:
17
+ - cukes-devs@googlegroups.com
18
+ webhooks:
19
+ urls: # gitter
20
+ - https://webhooks.gitter.im/e/dc010332f9d40fcc21c4
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+ gemspec
data/Makefile ADDED
@@ -0,0 +1,9 @@
1
+ LICENSE: ../../LICENSE
2
+ cp $< $@
3
+
4
+ .PHONY: release
5
+ release:
6
+ bundle exec rake build release:guard_clean release:rubygem_push
7
+ version=$$(cat *.gemspec | grep -m 1 ".version *= *" | sed "s/.*= *'\([^']*\)'.*/\1/"); \
8
+ git tag --annotate v$$version --message "Release $$version"
9
+ git push --tags
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Cucumber Expressions for Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/cucumber/cucumber-expressions-ruby.svg?branch=master)](https://travis-ci.org/cucumber/cucumber-expressions-ruby)
4
+
5
+ [The docs are here](http://docs.cucumber.io/cucumber-expressions/).
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ $:.unshift File.expand_path("../lib", __FILE__)
7
+
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.ruby_opts = %w[-r./spec/coverage -w]
11
+ end
12
+
13
+ require_relative 'spec/capture_warnings'
14
+ include CaptureWarnings
15
+ namespace :spec do
16
+ task :warnings do
17
+ report_warnings do
18
+ Rake::Task['spec'].invoke
19
+ end
20
+ end
21
+ end
22
+
23
+ task default: ['spec:warnings']
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = 'cucumber-expressions'
4
+ s.version = '1.0.0'
5
+ s.authors = ["Aslak Hellesøy"]
6
+ s.description = 'Cucumber Expressions - a simpler alternative to Regular Expressions'
7
+ s.summary = "cucumber-expressions-#{s.version}"
8
+ s.email = 'cukes@googlegroups.com'
9
+ s.homepage = "https://cucumber.io/"
10
+ s.platform = Gem::Platform::RUBY
11
+ s.license = "MIT"
12
+ s.required_ruby_version = ">= 1.9.3"
13
+
14
+ s.add_development_dependency 'bundler', '>= 1.12.5'
15
+ s.add_development_dependency 'rake', '>= 10.5.0'
16
+ s.add_development_dependency 'rspec', '~> 3.5'
17
+ s.add_development_dependency 'json', '~> 2.0.2'
18
+
19
+ # For coverage reports
20
+ s.add_development_dependency 'coveralls', '~> 0.7'
21
+
22
+ s.rubygems_version = ">= 1.6.1"
23
+ s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }
24
+ s.test_files = `git ls-files -- spec/*`.split("\n")
25
+ s.rdoc_options = ["--charset=UTF-8"]
26
+ s.require_path = "lib"
27
+ end
data/examples.txt ADDED
@@ -0,0 +1,15 @@
1
+ I have {n} cuke(s) in my {bodypart} now
2
+ I have 22 cukes in my belly now
3
+ ["22","belly"]
4
+ ---
5
+ I have {n:int} cuke(s) in my {bodypart} now
6
+ I have 1 cuke in my belly now
7
+ [1,"belly"]
8
+ ---
9
+ /I have (\d+) cukes? in my (.+) now/
10
+ I have 22 cukes in my belly now
11
+ [22,"belly"]
12
+ ---
13
+ /I have (-?\d+) cukes? in my (.*) now/
14
+ I have 1 cuke in my belly now
15
+ [1,"belly"]
@@ -0,0 +1,11 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class Argument
4
+ attr_reader :offset, :value, :transformed_value
5
+
6
+ def initialize(offset, value, transformed_value)
7
+ @offset, @value, @transformed_value = offset, value, transformed_value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require 'cucumber/cucumber_expressions/argument'
2
+
3
+ module Cucumber
4
+ module CucumberExpressions
5
+ class ArgumentMatcher
6
+ def self.match_arguments(regexp, text, transforms)
7
+ m = regexp.match(text)
8
+ return nil if m.nil?
9
+ (1...m.length).map do |index|
10
+ value = m[index]
11
+ transform = transforms[index-1]
12
+ transformed_value = transform.transform(value)
13
+ Argument.new(m.offset(index)[0], value, transformed_value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ require 'cucumber/cucumber_expressions/argument_matcher'
2
+ require 'cucumber/cucumber_expressions/transform'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ class CucumberExpression
7
+ PARAMETER_PATTERN = /\{([^}:]+)(:([^}]+))?}/
8
+ OPTIONAL_PATTERN = /\(([^\)]+)\)/
9
+
10
+ attr_reader :source
11
+
12
+ def initialize(expression, types, transform_lookup)
13
+ @source = expression
14
+ @transforms = []
15
+ regexp = "^"
16
+ type_index = 0
17
+ match = nil
18
+ match_offset = 0
19
+
20
+ # Create non-capturing, optional capture groups from parenthesis
21
+ expression = expression.gsub(OPTIONAL_PATTERN, '(?:\1)?')
22
+
23
+ loop do
24
+ match = PARAMETER_PATTERN.match(expression, match_offset)
25
+ break if match.nil?
26
+
27
+ parameter_name = match[1]
28
+ type_name = match[3]
29
+ type = types.length <= type_index ? nil : types[type_index]
30
+ type_index += 1
31
+
32
+ transform = nil
33
+ if (type)
34
+ transform = transform_lookup.lookup_by_type(type)
35
+ end
36
+ if (transform.nil? && type_name)
37
+ transform = transform_lookup.lookup_by_type_name(type_name, false)
38
+ end
39
+ if (transform.nil?)
40
+ transform = transform_lookup.lookup_by_type_name(parameter_name, true)
41
+ end
42
+ if (transform.nil?)
43
+ transform = transform_lookup.create_anonymous_lookup(lambda {|s| s})
44
+ end
45
+ @transforms.push(transform)
46
+
47
+ text = expression.slice(match_offset...match.offset(0)[0])
48
+ capture_regexp = "(#{transform.capture_group_regexps[0]})"
49
+ match_offset = match.offset(0)[1]
50
+ regexp += text
51
+ regexp += capture_regexp
52
+ end
53
+ regexp += expression.slice(match_offset..-1)
54
+ regexp += "$"
55
+ @regexp = Regexp.new(regexp)
56
+ end
57
+
58
+ def match(text)
59
+ ArgumentMatcher.match_arguments(@regexp, text, @transforms)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,76 @@
1
+ require 'cucumber/cucumber_expressions/transform_matcher'
2
+ require 'cucumber/cucumber_expressions/generated_expression'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ class CucumberExpressionGenerator
7
+ def initialize(transform_lookup)
8
+ @transform_lookup = transform_lookup
9
+ end
10
+
11
+ def generate_expression(text, typed)
12
+ transform_matchers = create_transform_matchers(text)
13
+ transforms = []
14
+
15
+ expression = ""
16
+ arg_counter = 0
17
+ pos = 0
18
+
19
+ loop do
20
+ matching_transform_matchers = []
21
+ transform_matchers.each do |transform_matcher|
22
+ advanced_transform_matcher = transform_matcher.advance_to(pos)
23
+ if advanced_transform_matcher.find
24
+ matching_transform_matchers.push(advanced_transform_matcher)
25
+ end
26
+ end
27
+
28
+ if matching_transform_matchers.any?
29
+ matching_transform_matchers = matching_transform_matchers.sort
30
+ best_transform_matcher = matching_transform_matchers[0]
31
+ transforms.push(best_transform_matcher.transform)
32
+
33
+ expression += text.slice(pos...best_transform_matcher.start)
34
+ expression += "{arg#{arg_counter += 1}"
35
+
36
+ if typed
37
+ expression += ":#{best_transform_matcher.transform.type_name}"
38
+ end
39
+ expression += "}"
40
+ pos = best_transform_matcher.start + best_transform_matcher.group.length
41
+ else
42
+ break
43
+ end
44
+
45
+ if pos >= text.length
46
+ break
47
+ end
48
+ end
49
+
50
+ expression += text.slice(pos..-1)
51
+ GeneratedExpression.new(expression, transforms)
52
+ end
53
+
54
+ private
55
+
56
+ def create_transform_matchers(text)
57
+ transform_matchers = []
58
+ @transform_lookup.transforms.each do |transform|
59
+ transform_matchers += create_transform_matchers2(transform, text)
60
+ end
61
+ transform_matchers
62
+ end
63
+
64
+ def create_transform_matchers2(transform, text)
65
+ result = []
66
+ capture_group_regexps = transform.capture_group_regexps
67
+ capture_group_regexps.each do |capture_group_regexp|
68
+ regexp = Regexp.new("(#{capture_group_regexp})")
69
+ result.push(TransformMatcher.new(transform, regexp, text))
70
+ end
71
+ result
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class GeneratedExpression
4
+ attr_reader :source, :transforms
5
+
6
+ def initialize(source, transforms)
7
+ @source, @transforms = source, transforms
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ require 'cucumber/cucumber_expressions/argument_matcher'
2
+
3
+ module Cucumber
4
+ module CucumberExpressions
5
+ class RegularExpression
6
+ CAPTURE_GROUP_PATTERN = /\(([^(]+)\)/
7
+
8
+ def initialize(regexp, types, transform_lookup)
9
+ @regexp = regexp
10
+ @transforms = []
11
+
12
+ type_index = 0
13
+ match = nil
14
+ match_offset = 0
15
+
16
+ loop do
17
+ match = CAPTURE_GROUP_PATTERN.match(regexp.source, match_offset)
18
+ break if match.nil?
19
+
20
+ capture_group_pattern = match[1]
21
+ type = types.length <= type_index ? nil : types[type_index]
22
+ type_index += 1
23
+
24
+ transform = nil
25
+ if (type)
26
+ transform = transform_lookup.lookup_by_type(type)
27
+ end
28
+ if (transform.nil?)
29
+ transform = transform_lookup.lookup_by_capture_group_regexp(capture_group_pattern)
30
+ end
31
+ if (transform.nil?)
32
+ transform = transform_lookup.create_anonymous_lookup(lambda {|s| s})
33
+ end
34
+
35
+ @transforms.push(transform)
36
+ match_offset = match.offset(0)[1]
37
+ end
38
+ end
39
+
40
+ def match(text)
41
+ ArgumentMatcher.match_arguments(@regexp, text, @transforms)
42
+ end
43
+
44
+ def source
45
+ @regexp
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class Transform
4
+ attr_reader :type_name, :type, :capture_group_regexps
5
+
6
+ # Create a new Transform
7
+ #
8
+ # @param type_name [Array] array of class or type name to use in {arg:type_name}
9
+ # @param capture_group_regexps [Array] list of regexps for capture groups.
10
+ # The first one is the primary one, used to convert CucumberExpression instances
11
+ # to their internal Regexp representation. They are all used for type conversion
12
+ # in RegularExpression instances.
13
+ # @transformer lambda that transforms a String to another type
14
+ #
15
+ def initialize(type_name, type, capture_group_regexps, transformer)
16
+ @type_name, @type, @transformer = type_name, type, transformer
17
+ @capture_group_regexps = capture_group_regexps.is_a?(String) ? [capture_group_regexps] : capture_group_regexps
18
+ end
19
+
20
+ def transform(value)
21
+ @transformer.call(value)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ require 'cucumber/cucumber_expressions/transform'
2
+
3
+ module Cucumber
4
+ module CucumberExpressions
5
+ class TransformLookup
6
+ FIXNUM_REGEXPS = ['-?\d+', '\d+']
7
+ FLOATING_POINT_REGEXPS = ['-?\d*\.?\d+']
8
+
9
+ def initialize
10
+ @transforms_by_type_name = {}
11
+ @transforms_by_capture_group_regexp = {}
12
+ @transforms_by_class = {}
13
+
14
+ add_transform(Transform.new('int', Fixnum, FIXNUM_REGEXPS, lambda {|s| s.to_i}))
15
+ add_transform(Transform.new('float', Float, FLOATING_POINT_REGEXPS, lambda {|s| s.to_f}))
16
+ end
17
+
18
+ def lookup_by_type(type)
19
+ if type.is_a?(Class)
20
+ lookup_by_class(type)
21
+ elsif type.is_a?(String)
22
+ lookup_by_type_name(type, false)
23
+ else
24
+ raise Exception.new("Type must be string or class, but was #{type} of type #{type.class}")
25
+ end
26
+ end
27
+
28
+ def lookup_by_class(clazz)
29
+ transform = @transforms_by_class[clazz]
30
+ if transform.nil?
31
+ create_anonymous_lookup(lambda {|s| clazz.new(s)})
32
+ else
33
+ transform
34
+ end
35
+ end
36
+
37
+ def lookup_by_type_name(type_name, ignore_unknown_type_name)
38
+ transform = @transforms_by_type_name[type_name]
39
+ if transform.nil?
40
+ return nil if ignore_unknown_type_name
41
+ raise Exception.new("No transform for type name \"#{type_name}\"")
42
+ else
43
+ transform
44
+ end
45
+ end
46
+
47
+ def lookup_by_capture_group_regexp(capture_group_regexp)
48
+ @transforms_by_capture_group_regexp[capture_group_regexp]
49
+ end
50
+
51
+ def create_anonymous_lookup(proc)
52
+ Transform.new(nil, nil, ['.+'], proc)
53
+ end
54
+
55
+ def add_transform(transform)
56
+ @transforms_by_type_name[transform.type_name] = transform
57
+ @transforms_by_class[transform.type] = transform
58
+
59
+ transform.capture_group_regexps.each do |capture_group_regexp|
60
+ @transforms_by_capture_group_regexp[capture_group_regexp] = transform
61
+ end
62
+ end
63
+
64
+ def transforms
65
+ @transforms_by_type_name.values
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,36 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class TransformMatcher
4
+ attr_reader :transform
5
+
6
+ def initialize(transform, regexp, text, match_position=0)
7
+ @transform, @regexp, @text = transform, regexp, text
8
+ @match = @regexp.match(@text, match_position)
9
+ end
10
+
11
+ def advance_to(new_match_position)
12
+ self.class.new(@transform, @regexp, @text, new_match_position)
13
+ end
14
+
15
+ def find
16
+ !@match.nil?
17
+ end
18
+
19
+ def start
20
+ @match.begin(0)
21
+ end
22
+
23
+ def group
24
+ @match.captures[0]
25
+ end
26
+
27
+ def <=>(other)
28
+ pos_comparison = start <=> other.start
29
+ return pos_comparison if pos_comparison != 0
30
+ length_comparison = other.group.length <=> group.length
31
+ return length_comparison if length_comparison != 0
32
+ 0
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ # With thanks to @myronmarston
3
+ # https://github.com/vcr/vcr/blob/master/spec/capture_warnings.rb
4
+
5
+ module CaptureWarnings
6
+ def report_warnings(&block)
7
+ current_dir = Dir.pwd
8
+ warnings, errors = capture_error(&block).partition { |line| line.include?('warning') }
9
+ project_warnings, other_warnings = warnings.uniq.partition { |line| line.include?(current_dir) }
10
+
11
+ if errors.any?
12
+ puts errors.join("\n")
13
+ end
14
+
15
+ if other_warnings.any?
16
+ puts "#{ other_warnings.count } non-cucumber-core warnings detected, set VIEW_OTHER_WARNINGS=true to see them."
17
+ print_warnings('other', other_warnings) if ENV['VIEW_OTHER_WARNINGS']
18
+ end
19
+
20
+ # Until they fix https://bugs.ruby-lang.org/issues/10661
21
+ if RUBY_VERSION == "2.2.0"
22
+ project_warnings = project_warnings.reject { |w| w =~ /warning: possible reference to past scope/ }
23
+ end
24
+
25
+ if project_warnings.any?
26
+ puts "#{ project_warnings.count } cucumber-core warnings detected"
27
+ print_warnings('cucumber-core', project_warnings)
28
+ fail "Please remove all cucumber-core warnings."
29
+ end
30
+
31
+ ensure_system_exit_if_required
32
+ end
33
+
34
+ def capture_error(&block)
35
+ old_stderr = STDERR.clone
36
+ pipe_r, pipe_w = IO.pipe
37
+ pipe_r.sync = true
38
+ error = String.new
39
+ reader = Thread.new do
40
+ begin
41
+ loop do
42
+ error << pipe_r.readpartial(1024)
43
+ end
44
+ rescue EOFError
45
+ end
46
+ end
47
+ STDERR.reopen(pipe_w)
48
+ block.call
49
+ ensure
50
+ capture_system_exit
51
+ STDERR.reopen(old_stderr)
52
+ pipe_w.close
53
+ reader.join
54
+ return error.split("\n")
55
+ end
56
+
57
+ def print_warnings(type, warnings)
58
+ puts
59
+ puts "-" * 30 + " #{type} warnings: " + "-" * 30
60
+ puts
61
+ puts warnings.join("\n")
62
+ puts
63
+ puts "-" * 75
64
+ puts
65
+ end
66
+
67
+ def ensure_system_exit_if_required
68
+ raise @system_exit if @system_exit
69
+ end
70
+
71
+ def capture_system_exit
72
+ @system_exit = $!
73
+ end
74
+ end
data/spec/coverage.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ require 'simplecov'
3
+ formatters = [ SimpleCov::Formatter::HTMLFormatter ]
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(*formatters)
6
+ SimpleCov.start
@@ -0,0 +1,77 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression_generator'
2
+ require 'cucumber/cucumber_expressions/transform'
3
+ require 'cucumber/cucumber_expressions/transform_lookup'
4
+
5
+ module Cucumber
6
+ module CucumberExpressions
7
+ describe CucumberExpressionGenerator do
8
+ class Currency
9
+ end
10
+
11
+ before do
12
+ @transform_lookup = TransformLookup.new
13
+ @generator = CucumberExpressionGenerator.new(@transform_lookup)
14
+ end
15
+
16
+ it "documents expression generation" do
17
+ transform_lookup = TransformLookup.new
18
+ ### [generate-expression]
19
+ generator = CucumberExpressionGenerator.new(transform_lookup)
20
+ undefined_step_text = "I have 2 cucumbers and 1.5 tomato"
21
+ generated_expression = generator.generate_expression(undefined_step_text, true)
22
+ expect(generated_expression.source).to eq("I have {arg1:int} cucumbers and {arg2:float} tomato")
23
+ expect(generated_expression.transforms[1].type).to eq(Float)
24
+ ### [generate-expression]
25
+ end
26
+
27
+ it "generates expression for no args" do
28
+ assert_typed_expression("hello", "hello")
29
+ end
30
+
31
+ it "generates expression for int double arg" do
32
+ assert_typed_expression(
33
+ "I have {arg1:int} cukes and {arg2:float} euro",
34
+ "I have 2 cukes and 1.5 euro")
35
+ end
36
+
37
+ it "generates expression for just int" do
38
+ assert_typed_expression(
39
+ "{arg1:int}",
40
+ "99999")
41
+ end
42
+
43
+ it "generates expression without expression type" do
44
+ assert_untyped_expression(
45
+ "I have {arg1} cukes and {arg2} euro",
46
+ "I have 2 cukes and 1.5 euro")
47
+ end
48
+
49
+ it "generates expression for custom type" do
50
+ @transform_lookup.add_transform(Transform.new(
51
+ 'currency',
52
+ Currency,
53
+ '[A-Z]{3}',
54
+ nil
55
+ ))
56
+
57
+ assert_typed_expression(
58
+ "I have a {arg1:currency} account",
59
+ "I have a EUR account")
60
+ end
61
+
62
+ it "exposes transforms in generated expression" do
63
+ expression = @generator.generate_expression("I have 2 cukes and 1.5 euro", true)
64
+ types = expression.transforms.map(&:type)
65
+ expect(types).to eq([Fixnum, Float])
66
+ end
67
+
68
+ def assert_typed_expression(expected, text)
69
+ expect(@generator.generate_expression(text, true).source).to eq(expected)
70
+ end
71
+
72
+ def assert_untyped_expression(expected, text)
73
+ expect(@generator.generate_expression(text, false).source).to eq(expected)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/transform_lookup'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ describe CucumberExpression do
7
+ context "Regexp translation" do
8
+ def assert_regexp(expression, regexp)
9
+ cucumber_expression = CucumberExpression.new(expression, [], TransformLookup.new)
10
+ expect(regexp).to eq(cucumber_expression.instance_variable_get('@regexp'))
11
+ end
12
+
13
+ it "translates no arguments" do
14
+ assert_regexp(
15
+ "I have 10 cukes in my belly now",
16
+ /^I have 10 cukes in my belly now$/
17
+ )
18
+ end
19
+
20
+ it "translates two untyped arguments" do
21
+ assert_regexp(
22
+ "I have {n} cukes in my {bodypart} now",
23
+ /^I have (.+) cukes in my (.+) now$/
24
+ )
25
+ end
26
+
27
+ it "translates three typed arguments" do
28
+ assert_regexp(
29
+ "I have {n:float} cukes in my {bodypart} at {time:int} o'clock",
30
+ /^I have (-?\d*\.?\d+) cukes in my (.+) at (-?\d+) o'clock$/
31
+ )
32
+ end
33
+
34
+ it "translates parenthesis to non-capturing optional capture group" do
35
+ assert_regexp(
36
+ "I have many big(ish) cukes",
37
+ /^I have many big(?:ish)? cukes$/
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/transform_lookup'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ describe CucumberExpression do
7
+ it "documents match arguments" do
8
+ transform_lookup = TransformLookup.new
9
+
10
+ ### [capture-match-arguments]
11
+ expr = "I have {n} cuke(s) in my {bodypart} now"
12
+ types = ['int', nil]
13
+ expression = CucumberExpression.new(expr, types, transform_lookup)
14
+ args = expression.match("I have 7 cukes in my belly now")
15
+ expect( args[0].transformed_value ).to eq(7)
16
+ expect( args[1].transformed_value ).to eq("belly")
17
+ ### [capture-match-arguments]
18
+ end
19
+
20
+ it "transforms nothing by default" do
21
+ expect( match("{what}", "22") ).to eq(["22"])
22
+ end
23
+
24
+ it "transforms to int by expression type" do
25
+ expect( match("{what:int}", "22") ).to eq([22])
26
+ end
27
+
28
+ it "transforms to int by explicit type" do
29
+ expect( match("{what}", "22", ['int']) ).to eq([22])
30
+ end
31
+
32
+ # Ruby-specific
33
+ it "transforms to Fixnum by explicit type" do
34
+ expect( match("{what}", "22", [Fixnum]) ).to eq([22])
35
+ end
36
+
37
+ it "doesn't match a float to an int" do
38
+ expect( match("{what:int}", "1.22") ).to be_nil
39
+ end
40
+
41
+ it "transforms to float by expression type" do
42
+ expect( match("{what:float}", "0.22") ).to eq([0.22])
43
+ expect( match("{what:float}", ".22") ).to eq([0.22])
44
+ end
45
+
46
+ it "transforms to float by explicit type" do
47
+ expect( match("{what}", "0.22", ['float']) ).to eq([0.22])
48
+ expect( match("{what}", ".22", ['float']) ).to eq([0.22])
49
+ end
50
+
51
+ it "doesn't transform unknown type" do
52
+ expect { match("{what:unknown}", "something") }.to raise_error(
53
+ 'No transform for type name "unknown"')
54
+ end
55
+
56
+ it "exposes source" do
57
+ expr = "I have {n:int} cuke(s) in my {bodypart} now"
58
+ expect(CucumberExpression.new(expr, [], TransformLookup.new).source).to eq(expr)
59
+ end
60
+
61
+ it "exposes offset and value" do
62
+ expr = "I have {n:int} cuke(s) in my {bodypart} now"
63
+ expression = CucumberExpression.new(expr, [], TransformLookup.new)
64
+ arg1 = expression.match("I have 800 cukes in my brain now")[0]
65
+ expect(arg1.offset).to eq(7)
66
+ expect(arg1.value).to eq("800")
67
+ end
68
+
69
+ def match(expression, text, types = [])
70
+ cucumber_expression = CucumberExpression.new(expression, types, TransformLookup.new)
71
+ args = cucumber_expression.match(text)
72
+ return nil if args.nil?
73
+ args.map { |arg| arg.transformed_value }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,81 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/regular_expression'
3
+ require 'cucumber/cucumber_expressions/transform_lookup'
4
+
5
+ module Cucumber
6
+ module CucumberExpressions
7
+ class Color
8
+ attr_reader :name
9
+
10
+ ### [color-constructor]
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+ ### [color-constructor]
15
+
16
+ def == (other)
17
+ other.is_a?(Color) && other.name == name
18
+ end
19
+ end
20
+
21
+ describe "Custom transform" do
22
+ before do
23
+ @transform_lookup = TransformLookup.new
24
+ ### [add-color-transform]
25
+ @transform_lookup.add_transform(Transform.new(
26
+ 'color',
27
+ Color,
28
+ 'red|blue|yellow',
29
+ lambda { |s| Color.new(s) }
30
+ ))
31
+ ### [add-color-transform]
32
+ end
33
+
34
+ describe CucumberExpression do
35
+ it "converts arguments with expression type" do
36
+ expression = CucumberExpression.new("I have a {color:color} ball", [], @transform_lookup)
37
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
38
+ expect( transformed_argument_value ).to eq(Color.new('red'))
39
+ end
40
+
41
+ it "converts arguments with explicit type" do
42
+ expression = CucumberExpression.new("I have a {color} ball", [Color], @transform_lookup)
43
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
44
+ expect( transformed_argument_value ).to eq(Color.new('red'))
45
+ end
46
+
47
+ it "converts arguments using argument name as type" do
48
+ expression = CucumberExpression.new("I have a {color} ball", [], @transform_lookup)
49
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
50
+ expect( transformed_argument_value ).to eq(Color.new('red'))
51
+ end
52
+
53
+ it "converts arguments with explicit type using constructor directly" do
54
+ expression = CucumberExpression.new("I have a {color} ball", [Color], TransformLookup.new)
55
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
56
+ expect( transformed_argument_value ).to eq(Color.new('red'))
57
+ end
58
+ end
59
+
60
+ describe RegularExpression do
61
+ it "converts arguments with expression type" do
62
+ expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, [], @transform_lookup)
63
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
64
+ expect( transformed_argument_value ).to eq(Color.new('red'))
65
+ end
66
+
67
+ it "converts arguments with explicit type" do
68
+ expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, ['color'], @transform_lookup)
69
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
70
+ expect( transformed_argument_value ).to eq(Color.new('red'))
71
+ end
72
+
73
+ it "converts arguments with explicit type using constructor directly" do
74
+ expression = RegularExpression.new(/I have a (red|blue|yellow) ball/, [Color], TransformLookup.new)
75
+ transformed_argument_value = expression.match("I have a red ball")[0].transformed_value
76
+ expect( transformed_argument_value ).to eq(Color.new('red'))
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ require 'cucumber/cucumber_expressions/cucumber_expression'
2
+ require 'cucumber/cucumber_expressions/regular_expression'
3
+ require 'cucumber/cucumber_expressions/transform_lookup'
4
+ require 'json'
5
+
6
+ module Cucumber
7
+ module CucumberExpressions
8
+ describe 'examples.txt' do
9
+ def match(expression_text, text)
10
+ expression = expression_text =~ /\/(.*)\// ?
11
+ RegularExpression.new(Regexp.new($1), [], TransformLookup.new) :
12
+ CucumberExpression.new(expression_text, [], TransformLookup.new)
13
+
14
+ arguments = expression.match(text)
15
+ return nil if arguments.nil?
16
+ arguments.map { |arg| arg.transformed_value }
17
+ end
18
+
19
+ File.open(File.expand_path("../../../../examples.txt", __FILE__), "r:utf-8") do |io|
20
+ chunks = io.read.split(/^---/m)
21
+ chunks.each do |chunk|
22
+ expression_text, text, expected_args = *chunk.strip.split(/\n/m)
23
+ it "Works with: #{expression_text}" do
24
+ expect( match(expression_text, text).to_json ).to eq(expected_args)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,70 @@
1
+ require 'cucumber/cucumber_expressions/regular_expression'
2
+ require 'cucumber/cucumber_expressions/transform_lookup'
3
+
4
+ module Cucumber
5
+ module CucumberExpressions
6
+ describe RegularExpression do
7
+ it "documents match arguments" do
8
+ transform_lookup = TransformLookup.new
9
+
10
+ ### [capture-match-arguments]
11
+ expr = /I have (\d+) cukes? in my (\w*) now/
12
+ types = ['int', nil]
13
+ expression = RegularExpression.new(expr, types, transform_lookup)
14
+ args = expression.match("I have 7 cukes in my belly now")
15
+ expect( args[0].transformed_value ).to eq(7)
16
+ expect( args[1].transformed_value ).to eq("belly")
17
+ ### [capture-match-arguments]
18
+ end
19
+
20
+ it "transforms to string by default" do
21
+ expect( match(/(\d\d)/, "22") ).to eq(["22"])
22
+ end
23
+
24
+ it "transforms integer to double using explicit type name" do
25
+ expect( match(/(.*)/, "22", ['float']) ).to eq([22.0])
26
+ end
27
+
28
+ it "transforms integer to double using explicit type" do
29
+ expect( match(/(.*)/, "22", [Float]) ).to eq([22.0])
30
+ end
31
+
32
+ it "transforms to int using capture group pattern" do
33
+ expect( match(/(-?\d+)/, "22") ).to eq([22])
34
+ end
35
+
36
+ it "transforms to int by alternate capture group pattern" do
37
+ expect( match(/(\d+)/, "22") ).to eq([22])
38
+ end
39
+
40
+ it "transforms double without integer value" do
41
+ expect( match(/(.*)/, ".22", ['float']) ).to eq([0.22])
42
+ end
43
+
44
+ it "transforms double with sign" do
45
+ expect( match(/(.*)/, "-1.22", ['float']) ).to eq([-1.22])
46
+ end
47
+
48
+ it "returns nil when there is no match" do
49
+ expect( match(/hello/, "world") ).to be_nil
50
+ end
51
+
52
+ it "fails when type is not type name or class" do
53
+ expect( lambda { match(/(.*)/, "-1.22", [99]) } ).to raise_error(
54
+ 'Type must be string or class, but was 99 of type Fixnum')
55
+ end
56
+
57
+ it "exposes source" do
58
+ expr = /I have (\d+) cukes? in my (\+) now/
59
+ expect(RegularExpression.new(expr, [], TransformLookup.new).source).to eq(expr)
60
+ end
61
+
62
+ def match(expression, text, types = [])
63
+ regular_expression = RegularExpression.new(expression, types, TransformLookup.new)
64
+ arguments = regular_expression.match(text)
65
+ return nil if arguments.nil?
66
+ arguments.map { |arg| arg.transformed_value }
67
+ end
68
+ end
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cucumber-expressions
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aslak Hellesøy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.12.5
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.12.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 10.5.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 10.5.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.7'
83
+ description: Cucumber Expressions - a simpler alternative to Regular Expressions
84
+ email: cukes@googlegroups.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".rspec"
90
+ - ".travis.yml"
91
+ - Gemfile
92
+ - Makefile
93
+ - README.md
94
+ - Rakefile
95
+ - cucumber-expressions.gemspec
96
+ - examples.txt
97
+ - lib/cucumber/cucumber_expressions/argument.rb
98
+ - lib/cucumber/cucumber_expressions/argument_matcher.rb
99
+ - lib/cucumber/cucumber_expressions/cucumber_expression.rb
100
+ - lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb
101
+ - lib/cucumber/cucumber_expressions/generated_expression.rb
102
+ - lib/cucumber/cucumber_expressions/regular_expression.rb
103
+ - lib/cucumber/cucumber_expressions/transform.rb
104
+ - lib/cucumber/cucumber_expressions/transform_lookup.rb
105
+ - lib/cucumber/cucumber_expressions/transform_matcher.rb
106
+ - spec/capture_warnings.rb
107
+ - spec/coverage.rb
108
+ - spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb
109
+ - spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb
110
+ - spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb
111
+ - spec/cucumber/cucumber_expressions/custom_transform_spec.rb
112
+ - spec/cucumber/cucumber_expressions/expression_examples_spec.rb
113
+ - spec/cucumber/cucumber_expressions/regular_expression_spec.rb
114
+ homepage: https://cucumber.io/
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options:
120
+ - "--charset=UTF-8"
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 1.9.3
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.5.1
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: cucumber-expressions-1.0.0
139
+ test_files:
140
+ - spec/capture_warnings.rb
141
+ - spec/coverage.rb
142
+ - spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb
143
+ - spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb
144
+ - spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb
145
+ - spec/cucumber/cucumber_expressions/custom_transform_spec.rb
146
+ - spec/cucumber/cucumber_expressions/expression_examples_spec.rb
147
+ - spec/cucumber/cucumber_expressions/regular_expression_spec.rb