gherkin 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/History.txt +12 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +2 -2
  4. data/Rakefile +2 -2
  5. data/VERSION.yml +2 -2
  6. data/features/json_formatter.feature +3 -41
  7. data/features/step_definitions/gherkin_steps.rb +5 -6
  8. data/features/step_definitions/json_formatter_steps.rb +3 -2
  9. data/features/step_definitions/json_lexer_steps.rb +5 -5
  10. data/features/step_definitions/pretty_formatter_steps.rb +9 -13
  11. data/features/support/env.rb +1 -1
  12. data/lib/gherkin/formatter/argument.rb +1 -0
  13. data/lib/gherkin/formatter/filter_formatter.rb +149 -0
  14. data/lib/gherkin/formatter/json_formatter.rb +35 -45
  15. data/lib/gherkin/formatter/line_filter.rb +26 -0
  16. data/lib/gherkin/formatter/model.rb +85 -0
  17. data/lib/gherkin/formatter/pretty_formatter.rb +36 -39
  18. data/lib/gherkin/formatter/regexp_filter.rb +17 -0
  19. data/lib/gherkin/formatter/tag_count_formatter.rb +44 -0
  20. data/lib/gherkin/i18n.rb +5 -5
  21. data/lib/gherkin/i18n.yml +13 -0
  22. data/lib/gherkin/i18n_lexer.rb +2 -2
  23. data/lib/gherkin/{json_lexer.rb → json_parser.rb} +17 -5
  24. data/lib/gherkin/{parser → listener}/event.rb +1 -1
  25. data/lib/gherkin/{parser → listener}/formatter_listener.rb +30 -23
  26. data/lib/gherkin/native/java.rb +9 -1
  27. data/lib/gherkin/parser/parser.rb +27 -14
  28. data/lib/gherkin/rubify.rb +5 -1
  29. data/lib/gherkin/tag_expression.rb +62 -0
  30. data/lib/gherkin/tools/files.rb +3 -4
  31. data/lib/gherkin/tools/reformat.rb +2 -2
  32. data/lib/gherkin/tools/stats.rb +3 -4
  33. data/lib/gherkin/tools/stats_listener.rb +1 -1
  34. data/ragel/lexer.c.rl.erb +2 -3
  35. data/ragel/lexer.java.rl.erb +1 -2
  36. data/ragel/lexer.rb.rl.erb +1 -2
  37. data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
  38. data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
  39. data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
  40. data/spec/gherkin/formatter/filter_formatter_spec.rb +156 -0
  41. data/spec/gherkin/formatter/model_spec.rb +15 -0
  42. data/spec/gherkin/formatter/pretty_formatter_spec.rb +17 -16
  43. data/spec/gherkin/formatter/tag_count_formatter_spec.rb +31 -0
  44. data/spec/gherkin/i18n_lexer_spec.rb +3 -3
  45. data/spec/gherkin/i18n_spec.rb +2 -4
  46. data/spec/gherkin/{json_lexer_spec.rb → json_parser_spec.rb} +13 -8
  47. data/spec/gherkin/sexp_recorder.rb +10 -4
  48. data/spec/gherkin/shared/lexer_group.rb +0 -40
  49. data/spec/gherkin/shared/py_string_group.rb +0 -1
  50. data/spec/gherkin/shared/row_group.rb +1 -2
  51. data/spec/gherkin/tag_expression_spec.rb +137 -0
  52. data/spec/spec_helper.rb +5 -1
  53. data/tasks/bench.rake +5 -9
  54. metadata +35 -25
  55. data/lib/gherkin/parser/filter_listener.rb +0 -203
  56. data/lib/gherkin/parser/row.rb +0 -15
  57. data/lib/gherkin/parser/tag_expression.rb +0 -50
  58. data/spec/gherkin/parser/filter_listener_spec.rb +0 -397
  59. data/spec/gherkin/parser/formatter_listener_spec.rb +0 -134
  60. data/spec/gherkin/parser/parser_spec.rb +0 -50
  61. data/spec/gherkin/parser/tag_expression_spec.rb +0 -116
@@ -144,6 +144,19 @@
144
144
  then: "*|DEN"
145
145
  and: "*|AN"
146
146
  but: "*|BUT"
147
+ "en-pirate":
148
+ name: Pirate
149
+ native: Pirate
150
+ feature: Ahoy matey!
151
+ background: Yo-ho-ho
152
+ scenario: Heave to
153
+ scenario_outline: Shiver me timbers
154
+ examples: Dead men tell no tales
155
+ given: "*|Gangway!"
156
+ when: "*|Blimey!"
157
+ then: "*|Let go and haul"
158
+ and: "*|Aye"
159
+ but: "*|Avast!"
147
160
  "en-Scouse":
148
161
  name: Scouse
149
162
  native: Scouse
@@ -17,8 +17,8 @@ module Gherkin
17
17
  @force_ruby = force_ruby
18
18
  end
19
19
 
20
- def scan(source, uri, offset)
21
- create_delegate(source).scan(source, uri, offset)
20
+ def scan(source)
21
+ create_delegate(source).scan(source)
22
22
  end
23
23
 
24
24
  private
@@ -1,13 +1,27 @@
1
1
  require 'json'
2
+ require 'gherkin/listener/formatter_listener'
2
3
 
3
4
  module Gherkin
4
- class JSONLexer
5
+ class JSONParser
5
6
 
6
- def initialize(listener)
7
+ def initialize(formatter)
8
+ @formatter = formatter
9
+ end
10
+
11
+ def parse(src, feature_uri='unknown.json', line_offset=0)
12
+ @listener = Listener::FormatterListener.new(@formatter)
13
+ _parse(src, feature_uri, line_offset)
14
+ end
15
+
16
+ def parse_with_listener(src, listener)
7
17
  @listener = listener
18
+ _parse(src, 'unknown.json', 0)
8
19
  end
9
20
 
10
- def scan(src, uri='unknown.json', offset=0)
21
+ private
22
+
23
+ def _parse(src, feature_uri, line_offset)
24
+ @listener.location(feature_uri)
11
25
  feature = JSON.parse(src)
12
26
 
13
27
  comments_for(feature)
@@ -27,8 +41,6 @@ module Gherkin
27
41
  @listener.eof
28
42
  end
29
43
 
30
- private
31
-
32
44
  def parse_element(feature_element)
33
45
  comments_for(feature_element)
34
46
  tags_for(feature_element)
@@ -1,5 +1,5 @@
1
1
  module Gherkin
2
- module Parser
2
+ module Listener
3
3
  class Event < Array
4
4
  def event
5
5
  self[0]
@@ -1,8 +1,8 @@
1
1
  require 'gherkin/native'
2
- require 'gherkin/parser/row'
2
+ require 'gherkin/formatter/model'
3
3
 
4
4
  module Gherkin
5
- module Parser
5
+ module Listener
6
6
  # Adapter from the "raw" Gherkin <tt>Listener</tt> API
7
7
  # to the slightly more high-level <tt>Formatter</tt> API,
8
8
  # which is easier to implement (less state to keep track of).
@@ -16,54 +16,53 @@ module Gherkin
16
16
  @table = nil
17
17
  end
18
18
 
19
- def location(uri, offset)
20
- @uri = uri
21
- @offset = offset
19
+ def location(feature_uri)
20
+ @feature_uri = feature_uri
22
21
  end
23
22
 
24
- def comment(content, line)
25
- @comments << content
23
+ def comment(value, line)
24
+ @comments << Formatter::Model::Comment.new(value, line)
26
25
  end
27
26
 
28
27
  def tag(name, line)
29
- @tags << name
28
+ @tags << Formatter::Model::Tag.new(name, line)
30
29
  end
31
30
 
32
31
  def feature(keyword, name, description, line)
33
- @formatter.feature(grab_comments!, grab_tags!, keyword, name, description, @uri)
32
+ @formatter.feature(statement(grab_comments!, grab_tags!, keyword, name, description, line), @feature_uri)
34
33
  end
35
34
 
36
35
  def background(keyword, name, description, line)
37
- @formatter.background(grab_comments!, keyword, name, description, line)
36
+ @formatter.background(statement(grab_comments!, [], keyword, name, description, line))
38
37
  end
39
38
 
40
39
  def scenario(keyword, name, description, line)
41
40
  replay_step_or_examples
42
- @formatter.scenario(grab_comments!, grab_tags!, keyword, name, description, line)
41
+ @formatter.scenario(statement(grab_comments!, grab_tags!, keyword, name, description, line))
43
42
  end
44
43
 
45
44
  def scenario_outline(keyword, name, description, line)
46
45
  replay_step_or_examples
47
- @formatter.scenario_outline(grab_comments!, grab_tags!, keyword, name, description, line)
46
+ @formatter.scenario_outline(statement(grab_comments!, grab_tags!, keyword, name, description, line))
48
47
  end
49
48
 
50
49
  def examples(keyword, name, description, line)
51
50
  replay_step_or_examples
52
- @examples = [grab_comments!, grab_tags!, keyword, name, description, line]
51
+ @examples_statement = statement(grab_comments!, grab_tags!, keyword, name, description, line)
53
52
  end
54
53
 
55
54
  def step(keyword, name, line)
56
55
  replay_step_or_examples
57
- @step = [grab_comments!, keyword, name, line]
56
+ @step_statement = statement(grab_comments!, [], keyword, name, nil, line)
58
57
  end
59
58
 
60
59
  def row(cells, line)
61
60
  @table ||= []
62
- @table << Row.new(cells, grab_comments!, line)
61
+ @table << Formatter::Model::Row.new(grab_comments!, cells, line)
63
62
  end
64
63
 
65
- def py_string(py_string, line)
66
- @py_string = py_string
64
+ def py_string(string, line)
65
+ @py_string = Formatter::Model::PyString.new(string, line)
67
66
  end
68
67
 
69
68
  def eof
@@ -71,8 +70,16 @@ module Gherkin
71
70
  @formatter.eof
72
71
  end
73
72
 
73
+ def syntax_error(state, ev, legal_events, uri, line)
74
+ @formatter.syntax_error(state, ev, legal_events, uri, line)
75
+ end
76
+
74
77
  private
75
78
 
79
+ def statement(comments, tags, keyword, name, description, line)
80
+ Formatter::Model::Statement.new(comments, tags, keyword, name, description, line)
81
+ end
82
+
76
83
  def grab_comments!
77
84
  comments = @comments
78
85
  @comments = []
@@ -98,14 +105,14 @@ module Gherkin
98
105
  end
99
106
 
100
107
  def replay_step_or_examples
101
- if(@step)
108
+ if(@step_statement)
102
109
  multiline_arg = grab_py_string! || grab_table!
103
- @formatter.step(*(@step + [multiline_arg, nil, nil, nil, nil]))
104
- @step = nil
110
+ @formatter.step(@step_statement, multiline_arg, nil)
111
+ @step_statement = nil
105
112
  end
106
- if(@examples)
107
- @formatter.examples(*(@examples + [grab_table!]))
108
- @examples = nil
113
+ if(@examples_statement)
114
+ @formatter.examples(@examples_statement, grab_table!)
115
+ @examples_statement = nil
109
116
  end
110
117
  end
111
118
  end
@@ -26,7 +26,15 @@ class Class
26
26
  end
27
27
 
28
28
  def new(*args)
29
- java_class.new(*javaify(args))
29
+ begin
30
+ java_class.new(*javaify(args))
31
+ rescue ArgumentError => e
32
+ e.message << "\n#{java_class.name}"
33
+ raise e
34
+ rescue NameError => e
35
+ e.message << "\n args: #{args.inspect}"
36
+ raise e
37
+ end
30
38
  end
31
39
 
32
40
  def ===(object)
@@ -1,4 +1,5 @@
1
1
  require 'gherkin/native'
2
+ require 'gherkin/listener/formatter_listener'
2
3
 
3
4
  module Gherkin
4
5
  module Parser
@@ -12,24 +13,36 @@ module Gherkin
12
13
  native_impl('gherkin')
13
14
 
14
15
  # Initialize the parser. +machine_name+ refers to a state machine table.
15
- def initialize(listener, raise_on_error=true, machine_name='root')
16
- @listener = listener
16
+ def initialize(formatter, raise_on_error=true, machine_name='root', force_ruby=false)
17
+ @formatter = formatter
18
+ @listener = Listener::FormatterListener.new(@formatter)
17
19
  @raise_on_error = raise_on_error
18
- @machines = []
19
20
  @machine_name = machine_name
21
+ @machines = []
20
22
  push_machine(@machine_name)
23
+ @lexer = I18nLexer.new(self, force_ruby)
24
+ end
25
+
26
+ def parse(gherkin, feature_uri, line_offset)
27
+ @feature_uri = feature_uri
28
+ @line_offset = line_offset
29
+ @listener.location(feature_uri)
30
+ @lexer.scan(gherkin)
21
31
  end
22
32
 
23
- def location(uri, offset)
24
- @listener.location(uri, offset)
33
+ def i18n_language
34
+ @lexer.i18n_language
35
+ end
36
+
37
+ def errors
38
+ @lexer.errors
25
39
  end
26
40
 
27
41
  # Doesn't yet fall back to super
28
42
  def method_missing(method, *args)
29
43
  # TODO: Catch exception and call super
30
- if(event(method.to_s, args[-1]))
31
- @listener.__send__(method, *args)
32
- end
44
+ event(method.to_s, args[-1])
45
+ @listener.__send__(method, *args)
33
46
  if method == :eof
34
47
  pop_machine
35
48
  push_machine(@machine_name)
@@ -37,15 +50,15 @@ module Gherkin
37
50
  end
38
51
 
39
52
  def event(ev, line)
40
- machine.event(ev, line) do |state, expected|
53
+ l = line ? @line_offset+line : nil
54
+ machine.event(ev, l) do |state, legal_events|
41
55
  if @raise_on_error
42
- raise ParseError.new(state, ev, expected, line)
56
+ raise ParseError.new(state, ev, legal_events, @feature_uri, l)
43
57
  else
44
- @listener.syntax_error(state, ev, expected, line)
45
- return false
58
+ # Only used for testing
59
+ @listener.syntax_error(state, ev, legal_events, @feature_uri, l)
46
60
  end
47
61
  end
48
- true
49
62
  end
50
63
 
51
64
  def push_machine(name)
@@ -122,7 +135,7 @@ module Gherkin
122
135
  state_machine_reader = StateMachineReader.new
123
136
  lexer = Gherkin::I18n.new('en').lexer(state_machine_reader)
124
137
  machine = File.dirname(__FILE__) + "/#{name}.txt"
125
- lexer.scan(File.read(machine), machine, 0)
138
+ lexer.scan(File.read(machine))
126
139
  state_machine_reader.rows
127
140
  end
128
141
 
@@ -5,8 +5,12 @@ module Gherkin
5
5
  # This is especially important to convert java.util.List coming
6
6
  # from Java and back to a Ruby Array.
7
7
  def rubify(o)
8
- if Java.java.util.Collection === o || Array === o
8
+ case(o)
9
+ when Java.java.util.Collection, Array
9
10
  o.map{|e| rubify(e)}
11
+ when Java.gherkin.formatter.model.PyString
12
+ require 'gherkin/formatter/model'
13
+ Formatter::Model::PyString.new(o.value, o.line)
10
14
  else
11
15
  o
12
16
  end
@@ -0,0 +1,62 @@
1
+ require 'gherkin/native'
2
+
3
+ module Gherkin
4
+ class TagExpression
5
+ native_impl('gherkin')
6
+
7
+ attr_reader :limits
8
+
9
+ def initialize(tag_expressions)
10
+ @ands = []
11
+ @limits = {}
12
+ tag_expressions.each do |expr|
13
+ add(expr.strip.split(/\s*,\s*/))
14
+ end
15
+ end
16
+
17
+ def empty?
18
+ @ands.empty?
19
+ end
20
+
21
+ def eval(tags)
22
+ return true if @ands.flatten.empty?
23
+ vars = Hash[*tags.map{|tag| [tag, true]}.flatten]
24
+ !!Kernel.eval(ruby_expression)
25
+ end
26
+
27
+ private
28
+
29
+ def add(tags_with_negation_and_limits)
30
+ negatives, positives = tags_with_negation_and_limits.partition{|tag| tag =~ /^~/}
31
+ @ands << (store_and_extract_limits(negatives, true) + store_and_extract_limits(positives, false))
32
+ end
33
+
34
+ def store_and_extract_limits(tags_with_negation_and_limits, negated)
35
+ tags_with_negation = []
36
+ tags_with_negation_and_limits.each do |tag_with_negation_and_limit|
37
+ tag_with_negation, limit = tag_with_negation_and_limit.split(':')
38
+ tags_with_negation << tag_with_negation
39
+ if limit
40
+ tag_without_negation = negated ? tag_with_negation[1..-1] : tag_with_negation
41
+ if @limits[tag_without_negation] && @limits[tag_without_negation] != limit.to_i
42
+ raise "Inconsistent tag limits for #{tag_without_negation}: #{@limits[tag_without_negation]} and #{limit.to_i}"
43
+ end
44
+ @limits[tag_without_negation] = limit.to_i
45
+ end
46
+ end
47
+ tags_with_negation
48
+ end
49
+
50
+ def ruby_expression
51
+ "(" + @ands.map do |ors|
52
+ ors.map do |tag|
53
+ if tag =~ /^~(.*)/
54
+ "!vars['#{$1}']"
55
+ else
56
+ "vars['#{tag}']"
57
+ end
58
+ end.join("||")
59
+ end.join(")&&(") + ")"
60
+ end
61
+ end
62
+ end
@@ -20,11 +20,10 @@ module Gherkin
20
20
  Dir[*globs].uniq.sort.each(&proc)
21
21
  end
22
22
 
23
- def scan(file, listener)
24
- parser = Gherkin::Parser::Parser.new(listener, true, "root")
25
- lexer = Gherkin::I18nLexer.new(parser, false)
23
+ def scan(file, formatter)
24
+ parser = Gherkin::Parser::Parser.new(formatter, true, "root")
26
25
  begin
27
- lexer.scan(IO.read(file), file, 0)
26
+ parser.parse(IO.read(file), file, 0)
28
27
  rescue => e
29
28
  e.message << " (#{file})"
30
29
  raise e
@@ -1,7 +1,7 @@
1
1
  require 'stringio'
2
2
  require 'gherkin/tools/files'
3
3
  require 'gherkin/formatter/pretty_formatter'
4
- require 'gherkin/parser/formatter_listener'
4
+ require 'gherkin/listener/formatter_listener'
5
5
 
6
6
  module Gherkin
7
7
  module Tools
@@ -10,7 +10,7 @@ module Gherkin
10
10
  each do |file|
11
11
  io = defined?(JRUBY_VERSION) ? Java.java.io.StringWriter.new : StringIO.new
12
12
  formatter = Formatter::PrettyFormatter.new(io, true)
13
- listener = Parser::FormatterListener.new(formatter)
13
+ listener = Listener::FormatterListener.new(formatter)
14
14
  scan(file, listener)
15
15
  string = defined?(JRUBY_VERSION) ? io.getBuffer.toString : io.string
16
16
  File.open(file, 'w') {|io| io.write(string)}
@@ -6,11 +6,10 @@ module Gherkin
6
6
  module Tools
7
7
  class Stats < Files
8
8
  def run
9
- listener = StatsListener.new
9
+ formatter = StatsFormatter.new
10
10
  each do |f|
11
- parser = Gherkin::Parser::Parser.new(listener, true)
12
- lexer = Gherkin::I18nLexer.new(parser)
13
- lexer.scan(IO.read(f), f, 0)
11
+ parser = Gherkin::Parser::Parser.new(formatter, true)
12
+ parser.parse(IO.read(f), f, 0)
14
13
  end
15
14
  puts "Features: #{listener.features}"
16
15
  puts "Scenarios: #{listener.scenarios}"
@@ -50,7 +50,7 @@ module Gherkin
50
50
  def py_string(string, line)
51
51
  end
52
52
 
53
- def syntax_error(state, event, legal_events, line)
53
+ def syntax_error(state, event, legal_events, uri, line)
54
54
  end
55
55
 
56
56
  def eof
@@ -389,10 +389,9 @@ static VALUE CLexer_init(VALUE self, VALUE listener)
389
389
  return self;
390
390
  }
391
391
 
392
- static VALUE CLexer_scan(VALUE self, VALUE input, VALUE uri, VALUE offset)
392
+ static VALUE CLexer_scan(VALUE self, VALUE input)
393
393
  {
394
394
  VALUE listener = rb_iv_get(self, "@listener");
395
- rb_funcall(listener, rb_intern("location"), 2, uri, offset);
396
395
 
397
396
  lexer_state *lexer = NULL;
398
397
  DATA_GET(self, lexer_state, lexer);
@@ -449,6 +448,6 @@ void Init_gherkin_lexer_<%= @i18n.underscored_iso_code %>()
449
448
  cI18nLexer = rb_define_class_under(mCLexer, "<%= @i18n.underscored_iso_code.capitalize %>", rb_cObject);
450
449
  rb_define_alloc_func(cI18nLexer, CLexer_alloc);
451
450
  rb_define_method(cI18nLexer, "initialize", CLexer_init, 1);
452
- rb_define_method(cI18nLexer, "scan", CLexer_scan, 3);
451
+ rb_define_method(cI18nLexer, "scan", CLexer_scan, 1);
453
452
  }
454
453