cucumber-expressions 3.0.0 → 4.0.0

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