gherkin 1.0.30 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.rspec +1 -0
  2. data/History.txt +19 -0
  3. data/Rakefile +4 -4
  4. data/VERSION.yml +2 -2
  5. data/features/feature_parser.feature +11 -0
  6. data/features/json_formatter.feature +238 -0
  7. data/features/pretty_formatter.feature +9 -0
  8. data/features/step_definitions/gherkin_steps.rb +1 -1
  9. data/features/step_definitions/json_formatter_steps.rb +32 -0
  10. data/features/step_definitions/pretty_formatter_steps.rb +24 -23
  11. data/features/support/env.rb +3 -3
  12. data/lib/gherkin/formatter/json_formatter.rb +82 -0
  13. data/lib/gherkin/formatter/pretty_formatter.rb +73 -78
  14. data/lib/gherkin/i18n.rb +22 -18
  15. data/lib/gherkin/i18n.yml +9 -9
  16. data/lib/gherkin/i18n_lexer.rb +2 -2
  17. data/lib/gherkin/parser/event.rb +6 -6
  18. data/lib/gherkin/parser/filter_listener.rb +5 -1
  19. data/lib/gherkin/parser/formatter_listener.rb +113 -0
  20. data/lib/gherkin/parser/json_parser.rb +102 -0
  21. data/lib/gherkin/parser/parser.rb +10 -2
  22. data/lib/gherkin/parser/row.rb +15 -0
  23. data/lib/gherkin/rubify.rb +2 -0
  24. data/lib/gherkin/tools/files.rb +1 -1
  25. data/lib/gherkin/tools/reformat.rb +1 -2
  26. data/lib/gherkin/tools/stats.rb +1 -1
  27. data/lib/gherkin/tools/stats_listener.rb +5 -5
  28. data/ragel/lexer.c.rl.erb +41 -12
  29. data/ragel/lexer.java.rl.erb +26 -17
  30. data/ragel/lexer.rb.rl.erb +10 -5
  31. data/ragel/lexer_common.rl.erb +6 -6
  32. data/spec/gherkin/c_lexer_spec.rb +2 -2
  33. data/spec/gherkin/fixtures/complex.js +105 -0
  34. data/spec/gherkin/formatter/argument_spec.rb +3 -3
  35. data/spec/gherkin/formatter/colors_spec.rb +3 -4
  36. data/spec/gherkin/formatter/pretty_formatter_spec.rb +21 -50
  37. data/spec/gherkin/i18n_lexer_spec.rb +6 -6
  38. data/spec/gherkin/i18n_spec.rb +16 -9
  39. data/spec/gherkin/java_lexer_spec.rb +1 -2
  40. data/spec/gherkin/output_stream_string_io.rb +24 -0
  41. data/spec/gherkin/parser/filter_listener_spec.rb +16 -9
  42. data/spec/gherkin/parser/formatter_listener_spec.rb +134 -0
  43. data/spec/gherkin/parser/json_parser_spec.rb +129 -0
  44. data/spec/gherkin/parser/parser_spec.rb +9 -9
  45. data/spec/gherkin/parser/tag_expression_spec.rb +8 -8
  46. data/spec/gherkin/rb_lexer_spec.rb +1 -1
  47. data/spec/gherkin/sexp_recorder.rb +21 -1
  48. data/spec/gherkin/shared/{lexer_spec.rb → lexer_group.rb} +172 -102
  49. data/spec/gherkin/shared/{py_string_spec.rb → py_string_group.rb} +21 -17
  50. data/spec/gherkin/shared/{row_spec.rb → row_group.rb} +36 -19
  51. data/spec/gherkin/shared/{tags_spec.rb → tags_group.rb} +13 -9
  52. data/spec/spec_helper.rb +18 -38
  53. data/tasks/bench.rake +3 -3
  54. data/tasks/compile.rake +13 -14
  55. data/tasks/rspec.rake +6 -11
  56. metadata +42 -28
  57. data/features/pretty_printer.feature +0 -14
  58. data/spec/gherkin/csharp_lexer_spec.rb +0 -20
@@ -1,11 +1,6 @@
1
1
  module Gherkin
2
2
  module Parser
3
3
  class Event < Array
4
- def initialize(*args)
5
- super
6
- self[1] = self[1].to_a if event == :row # Special JRuby handling
7
- end
8
-
9
4
  def event
10
5
  self[0]
11
6
  end
@@ -24,7 +19,12 @@ module Gherkin
24
19
  end
25
20
 
26
21
  def replay(listener)
27
- listener.__send__(event, *args)
22
+ begin
23
+ listener.__send__(event, *args)
24
+ rescue ArgumentError => e
25
+ e.message << "\nListener: #{listener.class}, args: #{args.inspect}"
26
+ raise e
27
+ end
28
28
  end
29
29
 
30
30
  private
@@ -31,7 +31,11 @@ module Gherkin
31
31
 
32
32
  @table_state = :step
33
33
  end
34
-
34
+
35
+ def location(uri, offset)
36
+ @listener.location(uri, offset)
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def method_missing(*event_args)
@@ -0,0 +1,113 @@
1
+ require 'gherkin/native'
2
+ require 'gherkin/parser/row'
3
+
4
+ module Gherkin
5
+ module Parser
6
+ # Adapter from the "raw" Gherkin <tt>Listener</tt> API
7
+ # to the slightly more high-level <tt>Formatter</tt> API,
8
+ # which is easier to implement (less state to keep track of).
9
+ class FormatterListener
10
+ native_impl('gherkin')
11
+
12
+ def initialize(formatter)
13
+ @formatter = formatter
14
+ @comments = []
15
+ @tags = []
16
+ @table = nil
17
+ end
18
+
19
+ def location(uri, offset)
20
+ @uri = uri
21
+ @offset = offset
22
+ end
23
+
24
+ def comment(content, line)
25
+ @comments << content
26
+ end
27
+
28
+ def tag(name, line)
29
+ @tags << name
30
+ end
31
+
32
+ def feature(keyword, name, description, line)
33
+ @formatter.feature(grab_comments!, grab_tags!, keyword, name, description, @uri)
34
+ end
35
+
36
+ def background(keyword, name, description, line)
37
+ @formatter.background(grab_comments!, keyword, name, description, line)
38
+ end
39
+
40
+ def scenario(keyword, name, description, line)
41
+ replay_step_or_examples
42
+ @formatter.scenario(grab_comments!, grab_tags!, keyword, name, description, line)
43
+ end
44
+
45
+ def scenario_outline(keyword, name, description, line)
46
+ replay_step_or_examples
47
+ @formatter.scenario_outline(grab_comments!, grab_tags!, keyword, name, description, line)
48
+ end
49
+
50
+ def examples(keyword, name, description, line)
51
+ replay_step_or_examples
52
+ @examples = [grab_comments!, grab_tags!, keyword, name, description, line]
53
+ end
54
+
55
+ def step(keyword, name, line)
56
+ replay_step_or_examples
57
+ @step = [grab_comments!, keyword, name, line]
58
+ end
59
+
60
+ def row(cells, line)
61
+ @table ||= []
62
+ @table << Row.new(cells, grab_comments!, line)
63
+ end
64
+
65
+ def py_string(py_string, line)
66
+ @py_string = py_string
67
+ end
68
+
69
+ def eof
70
+ replay_step_or_examples
71
+ @formatter.eof
72
+ end
73
+
74
+ private
75
+
76
+ def grab_comments!
77
+ comments = @comments
78
+ @comments = []
79
+ comments
80
+ end
81
+
82
+ def grab_tags!
83
+ tags = @tags
84
+ @tags = []
85
+ tags
86
+ end
87
+
88
+ def grab_table!
89
+ table = @table
90
+ @table = nil
91
+ table
92
+ end
93
+
94
+ def grab_py_string!
95
+ py_string = @py_string
96
+ @py_string = nil
97
+ py_string
98
+ end
99
+
100
+ def replay_step_or_examples
101
+ if(@step)
102
+ multiline_arg = grab_py_string! || grab_table!
103
+ @formatter.step(*(@step + [multiline_arg, nil, nil, nil, nil]))
104
+ @step = nil
105
+ end
106
+ if(@examples)
107
+ @formatter.examples(*(@examples + [grab_table!]))
108
+ @examples = nil
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,102 @@
1
+ require 'json'
2
+ require 'gherkin/i18n'
3
+
4
+ module Gherkin
5
+ module Parser
6
+ class JSONParser
7
+ attr_reader :i18n_language
8
+
9
+ def initialize(listener)
10
+ @listener = listener
11
+ end
12
+
13
+ def parse(src)
14
+ feature = JSON.parse(src)
15
+
16
+ @i18n_language = Gherkin::I18n.get(feature["language"] || "en" )
17
+
18
+ tags_for(feature)
19
+ @listener.feature(keyword_for("feature", feature), feature["name"], line_for(feature)) if feature["name"]
20
+
21
+ if feature["background"]
22
+ @listener.background(keyword_for("background", feature["background"]), feature["background"]["name"], line_for(feature["background"]))
23
+ steps_for(feature["background"])
24
+ end
25
+
26
+ feature["elements"].each do |feature_element|
27
+ parse_element(feature_element)
28
+ end if feature["elements"]
29
+
30
+ @listener.eof
31
+ end
32
+
33
+ private
34
+
35
+ def parse_element(feature_element)
36
+ case feature_element["type"]
37
+ when "Scenario" then parse_scenario(feature_element)
38
+ when "Scenario Outline" then parse_outline(feature_element)
39
+ end
40
+ end
41
+
42
+ def parse_outline(scenario_outline)
43
+ tags_for(scenario_outline)
44
+ @listener.scenario_outline(keyword_for("scenario_outline", scenario_outline), scenario_outline["name"], line_for(scenario_outline) )
45
+ steps_for(scenario_outline)
46
+ scenario_outline["examples"].each do |examples|
47
+ tags_for(examples)
48
+ @listener.examples(keyword_for("examples", examples), examples["name"], line_for(examples))
49
+ rows_for(examples)
50
+ end
51
+ end
52
+
53
+ def parse_scenario(scenario)
54
+ tags_for(scenario)
55
+ @listener.scenario(keyword_for("scenario", scenario), scenario["name"], line_for(scenario))
56
+ steps_for(scenario)
57
+ end
58
+
59
+ def tags_for(element)
60
+ element["tags"].each do |tag|
61
+ @listener.tag(tag, 0)
62
+ end if element["tags"]
63
+ end
64
+
65
+ def steps_for(element)
66
+ element["steps"].each do |step|
67
+ @listener.step(keyword_for("given",step), step["name"], line_for(step))
68
+ py_string_for(step)
69
+ rows_for(step)
70
+ end
71
+ end
72
+
73
+ def py_string_for(element)
74
+ @listener.py_string(element["py_string"], 0) if element["py_string"]
75
+ end
76
+
77
+ def rows_for(element)
78
+ element["table"].each do |row|
79
+ @listener.row(cells_for(row), 0)
80
+ end if element["table"]
81
+ end
82
+
83
+ def cells_for(row)
84
+ row["cells"].inject([]) { |col, ele| col << ele["text"] }
85
+ end
86
+
87
+ def line_for(element)
88
+ if element["line"]
89
+ element["line"]
90
+ elsif element["file_colon_line"]
91
+ element["file_colon_line"].split(':')[1].to_i
92
+ else
93
+ 0
94
+ end
95
+ end
96
+
97
+ def keyword_for(gherkin_keyword, element)
98
+ element["keyword"] || i18n_language.keywords(gherkin_keyword).reject { |kw| kw == "* " }.first
99
+ end
100
+ end
101
+ end
102
+ end
@@ -20,11 +20,15 @@ module Gherkin
20
20
  push_machine(@machine_name)
21
21
  end
22
22
 
23
+ def location(uri, offset)
24
+ @listener.location(uri, offset)
25
+ end
26
+
23
27
  # Doesn't yet fall back to super
24
28
  def method_missing(method, *args)
25
29
  # TODO: Catch exception and call super
26
30
  if(event(method.to_s, args[-1]))
27
- @listener.send(method, *args)
31
+ @listener.__send__(method, *args)
28
32
  end
29
33
  if method == :eof
30
34
  pop_machine
@@ -117,7 +121,8 @@ module Gherkin
117
121
  def transition_table(name)
118
122
  state_machine_reader = StateMachineReader.new
119
123
  lexer = Gherkin::I18n.new('en').lexer(state_machine_reader)
120
- lexer.scan(File.read(File.dirname(__FILE__) + "/#{name}.txt"))
124
+ machine = File.dirname(__FILE__) + "/#{name}.txt"
125
+ lexer.scan(File.read(machine), machine, 0)
121
126
  state_machine_reader.rows
122
127
  end
123
128
 
@@ -128,6 +133,9 @@ module Gherkin
128
133
  @rows = []
129
134
  end
130
135
 
136
+ def location(uri, offset)
137
+ end
138
+
131
139
  def row(row, line_number)
132
140
  @rows << row
133
141
  end
@@ -0,0 +1,15 @@
1
+ require 'gherkin/native'
2
+
3
+ module Gherkin
4
+ module Parser
5
+ class Row
6
+ native_impl('gherkin')
7
+
8
+ attr_reader :cells, :comments, :line
9
+
10
+ def initialize(cells, comments, line)
11
+ @cells, @comments, @line = cells, comments, line
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,6 +2,8 @@ module Gherkin
2
2
  module Rubify
3
3
  if defined?(JRUBY_VERSION)
4
4
  # Translate Java objects to Ruby.
5
+ # This is especially important to convert java.util.List coming
6
+ # from Java and back to a Ruby Array.
5
7
  def rubify(o)
6
8
  if Java.java.util.Collection === o || Array === o
7
9
  o.map{|e| rubify(e)}
@@ -24,7 +24,7 @@ module Gherkin
24
24
  parser = Gherkin::Parser::Parser.new(listener, true, "root")
25
25
  lexer = Gherkin::I18nLexer.new(parser, false)
26
26
  begin
27
- lexer.scan(IO.read(file))
27
+ lexer.scan(IO.read(file), file, 0)
28
28
  rescue => e
29
29
  e.message << " (#{file})"
30
30
  raise e
@@ -10,8 +10,7 @@ module Gherkin
10
10
  purdy = StringIO.new
11
11
  listener = Formatter::PrettyFormatter.new(purdy)
12
12
  scan(file, listener)
13
- purdy.rewind
14
- File.open(file, 'w') {|io| io.write(purdy.read)}
13
+ File.open(file, 'w') {|io| io.write(purdy.string)}
15
14
  end
16
15
  end
17
16
  end
@@ -10,7 +10,7 @@ module Gherkin
10
10
  each do |f|
11
11
  parser = Gherkin::Parser::Parser.new(listener, true)
12
12
  lexer = Gherkin::I18nLexer.new(parser)
13
- lexer.scan(IO.read(f))
13
+ lexer.scan(IO.read(f), f, 0)
14
14
  end
15
15
  puts "Features: #{listener.features}"
16
16
  puts "Scenarios: #{listener.scenarios}"
@@ -20,21 +20,21 @@ module Gherkin
20
20
  def comment(content, line)
21
21
  end
22
22
 
23
- def feature(keyword, name, line)
23
+ def feature(keyword, name, description, line)
24
24
  @features += 1
25
25
  end
26
26
 
27
- def background(keyword, name, line)
27
+ def background(keyword, name, description, line)
28
28
  end
29
29
 
30
- def scenario(keyword, name, line)
30
+ def scenario(keyword, name, description, line)
31
31
  @scenarios += 1
32
32
  end
33
33
 
34
- def scenario_outline(keyword, name, line)
34
+ def scenario_outline(keyword, name, description, line)
35
35
  end
36
36
 
37
- def examples(keyword, name, line)
37
+ def examples(keyword, name, description, line)
38
38
  end
39
39
 
40
40
  def step(keyword, name, line)
@@ -66,7 +66,7 @@ static VALUE rb_eGherkinLexingError;
66
66
  #define PTR_TO(P) (data + lexer->P)
67
67
 
68
68
  #define STORE_KW_END_CON(EVENT) \
69
- store_kw_con(listener, # EVENT, \
69
+ store_multiline_kw_con(listener, # EVENT, \
70
70
  PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end - 1)), \
71
71
  PTR_TO(content_start), LEN(content_start, PTR_TO(content_end)), \
72
72
  lexer->current_line, lexer->eol); \
@@ -129,7 +129,7 @@ static VALUE rb_eGherkinLexingError;
129
129
  store_kw_con(listener, "step",
130
130
  PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end)),
131
131
  PTR_TO(content_start), LEN(content_start, p),
132
- lexer->current_line, lexer->eol);
132
+ lexer->current_line);
133
133
  }
134
134
 
135
135
  action store_comment_content {
@@ -251,31 +251,59 @@ strip_i(VALUE str, VALUE ary)
251
251
  }
252
252
 
253
253
  static VALUE
254
- multiline_strip(VALUE text, int eol)
254
+ multiline_strip(VALUE text)
255
255
  {
256
256
  VALUE map = rb_ary_new();
257
257
  VALUE split = rb_str_split(text, "\n");
258
258
 
259
259
  rb_iterate(rb_each, split, strip_i, map);
260
260
 
261
- return rb_ary_join(split, rb_str_new2( \
262
- eol == CRLF_FLAG ? CRLF : LF ));
261
+ return split;
263
262
  }
264
263
 
265
264
  static void
266
265
  store_kw_con(VALUE listener, const char * event_name,
267
266
  const char * keyword_at, size_t keyword_length,
268
267
  const char * at, size_t length,
269
- int current_line, int eol)
268
+ int current_line)
270
269
  {
271
270
  VALUE con = Qnil, kw = Qnil;
272
271
  kw = ENCODED_STR_NEW(keyword_at, keyword_length);
273
272
  con = ENCODED_STR_NEW(at, length);
274
- con = multiline_strip(con, eol);
275
273
  rb_funcall(con, rb_intern("strip!"), 0);
276
274
  rb_funcall(listener, rb_intern(event_name), 3, kw, con, INT2FIX(current_line));
277
275
  }
278
276
 
277
+ static void
278
+ store_multiline_kw_con(VALUE listener, const char * event_name,
279
+ const char * keyword_at, size_t keyword_length,
280
+ const char * at, size_t length,
281
+ int current_line, int eol)
282
+ {
283
+ VALUE con = Qnil, kw = Qnil, name = Qnil, desc = Qnil;
284
+
285
+ kw = ENCODED_STR_NEW(keyword_at, keyword_length);
286
+ con = ENCODED_STR_NEW(at, length);
287
+
288
+ VALUE split = multiline_strip(con);
289
+
290
+ name = rb_funcall(split, rb_intern("shift"), 0);
291
+ desc = rb_ary_join(split, rb_str_new2( \
292
+ eol == CRLF_FLAG ? CRLF : LF ));
293
+
294
+ if( name == Qnil )
295
+ {
296
+ name = rb_str_new2("");
297
+ }
298
+ if( rb_funcall(desc, rb_intern("size"), 0) == 0)
299
+ {
300
+ desc = rb_str_new2("");
301
+ }
302
+ rb_funcall(name, rb_intern("strip!"), 0);
303
+ rb_funcall(desc, rb_intern("strip!"), 0);
304
+ rb_funcall(listener, rb_intern(event_name), 4, kw, name, desc, INT2FIX(current_line));
305
+ }
306
+
279
307
  static void
280
308
  store_attr(VALUE listener, const char * attr_type,
281
309
  const char * at, size_t length,
@@ -307,7 +335,7 @@ store_pystring_content(VALUE listener,
307
335
  static void
308
336
  raise_lexer_error(const char * at, int line)
309
337
  {
310
- rb_raise(rb_eGherkinLexingError, "Lexing error on line %d: '%s'.", line, at);
338
+ rb_raise(rb_eGherkinLexingError, "Lexing error on line %d: '%s'. See http://wiki.github.com/aslakhellesoy/gherkin/lexingerror for more information.", line, at);
311
339
  }
312
340
 
313
341
  static int
@@ -361,12 +389,14 @@ static VALUE CLexer_init(VALUE self, VALUE listener)
361
389
  return self;
362
390
  }
363
391
 
364
- static VALUE CLexer_scan(VALUE self, VALUE input)
392
+ static VALUE CLexer_scan(VALUE self, VALUE input, VALUE uri, VALUE offset)
365
393
  {
394
+ VALUE listener = rb_iv_get(self, "@listener");
395
+ rb_funcall(listener, rb_intern("location"), 2, uri, offset);
396
+
366
397
  lexer_state *lexer = NULL;
367
398
  DATA_GET(self, lexer_state, lexer);
368
399
 
369
-
370
400
  VALUE input_copy = rb_str_dup(input);
371
401
 
372
402
  rb_str_append(input_copy, rb_str_new2("\n%_FEATURE_END_%"));
@@ -384,7 +414,6 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
384
414
  const char *p, *pe, *eof;
385
415
  int cs = 0;
386
416
 
387
- VALUE listener = rb_iv_get(self, "@listener");
388
417
  VALUE current_row = Qnil;
389
418
 
390
419
  p = data;
@@ -420,6 +449,6 @@ void Init_gherkin_lexer_<%= @i18n.underscored_iso_code %>()
420
449
  cI18nLexer = rb_define_class_under(mCLexer, "<%= @i18n.underscored_iso_code.capitalize %>", rb_cObject);
421
450
  rb_define_alloc_func(cI18nLexer, CLexer_alloc);
422
451
  rb_define_method(cI18nLexer, "initialize", CLexer_init, 1);
423
- rb_define_method(cI18nLexer, "scan", CLexer_scan, 1);
452
+ rb_define_method(cI18nLexer, "scan", CLexer_scan, 3);
424
453
  }
425
454