cucumber-expressions 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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