gherkin 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -0
- data/LICENSE +1 -1
- data/README.rdoc +2 -2
- data/Rakefile +2 -2
- data/VERSION.yml +2 -2
- data/features/json_formatter.feature +3 -41
- data/features/step_definitions/gherkin_steps.rb +5 -6
- data/features/step_definitions/json_formatter_steps.rb +3 -2
- data/features/step_definitions/json_lexer_steps.rb +5 -5
- data/features/step_definitions/pretty_formatter_steps.rb +9 -13
- data/features/support/env.rb +1 -1
- data/lib/gherkin/formatter/argument.rb +1 -0
- data/lib/gherkin/formatter/filter_formatter.rb +149 -0
- data/lib/gherkin/formatter/json_formatter.rb +35 -45
- data/lib/gherkin/formatter/line_filter.rb +26 -0
- data/lib/gherkin/formatter/model.rb +85 -0
- data/lib/gherkin/formatter/pretty_formatter.rb +36 -39
- data/lib/gherkin/formatter/regexp_filter.rb +17 -0
- data/lib/gherkin/formatter/tag_count_formatter.rb +44 -0
- data/lib/gherkin/i18n.rb +5 -5
- data/lib/gherkin/i18n.yml +13 -0
- data/lib/gherkin/i18n_lexer.rb +2 -2
- data/lib/gherkin/{json_lexer.rb → json_parser.rb} +17 -5
- data/lib/gherkin/{parser → listener}/event.rb +1 -1
- data/lib/gherkin/{parser → listener}/formatter_listener.rb +30 -23
- data/lib/gherkin/native/java.rb +9 -1
- data/lib/gherkin/parser/parser.rb +27 -14
- data/lib/gherkin/rubify.rb +5 -1
- data/lib/gherkin/tag_expression.rb +62 -0
- data/lib/gherkin/tools/files.rb +3 -4
- data/lib/gherkin/tools/reformat.rb +2 -2
- data/lib/gherkin/tools/stats.rb +3 -4
- data/lib/gherkin/tools/stats_listener.rb +1 -1
- data/ragel/lexer.c.rl.erb +2 -3
- data/ragel/lexer.java.rl.erb +1 -2
- data/ragel/lexer.rb.rl.erb +1 -2
- data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
- data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
- data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
- data/spec/gherkin/formatter/filter_formatter_spec.rb +156 -0
- data/spec/gherkin/formatter/model_spec.rb +15 -0
- data/spec/gherkin/formatter/pretty_formatter_spec.rb +17 -16
- data/spec/gherkin/formatter/tag_count_formatter_spec.rb +31 -0
- data/spec/gherkin/i18n_lexer_spec.rb +3 -3
- data/spec/gherkin/i18n_spec.rb +2 -4
- data/spec/gherkin/{json_lexer_spec.rb → json_parser_spec.rb} +13 -8
- data/spec/gherkin/sexp_recorder.rb +10 -4
- data/spec/gherkin/shared/lexer_group.rb +0 -40
- data/spec/gherkin/shared/py_string_group.rb +0 -1
- data/spec/gherkin/shared/row_group.rb +1 -2
- data/spec/gherkin/tag_expression_spec.rb +137 -0
- data/spec/spec_helper.rb +5 -1
- data/tasks/bench.rake +5 -9
- metadata +35 -25
- data/lib/gherkin/parser/filter_listener.rb +0 -203
- data/lib/gherkin/parser/row.rb +0 -15
- data/lib/gherkin/parser/tag_expression.rb +0 -50
- data/spec/gherkin/parser/filter_listener_spec.rb +0 -397
- data/spec/gherkin/parser/formatter_listener_spec.rb +0 -134
- data/spec/gherkin/parser/parser_spec.rb +0 -50
- data/spec/gherkin/parser/tag_expression_spec.rb +0 -116
data/lib/gherkin/i18n.yml
CHANGED
@@ -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
|
data/lib/gherkin/i18n_lexer.rb
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'gherkin/listener/formatter_listener'
|
2
3
|
|
3
4
|
module Gherkin
|
4
|
-
class
|
5
|
+
class JSONParser
|
5
6
|
|
6
|
-
def initialize(
|
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
|
-
|
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,8 +1,8 @@
|
|
1
1
|
require 'gherkin/native'
|
2
|
-
require 'gherkin/
|
2
|
+
require 'gherkin/formatter/model'
|
3
3
|
|
4
4
|
module Gherkin
|
5
|
-
module
|
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(
|
20
|
-
@
|
21
|
-
@offset = offset
|
19
|
+
def location(feature_uri)
|
20
|
+
@feature_uri = feature_uri
|
22
21
|
end
|
23
22
|
|
24
|
-
def comment(
|
25
|
-
@comments <<
|
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, @
|
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
|
-
@
|
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
|
-
@
|
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,
|
61
|
+
@table << Formatter::Model::Row.new(grab_comments!, cells, line)
|
63
62
|
end
|
64
63
|
|
65
|
-
def py_string(
|
66
|
-
@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(@
|
108
|
+
if(@step_statement)
|
102
109
|
multiline_arg = grab_py_string! || grab_table!
|
103
|
-
@formatter.step(
|
104
|
-
@
|
110
|
+
@formatter.step(@step_statement, multiline_arg, nil)
|
111
|
+
@step_statement = nil
|
105
112
|
end
|
106
|
-
if(@
|
107
|
-
@formatter.examples(
|
108
|
-
@
|
113
|
+
if(@examples_statement)
|
114
|
+
@formatter.examples(@examples_statement, grab_table!)
|
115
|
+
@examples_statement = nil
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
data/lib/gherkin/native/java.rb
CHANGED
@@ -26,7 +26,15 @@ class Class
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def new(*args)
|
29
|
-
|
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(
|
16
|
-
@
|
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
|
24
|
-
@
|
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
|
-
|
31
|
-
|
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
|
-
|
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,
|
56
|
+
raise ParseError.new(state, ev, legal_events, @feature_uri, l)
|
43
57
|
else
|
44
|
-
|
45
|
-
|
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)
|
138
|
+
lexer.scan(File.read(machine))
|
126
139
|
state_machine_reader.rows
|
127
140
|
end
|
128
141
|
|
data/lib/gherkin/rubify.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/gherkin/tools/files.rb
CHANGED
@@ -20,11 +20,10 @@ module Gherkin
|
|
20
20
|
Dir[*globs].uniq.sort.each(&proc)
|
21
21
|
end
|
22
22
|
|
23
|
-
def scan(file,
|
24
|
-
parser = Gherkin::Parser::Parser.new(
|
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
|
-
|
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/
|
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 =
|
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)}
|
data/lib/gherkin/tools/stats.rb
CHANGED
@@ -6,11 +6,10 @@ module Gherkin
|
|
6
6
|
module Tools
|
7
7
|
class Stats < Files
|
8
8
|
def run
|
9
|
-
|
9
|
+
formatter = StatsFormatter.new
|
10
10
|
each do |f|
|
11
|
-
parser = Gherkin::Parser::Parser.new(
|
12
|
-
|
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}"
|
data/ragel/lexer.c.rl.erb
CHANGED
@@ -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
|
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,
|
451
|
+
rb_define_method(cI18nLexer, "scan", CLexer_scan, 1);
|
453
452
|
}
|
454
453
|
|