cucumber-expressions 3.0.0 → 4.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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/.rsync +2 -0
  3. data/Makefile +5 -9
  4. data/cucumber-expressions.gemspec +1 -1
  5. data/examples.txt +7 -11
  6. data/lib/cucumber/cucumber_expressions/argument.rb +25 -5
  7. data/lib/cucumber/cucumber_expressions/combinatorial_generated_expression_factory.rb +40 -0
  8. data/lib/cucumber/cucumber_expressions/cucumber_expression.rb +8 -26
  9. data/lib/cucumber/cucumber_expressions/cucumber_expression_generator.rb +36 -19
  10. data/lib/cucumber/cucumber_expressions/errors.rb +40 -0
  11. data/lib/cucumber/cucumber_expressions/generated_expression.rb +23 -3
  12. data/lib/cucumber/cucumber_expressions/group.rb +66 -0
  13. data/lib/cucumber/cucumber_expressions/parameter_type.rb +38 -5
  14. data/lib/cucumber/cucumber_expressions/parameter_type_matcher.rb +4 -4
  15. data/lib/cucumber/cucumber_expressions/parameter_type_registry.rb +34 -54
  16. data/lib/cucumber/cucumber_expressions/regular_expression.rb +27 -27
  17. data/spec/cucumber/cucumber_expressions/combinatorial_generated_expression_factory_test.rb +43 -0
  18. data/spec/cucumber/cucumber_expressions/cucumber_expression_generator_spec.rb +10 -2
  19. data/spec/cucumber/cucumber_expressions/cucumber_expression_regexp_spec.rb +4 -11
  20. data/spec/cucumber/cucumber_expressions/cucumber_expression_spec.rb +42 -43
  21. data/spec/cucumber/cucumber_expressions/custom_parameter_type_spec.rb +109 -94
  22. data/spec/cucumber/cucumber_expressions/expression_examples_spec.rb +3 -3
  23. data/spec/cucumber/cucumber_expressions/group_spec.rb +34 -0
  24. data/spec/cucumber/cucumber_expressions/parameter_type_registry_spec.rb +86 -0
  25. data/spec/cucumber/cucumber_expressions/regular_expression_spec.rb +17 -25
  26. metadata +14 -5
  27. data/lib/cucumber/cucumber_expressions/argument_builder.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ec8564fdc53d2078266f3cfb82f7eb8db6e8957
4
- data.tar.gz: 2bb4c9234813e74029bd93c6448ff41ae1a85abb
3
+ metadata.gz: 3f97682c81c4fc810b23bb199a01196191dba087
4
+ data.tar.gz: 7c29ceea697976f3de194014a9ff039f25478ffe
5
5
  SHA512:
6
- metadata.gz: a3c592d0e6e4fad991e45fd27e1f54c466f418f204ba2f65f304c9f9400d9c5289b9472712e19a71637a71fedc762ccea4ef4bf3e00a7600d68dba2c02992dc4
7
- data.tar.gz: 26195cf45a0624e6920e44fb1a6c99e05300603ec053be5cb13f99df70d55e48a98ecdfda78fb085cbeadaddc394ddceb2149bb244399d1bfde199ad498550d0
6
+ metadata.gz: e3a91aeeae161ea22e6114c17ed13b3160eec652de6927cc45241772dd7c8121d620558339db2eb992652da21fa788efd6645636629b259888d23f24acd615f3
7
+ data.tar.gz: 1f2ee3c5d76189bdf6c52c5bf86a2d485524ecaa2e8207cfe83d91ee7fafb1dda8c9f9d515ce6b8ee3326f59aa3d52a422da38dcf45be9c16e34483904b5cb1d
data/.rsync ADDED
@@ -0,0 +1,2 @@
1
+ ../../LICENSE LICENSE
2
+ ../examples.txt examples.txt
data/Makefile CHANGED
@@ -1,12 +1,8 @@
1
- .PHONY: build
2
- build:
1
+ default:
3
2
  bundle install
4
3
  bundle exec rake
4
+ bundle exec rake install
5
+ .PHONY: default
5
6
 
6
- .PHONY: release
7
- release:
8
- bundle install
9
- bundle exec rake build release:guard_clean release:rubygem_push
10
- version=$$(cat *.gemspec | grep -m 1 ".version *= *" | sed "s/.*= *'\([^']*\)'.*/\1/"); \
11
- git tag --annotate v$$version --message "Release $$version"
12
- git push --tags
7
+ clean:
8
+ .PHONY: clean
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = 'cucumber-expressions'
4
- s.version = '3.0.0'
4
+ s.version = '4.0.0'
5
5
  s.authors = ["Aslak Hellesøy"]
6
6
  s.description = 'Cucumber Expressions - a simpler alternative to Regular Expressions'
7
7
  s.summary = "cucumber-expressions-#{s.version}"
data/examples.txt CHANGED
@@ -1,16 +1,12 @@
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 {int} cuke(s) in my {bodypart} now
6
- I have 1 cuke in my belly now
7
- [1,"belly"]
1
+ I have {int} cuke(s)
2
+ I have 22 cukes
3
+ [22]
8
4
  ---
9
- I have {int} cuke(s) and some \[]^$.|?*+ {something}
10
- I have 1 cuke and some \[]^$.|?*+ characters
11
- [1,"characters"]
5
+ I have {int} cuke(s) and some \[]^$.|?*+
6
+ I have 1 cuke and some \[]^$.|?*+
7
+ [1]
12
8
  ---
13
- /I have (\d+) cukes? in my (.+) now/
9
+ /I have (\d+) cukes? in my (\w+) now/
14
10
  I have 22 cukes in my belly now
15
11
  [22,"belly"]
16
12
  ---
@@ -1,14 +1,34 @@
1
+ require 'cucumber/cucumber_expressions/group'
2
+ require 'cucumber/cucumber_expressions/errors'
3
+
1
4
  module Cucumber
2
5
  module CucumberExpressions
3
6
  class Argument
4
- attr_reader :offset, :value
7
+ def self.build(regexp, text, parameter_types)
8
+ m = regexp.match(text)
9
+ return nil if m.nil?
10
+
11
+ match_group = Group.new(m)
12
+ arg_groups = match_group.children
13
+
14
+ if arg_groups.length != parameter_types.length
15
+ raise CucumberExpressionException.new(
16
+ "Expression has #{arg_groups.length} arguments, but there were #{parameter_types.length} parameter types"
17
+ )
18
+ end
19
+
20
+ parameter_types.zip(arg_groups).map do |parameter_type, arg_group|
21
+ Argument.new(arg_group, parameter_type)
22
+ end
23
+ end
5
24
 
6
- def initialize(offset, value, parameter_type)
7
- @offset, @value, @parameter_type = offset, value, parameter_type
25
+ def initialize(group, parameter_type)
26
+ raise "WTF" if Array === group
27
+ @group, @parameter_type = group, parameter_type
8
28
  end
9
29
 
10
- def transformed_value
11
- @parameter_type.transform(@value)
30
+ def value
31
+ @parameter_type.transform(@group ? @group.values : nil)
12
32
  end
13
33
  end
14
34
  end
@@ -0,0 +1,40 @@
1
+ require('cucumber/cucumber_expressions/generated_expression')
2
+
3
+ module Cucumber
4
+ module CucumberExpressions
5
+
6
+ class CombinatorialGeneratedExpressionFactory
7
+ def initialize(expression_template, parameter_type_combinations)
8
+ @expression_template = expression_template
9
+ @parameter_type_combinations = parameter_type_combinations
10
+ end
11
+
12
+ def generate_expressions
13
+ generated_expressions = []
14
+ generate_permutations(generated_expressions, 0, [])
15
+ generated_expressions
16
+ end
17
+
18
+ private
19
+
20
+ def generate_permutations(generated_expressions, depth, current_parameter_types)
21
+ if depth == @parameter_type_combinations.length
22
+ generated_expression = GeneratedExpression.new(@expression_template, current_parameter_types)
23
+ generated_expressions.push(generated_expression)
24
+ return
25
+ end
26
+
27
+ (0...@parameter_type_combinations[depth].length).each do |i|
28
+ new_current_parameter_types = current_parameter_types.dup # clone
29
+ new_current_parameter_types.push(@parameter_type_combinations[depth][i])
30
+ generate_permutations(
31
+ generated_expressions,
32
+ depth + 1,
33
+ new_current_parameter_types
34
+ )
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -1,20 +1,20 @@
1
- require 'cucumber/cucumber_expressions/argument_builder'
1
+ require 'cucumber/cucumber_expressions/argument'
2
2
  require 'cucumber/cucumber_expressions/parameter_type'
3
+ require 'cucumber/cucumber_expressions/errors'
3
4
 
4
5
  module Cucumber
5
6
  module CucumberExpressions
6
7
  class CucumberExpression
7
- PARAMETER_REGEXP = /\{([^}:]+)(:([^}]+))?}/
8
+ PARAMETER_REGEXP = /\{([^}]+)}/
8
9
  OPTIONAL_REGEXP = /\(([^)]+)\)/
9
10
  ALTERNATIVE_WORD_REGEXP = /([[:alpha:]]+)((\/[[:alpha:]]+)+)/
10
11
 
11
12
  attr_reader :source
12
13
 
13
- def initialize(expression, types, parameter_type_registry)
14
+ def initialize(expression, parameter_type_registry)
14
15
  @source = expression
15
16
  @parameter_types = []
16
17
  regexp = "^"
17
- type_index = 0
18
18
  match_offset = 0
19
19
 
20
20
  # Escape Does not include (){} because they have special meaning
@@ -31,28 +31,10 @@ module Cucumber
31
31
  match = PARAMETER_REGEXP.match(expression, match_offset)
32
32
  break if match.nil?
33
33
 
34
- parameter_name = match[1]
35
- parameter_type_name = match[3]
36
- if parameter_type_name
37
- $stderr.puts("Cucumber expression parameter syntax {#{parameter_name}:#{parameter_type_name}} is deprecated. Please use {#{parameter_type_name}} instead.")
38
- end
34
+ type_name = match[1]
39
35
 
40
- type = types.length <= type_index ? nil : types[type_index]
41
- type_index += 1
42
-
43
- parameter_type = nil
44
- if type
45
- parameter_type = parameter_type_registry.lookup_by_type(type)
46
- end
47
- if parameter_type.nil? && parameter_type_name
48
- parameter_type = parameter_type_registry.lookup_by_name(parameter_type_name)
49
- end
50
- if parameter_type.nil?
51
- parameter_type = parameter_type_registry.lookup_by_name(parameter_name)
52
- end
53
- if parameter_type.nil?
54
- parameter_type = parameter_type_registry.create_anonymous_lookup(lambda {|s| s})
55
- end
36
+ parameter_type = parameter_type_registry.lookup_by_type_name(type_name)
37
+ raise UndefinedParameterTypeError.new(type_name) if parameter_type.nil?
56
38
  @parameter_types.push(parameter_type)
57
39
 
58
40
  text = expression.slice(match_offset...match.offset(0)[0])
@@ -67,7 +49,7 @@ module Cucumber
67
49
  end
68
50
 
69
51
  def match(text)
70
- ArgumentBuilder.build_arguments(@regexp, text, @parameter_types)
52
+ Argument.build(@regexp, text, @parameter_types)
71
53
  end
72
54
 
73
55
  private
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/cucumber_expressions/parameter_type_matcher'
2
2
  require 'cucumber/cucumber_expressions/generated_expression'
3
+ require 'cucumber/cucumber_expressions/combinatorial_generated_expression_factory'
3
4
 
4
5
  module Cucumber
5
6
  module CucumberExpressions
@@ -9,12 +10,13 @@ module Cucumber
9
10
  end
10
11
 
11
12
  def generate_expression(text)
12
- parameter_names = []
13
- parameter_type_matchers = create_parameter_type_matchers(text)
14
- parameter_types = []
15
- usage_by_name = Hash.new(0)
13
+ generate_expressions(text)[0]
14
+ end
16
15
 
17
- expression = ""
16
+ def generate_expressions(text)
17
+ parameter_type_combinations = []
18
+ parameter_type_matchers = create_parameter_type_matchers(text)
19
+ expression_template = ""
18
20
  pos = 0
19
21
 
20
22
  loop do
@@ -29,14 +31,28 @@ module Cucumber
29
31
  if matching_parameter_type_matchers.any?
30
32
  matching_parameter_type_matchers = matching_parameter_type_matchers.sort
31
33
  best_parameter_type_matcher = matching_parameter_type_matchers[0]
32
- parameter_type = best_parameter_type_matcher.parameter
33
- parameter_types.push(parameter_type)
34
+ best_parameter_type_matchers = matching_parameter_type_matchers.select do |m|
35
+ (m <=> best_parameter_type_matcher).zero?
36
+ end
37
+
38
+ # Build a list of parameter types without duplicates. The reason there
39
+ # might be duplicates is that some parameter types have more than one regexp,
40
+ # which means multiple ParameterTypeMatcher objects will have a reference to the
41
+ # same ParameterType.
42
+ # We're sorting the list so prefer_for_regexp_match parameter types are listed first.
43
+ # Users are most likely to want these, so they should be listed at the top.
44
+ parameter_types = []
45
+ best_parameter_type_matchers.each do |parameter_type_matcher|
46
+ unless parameter_types.include?(parameter_type_matcher.parameter_type)
47
+ parameter_types.push(parameter_type_matcher.parameter_type)
48
+ end
49
+ end
50
+ parameter_types.sort!
34
51
 
35
- parameter_name = get_parameter_name(parameter_type.name, usage_by_name)
36
- parameter_names.push(parameter_name)
52
+ parameter_type_combinations.push(parameter_types)
37
53
 
38
- expression += text.slice(pos...best_parameter_type_matcher.start)
39
- expression += "{#{parameter_type.name}}"
54
+ expression_template += text.slice(pos...best_parameter_type_matcher.start)
55
+ expression_template += "{%s}"
40
56
 
41
57
  pos = best_parameter_type_matcher.start + best_parameter_type_matcher.group.length
42
58
  else
@@ -48,21 +64,22 @@ module Cucumber
48
64
  end
49
65
  end
50
66
 
51
- expression += text.slice(pos..-1)
52
- GeneratedExpression.new(expression, parameter_names, parameter_types)
67
+ expression_template += text.slice(pos..-1)
68
+
69
+ CombinatorialGeneratedExpressionFactory.new(
70
+ expression_template,
71
+ parameter_type_combinations
72
+ ).generate_expressions
53
73
  end
54
74
 
55
75
  private
56
76
 
57
- def get_parameter_name(name, usage_by_name)
58
- count = (usage_by_name[name] += 1)
59
- count == 1 ? name : "#{name}#{count}"
60
- end
61
-
62
77
  def create_parameter_type_matchers(text)
63
78
  parameter_matchers = []
64
79
  @parameter_type_registry.parameter_types.each do |parameter_type|
65
- parameter_matchers += create_parameter_type_matchers2(parameter_type, text)
80
+ if parameter_type.use_for_snippets?
81
+ parameter_matchers += create_parameter_type_matchers2(parameter_type, text)
82
+ end
66
83
  end
67
84
  parameter_matchers
68
85
  end
@@ -0,0 +1,40 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class CucumberExpressionError < StandardError
4
+ end
5
+
6
+ class UndefinedParameterTypeError < CucumberExpressionError
7
+ def initialize(type_name)
8
+ super("Undefined parameter type {#{type_name}}")
9
+ end
10
+ end
11
+
12
+ class AmbiguousParameterTypeError < CucumberExpressionError
13
+ def initialize(parameter_type_regexp, expression_regexp, parameter_types, generated_expressions)
14
+ super(<<-EOM)
15
+ Your Regular Expression /#{expression_regexp.source}/
16
+ matches multiple parameter types with regexp /#{parameter_type_regexp}/:
17
+ #{parameter_type_names(parameter_types)}
18
+
19
+ I couldn't decide which one to use. You have two options:
20
+
21
+ 1) Use a Cucumber Expression instead of a Regular Expression. Try one of these:
22
+ #{expressions(generated_expressions)}
23
+
24
+ 2) Make one of the parameter types prefer_for_regexp_match and continue to use a Regular Expression.
25
+
26
+ EOM
27
+ end
28
+
29
+ private
30
+
31
+ def parameter_type_names(parameter_types)
32
+ parameter_types.map{|p| "{#{p.name}}"}.join("\n ")
33
+ end
34
+
35
+ def expressions(generated_expressions)
36
+ generated_expressions.map{|ge| ge.source}.join("\n ")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,10 +1,30 @@
1
1
  module Cucumber
2
2
  module CucumberExpressions
3
3
  class GeneratedExpression
4
- attr_reader :source, :parameter_names, :parameter_types
4
+ attr_reader :parameter_types
5
5
 
6
- def initialize(source, parameter_names, parameters_types)
7
- @source, @parameter_names, @parameter_types = source, parameter_names, parameters_types
6
+ def initialize(expression_template, parameters_types)
7
+ @expression_template, @parameter_types = expression_template, parameters_types
8
+ end
9
+
10
+ def source
11
+ sprintf(@expression_template, *@parameter_types.map(&:name))
12
+ end
13
+
14
+ def parameter_names
15
+ usage_by_type_name = Hash.new(0)
16
+ @parameter_types.map do |t|
17
+ get_parameter_name(t.name, usage_by_type_name)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def get_parameter_name(type_name, usage_by_type_name)
24
+ count = usage_by_type_name[type_name]
25
+ count += 1
26
+ usage_by_type_name[type_name] = count
27
+ count == 1 ? type_name : "#{type_name}#{count}"
8
28
  end
9
29
  end
10
30
  end
@@ -0,0 +1,66 @@
1
+ module Cucumber
2
+ module CucumberExpressions
3
+ class Group
4
+ attr_reader :children, :start, :end, :value
5
+
6
+ def initialize(*args)
7
+ @children = []
8
+
9
+ if MatchData === args[0]
10
+ match_data = args[0]
11
+ parse(match_data)
12
+ else
13
+ @start = args[0] || -1
14
+ @end = args[1] || -1
15
+ @value = args[2]
16
+ end
17
+ end
18
+
19
+ def contains?(group)
20
+ group.null? || (group.start >= @start && group.end <= @end)
21
+ end
22
+
23
+ def add(group)
24
+ @children.push(group)
25
+ end
26
+
27
+ def null?
28
+ @value.nil?
29
+ end
30
+
31
+ def values
32
+ (children.empty? ? [self] : children).map(&:value)
33
+ end
34
+
35
+ private
36
+
37
+ def parse(match_data)
38
+ if match_data.length == 1
39
+ @start = @end = -1
40
+ @value = nil
41
+ return
42
+ end
43
+
44
+ @start = match_data.offset(0)[0]
45
+ @end = match_data.offset(0)[1]
46
+ @value = match_data[0]
47
+
48
+ stack = []
49
+ stack.push(self)
50
+
51
+ (1...match_data.length).each do |group_index|
52
+ group = Group.new(
53
+ match_data.offset(group_index)[0],
54
+ match_data.offset(group_index)[1],
55
+ match_data[group_index]
56
+ )
57
+ while !stack.last.contains?(group)
58
+ stack.pop
59
+ end
60
+ stack.last.add(group)
61
+ stack.push(group)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end