gherkin 1.0.3-i386-mswin32 → 1.0.4-i386-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +15 -6
- data/README.rdoc +15 -0
- data/Rakefile +1 -0
- data/VERSION.yml +1 -1
- data/features/step_definitions/gherkin_steps.rb +1 -1
- data/features/step_definitions/{pretty_printer_steps.rb → pretty_listener_step.rb} +2 -2
- data/gherkin.gemspec +24 -35
- data/java/Gherkin.iml +8 -14
- data/java/src/{gherkin → main/java/gherkin}/lexer/.gitignore +0 -0
- data/lib/gherkin.rb +1 -1
- data/lib/gherkin/c_lexer.rb +2 -2
- data/lib/gherkin/{format → formatter}/argument.rb +1 -1
- data/lib/gherkin/{tools → formatter}/colors.rb +1 -1
- data/lib/gherkin/{format → formatter}/monochrome_format.rb +1 -1
- data/lib/gherkin/{tools → formatter}/pretty_listener.rb +8 -9
- data/lib/gherkin/i18n.rb +27 -5
- data/lib/gherkin/i18n_lexer.rb +18 -44
- data/lib/gherkin/parser/filter_listener.rb +191 -0
- data/lib/gherkin/parser/parser.rb +142 -0
- data/lib/gherkin/parser/sexp.rb +45 -0
- data/lib/gherkin/parser/tag_expression.rb +46 -0
- data/lib/gherkin/rb_lexer.rb +2 -2
- data/ragel/lexer_common.rl.erb +1 -1
- data/spec/gherkin/{format → formatter}/argument_spec.rb +2 -2
- data/spec/gherkin/{tools → formatter}/colors_spec.rb +2 -2
- data/spec/gherkin/{tools → formatter}/pretty_listener_spec.rb +5 -5
- data/spec/gherkin/i18n_lexer_spec.rb +3 -3
- data/spec/gherkin/parser/filter_listener_spec.rb +363 -0
- data/spec/gherkin/parser/parser_spec.rb +35 -0
- data/spec/gherkin/parser/tag_expression_spec.rb +120 -0
- data/spec/gherkin/rb_lexer_spec.rb +0 -1
- data/spec/gherkin/sexp_recorder.rb +3 -3
- data/spec/gherkin/shared/lexer_spec.rb +19 -19
- data/spec/gherkin/shared/tags_spec.rb +4 -4
- data/tasks/bench.rake +2 -2
- data/tasks/compile.rake +1 -1
- data/tasks/ragel_task.rb +1 -1
- metadata +25 -36
- data/java/build.xml +0 -16
- data/java/src/gherkin/FixJava.java +0 -37
- data/java/src/gherkin/I18nLexer.java +0 -48
- data/java/src/gherkin/Lexer.java +0 -5
- data/java/src/gherkin/LexingError.java +0 -7
- data/java/src/gherkin/Listener.java +0 -29
- data/java/src/gherkin/Main.java +0 -17
- data/java/src/gherkin/ParseError.java +0 -22
- data/java/src/gherkin/Parser.java +0 -191
- data/java/src/gherkin/formatter/Argument.java +0 -39
- data/java/src/gherkin/formatter/ArgumentFormat.java +0 -17
- data/java/src/gherkin/formatter/Colors.java +0 -7
- data/java/src/gherkin/formatter/Formatter.java +0 -15
- data/java/src/gherkin/formatter/PrettyFormatter.java +0 -219
- data/java/src/gherkin/parser/StateMachineReader.java +0 -67
- data/java/test/gherkin/formatter/ArgumentTest.java +0 -17
- data/lib/gherkin/lexer.rb +0 -35
- data/lib/gherkin/parser.rb +0 -19
- data/lib/gherkin/rb_parser.rb +0 -125
- data/spec/gherkin/parser_spec.rb +0 -33
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'gherkin/parser/sexp'
|
2
|
+
require 'gherkin/parser/tag_expression'
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
# This class filters events based on filter criteria.
|
7
|
+
class FilterListener
|
8
|
+
# Creates a new instance that replays events to +listener+, filtered by +filters+,
|
9
|
+
# a Hash that can contain:
|
10
|
+
#
|
11
|
+
# * <tt>:lines</tt> An Array of line numbers to filter on.
|
12
|
+
# * <tt>:name_regexen</tt> An Array of name regexen to filter on. Matches against :feature, :scenario, :scenario_outline and :examples
|
13
|
+
# * <tt>:tag_expression</tt> A TagExpression to filter on.
|
14
|
+
#
|
15
|
+
def initialize(listener, filters)
|
16
|
+
@listener = listener
|
17
|
+
@filter_method = detect_filter(filters)
|
18
|
+
|
19
|
+
@meta_buffer = []
|
20
|
+
@feature_buffer = []
|
21
|
+
@scenario_buffer = []
|
22
|
+
@examples_buffer = []
|
23
|
+
@examples_rows_buffer = []
|
24
|
+
|
25
|
+
@feature_tags = []
|
26
|
+
@scenario_tags = []
|
27
|
+
@example_tags = []
|
28
|
+
|
29
|
+
@table_state = :step
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def method_missing(*sexp_args)
|
35
|
+
sexp = Sexp.new(sexp_args)
|
36
|
+
|
37
|
+
return sexp.replay(@listener) if no_filters?
|
38
|
+
|
39
|
+
case(sexp.event)
|
40
|
+
when :tag
|
41
|
+
@meta_buffer << sexp
|
42
|
+
when :comment
|
43
|
+
@meta_buffer << sexp
|
44
|
+
when :feature
|
45
|
+
@feature_buffer = @meta_buffer
|
46
|
+
@feature_buffer << sexp
|
47
|
+
@feature_tags = extract_tags
|
48
|
+
@meta_buffer = []
|
49
|
+
@feature_ok = true if filter_match?(sexp)
|
50
|
+
when :background
|
51
|
+
@feature_buffer += @meta_buffer
|
52
|
+
@feature_buffer << sexp
|
53
|
+
@meta_buffer = []
|
54
|
+
@table_state = :background
|
55
|
+
@feature_ok = true if filter_match?(sexp)
|
56
|
+
when :scenario
|
57
|
+
replay_examples_rows_buffer
|
58
|
+
@scenario_buffer = @meta_buffer
|
59
|
+
@scenario_buffer << sexp
|
60
|
+
@scenario_tags = extract_tags
|
61
|
+
@example_tags = []
|
62
|
+
@meta_buffer = []
|
63
|
+
@scenario_ok = filter_match?(*@scenario_buffer) || tag_match?
|
64
|
+
@examples_ok = false
|
65
|
+
@table_state = :step
|
66
|
+
when :scenario_outline
|
67
|
+
replay_examples_rows_buffer
|
68
|
+
@scenario_buffer = @meta_buffer
|
69
|
+
@scenario_buffer << sexp
|
70
|
+
@scenario_tags = extract_tags
|
71
|
+
@example_tags = []
|
72
|
+
@meta_buffer = []
|
73
|
+
@scenario_ok = filter_match?(*@scenario_buffer)
|
74
|
+
@examples_ok = false
|
75
|
+
@table_state = :step
|
76
|
+
when :examples
|
77
|
+
replay_examples_rows_buffer
|
78
|
+
@examples_buffer = @meta_buffer
|
79
|
+
@examples_buffer << sexp
|
80
|
+
@example_tags = extract_tags
|
81
|
+
@meta_buffer = []
|
82
|
+
@examples_rows_buffer = []
|
83
|
+
@examples_ok = filter_match?(*@examples_buffer) || tag_match?
|
84
|
+
@table_state = :examples
|
85
|
+
when :step
|
86
|
+
case(@table_state)
|
87
|
+
when :background
|
88
|
+
@feature_buffer += @meta_buffer
|
89
|
+
@feature_buffer << sexp
|
90
|
+
@meta_buffer = []
|
91
|
+
@feature_ok = true if filter_match?(sexp)
|
92
|
+
else
|
93
|
+
@scenario_buffer << sexp
|
94
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
95
|
+
@table_state = :step
|
96
|
+
end
|
97
|
+
when :row
|
98
|
+
case(@table_state)
|
99
|
+
when :examples
|
100
|
+
unless header_row_already_buffered?
|
101
|
+
@examples_buffer << sexp
|
102
|
+
@examples_ok = true if filter_match?(*@examples_buffer)
|
103
|
+
else
|
104
|
+
@examples_rows_buffer << sexp if @scenario_ok || @examples_ok || @feature_ok || filter_match?(sexp)
|
105
|
+
end
|
106
|
+
when :step
|
107
|
+
@scenario_buffer << sexp
|
108
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
109
|
+
when :background
|
110
|
+
@feature_buffer += @meta_buffer
|
111
|
+
@feature_buffer << sexp
|
112
|
+
@meta_buffer = []
|
113
|
+
else
|
114
|
+
raise "Bad table_state:#{@table_state.inspect}"
|
115
|
+
end
|
116
|
+
when :py_string
|
117
|
+
@scenario_buffer << sexp
|
118
|
+
@scenario_ok ||= filter_match?(*@scenario_buffer)
|
119
|
+
when :eof
|
120
|
+
replay_examples_rows_buffer
|
121
|
+
sexp.replay(@listener)
|
122
|
+
return
|
123
|
+
else
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
127
|
+
if @scenario_ok || @examples_ok || @feature_ok
|
128
|
+
replay_buffers
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def detect_filter(filters)
|
133
|
+
@filters = filters
|
134
|
+
raise "Bad filter: #{filters.inspect}" if filters.map{|filter| filter.class}.uniq.length > 1
|
135
|
+
@filter_method = case(filters[0])
|
136
|
+
when Fixnum
|
137
|
+
:line_match?
|
138
|
+
when Regexp
|
139
|
+
:name_match?
|
140
|
+
when String
|
141
|
+
TagExpression.new(*filters)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def no_filters?
|
146
|
+
@filters.empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
def header_row_already_buffered?
|
150
|
+
return false unless @examples_buffer.any?
|
151
|
+
@examples_buffer[-1].event == :row
|
152
|
+
end
|
153
|
+
|
154
|
+
def filter_match?(*sexps)
|
155
|
+
return false unless[:name_match?, :line_match?].include?(@filter_method)
|
156
|
+
sexps.detect{|sexp| sexp.__send__(@filter_method, @filters)}
|
157
|
+
end
|
158
|
+
|
159
|
+
def tag_match?
|
160
|
+
return false unless TagExpression === @filter_method
|
161
|
+
@filter_method.eval(*current_tags)
|
162
|
+
end
|
163
|
+
|
164
|
+
def replay_buffers
|
165
|
+
(@feature_buffer + @scenario_buffer).each do |sexp|
|
166
|
+
sexp.replay(@listener)
|
167
|
+
end
|
168
|
+
@feature_buffer = []
|
169
|
+
@scenario_buffer = []
|
170
|
+
end
|
171
|
+
|
172
|
+
def replay_examples_rows_buffer
|
173
|
+
if @examples_rows_buffer.any?
|
174
|
+
replay_buffers
|
175
|
+
(@examples_buffer + @examples_rows_buffer).each do |sexp|
|
176
|
+
sexp.replay(@listener)
|
177
|
+
end
|
178
|
+
@examples_rows_buffer = []
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def current_tags
|
183
|
+
@feature_tags + @scenario_tags + @example_tags
|
184
|
+
end
|
185
|
+
|
186
|
+
def extract_tags
|
187
|
+
@meta_buffer.select { |sexp| sexp.event == :tag }.map { |sexp| sexp.keyword }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class ParseError < StandardError
|
4
|
+
def initialize(state, new_state, expected_states, line)
|
5
|
+
super("Parse error on line #{line}. Found #{new_state} when expecting one of: #{expected_states.join(', ')}. (Current state: #{state}).")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Parser
|
10
|
+
def self.new(listener, raise_on_error=false, machine_name='root')
|
11
|
+
if defined?(JRUBY_VERSION)
|
12
|
+
require 'gherkin.jar'
|
13
|
+
Java::GherkinParser::Parser.new(listener, raise_on_error, machine_name)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Initialize the parser. +machine_name+ refers to a state machine table.
|
20
|
+
def initialize(listener, raise_on_error, machine_name)
|
21
|
+
@listener = listener
|
22
|
+
@raise_on_error = raise_on_error
|
23
|
+
@machines = []
|
24
|
+
push_machine(machine_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Doesn't yet fall back to super
|
28
|
+
def method_missing(method, *args)
|
29
|
+
# TODO: Catch exception and call super
|
30
|
+
if(event(method.to_s, args[-1]))
|
31
|
+
@listener.send(method, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def event(ev, line)
|
36
|
+
machine.event(ev, line) do |state, expected|
|
37
|
+
if @raise_on_error
|
38
|
+
raise ParseError.new(state, ev, expected, line)
|
39
|
+
else
|
40
|
+
@listener.syntax_error(state, ev, expected, line)
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def push_machine(name)
|
48
|
+
@machines.push(Machine.new(self, name))
|
49
|
+
end
|
50
|
+
|
51
|
+
def pop_machine
|
52
|
+
@machines.pop
|
53
|
+
end
|
54
|
+
|
55
|
+
def machine
|
56
|
+
@machines[-1]
|
57
|
+
end
|
58
|
+
|
59
|
+
def expected
|
60
|
+
machine.expected
|
61
|
+
end
|
62
|
+
|
63
|
+
def force_state(state)
|
64
|
+
machine.instance_variable_set('@state', state)
|
65
|
+
end
|
66
|
+
|
67
|
+
class Machine
|
68
|
+
def initialize(parser, name)
|
69
|
+
@parser = parser
|
70
|
+
@name = name
|
71
|
+
@transition_map = transition_map(name)
|
72
|
+
@state = name
|
73
|
+
end
|
74
|
+
|
75
|
+
def event(ev, line)
|
76
|
+
states = @transition_map[@state]
|
77
|
+
raise "Unknown state: #{@state.inspect} for machine #{@name}" if states.nil?
|
78
|
+
new_state = states[ev]
|
79
|
+
case new_state
|
80
|
+
when "E"
|
81
|
+
yield @state, expected
|
82
|
+
when /push\((.+)\)/
|
83
|
+
@parser.push_machine($1)
|
84
|
+
@parser.event(ev, line)
|
85
|
+
when "pop()"
|
86
|
+
@parser.pop_machine()
|
87
|
+
@parser.event(ev, line)
|
88
|
+
else
|
89
|
+
raise "Unknown transition: #{ev.inspect} among #{states.inspect} for machine #{@name}" if new_state.nil?
|
90
|
+
@state = new_state
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def expected
|
95
|
+
allowed = @transition_map[@state].find_all { |_, action| action != "E" }
|
96
|
+
allowed.collect { |state| state[0] }.sort - ['eof']
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
@@transition_maps = {}
|
102
|
+
|
103
|
+
def transition_map(name)
|
104
|
+
@@transition_maps[name] ||= build_transition_map(name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_transition_map(name)
|
108
|
+
table = transition_table(name)
|
109
|
+
events = table.shift[1..-1]
|
110
|
+
table.inject({}) do |machine, actions|
|
111
|
+
state = actions.shift
|
112
|
+
machine[state] = Hash[*events.zip(actions).flatten]
|
113
|
+
machine
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def transition_table(name)
|
118
|
+
state_machine_reader = StateMachineReader.new
|
119
|
+
lexer = Gherkin::I18n.new('en').lexer(state_machine_reader, false)
|
120
|
+
lexer.scan(File.read(File.dirname(__FILE__) + "/#{name}.txt"))
|
121
|
+
state_machine_reader.rows
|
122
|
+
end
|
123
|
+
|
124
|
+
class StateMachineReader
|
125
|
+
attr_reader :rows
|
126
|
+
|
127
|
+
def initialize
|
128
|
+
@rows = []
|
129
|
+
end
|
130
|
+
|
131
|
+
def row(row, line_number)
|
132
|
+
@rows << row
|
133
|
+
end
|
134
|
+
|
135
|
+
def eof
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class Sexp < Array
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
self[1] = self[1].to_a if event == :row # Special JRuby handling
|
7
|
+
end
|
8
|
+
|
9
|
+
def event
|
10
|
+
self[0]
|
11
|
+
end
|
12
|
+
|
13
|
+
def keyword
|
14
|
+
self[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def line_match?(lines)
|
18
|
+
lines.include?(line)
|
19
|
+
end
|
20
|
+
|
21
|
+
def name_match?(name_regexen)
|
22
|
+
return false unless [:feature, :scenario, :scenario_outline, :examples].include?(event)
|
23
|
+
name_regexen.detect{|name_regex| name =~ name_regex}
|
24
|
+
end
|
25
|
+
|
26
|
+
def replay(listener)
|
27
|
+
listener.__send__(event, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def name
|
33
|
+
self[2]
|
34
|
+
end
|
35
|
+
|
36
|
+
def line
|
37
|
+
self[-1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def args
|
41
|
+
self[1..-1]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Parser
|
3
|
+
class TagExpression
|
4
|
+
attr_reader :limits
|
5
|
+
|
6
|
+
def initialize(*tag_expressions)
|
7
|
+
@ands = []
|
8
|
+
@limits = {}
|
9
|
+
tag_expressions.each do |expr|
|
10
|
+
add(expr.strip.split(/\s*,\s*/))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
@ands.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def eval(*tags)
|
19
|
+
return true if @ands.flatten.empty?
|
20
|
+
vars = Hash[*tags.map{|tag| [tag, true]}.flatten]
|
21
|
+
!!Kernel.eval(ruby_expression)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def add(tags)
|
27
|
+
negatives, positives = tags.partition{|tag| tag =~ /^~/}
|
28
|
+
positive_limits = Hash[*positives.map{|positive| tag, limit = positive.split(':'); [tag, limit ? limit.to_i : nil]}.flatten]
|
29
|
+
@limits.merge!(positive_limits)
|
30
|
+
@ands << (negatives + positive_limits.keys)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ruby_expression
|
34
|
+
"(" + @ands.map do |ors|
|
35
|
+
ors.map do |tag|
|
36
|
+
if tag =~ /^~(.*)/
|
37
|
+
"!vars['#{$1}']"
|
38
|
+
else
|
39
|
+
"vars['#{tag}']"
|
40
|
+
end
|
41
|
+
end.join("||")
|
42
|
+
end.join(")&&(") + ")"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/gherkin/rb_lexer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Gherkin
|
2
2
|
module RbLexer
|
3
|
-
def self.[](
|
4
|
-
name =
|
3
|
+
def self.[](i18n_language_name)
|
4
|
+
name = i18n_language_name.gsub(/[\s-]/, '')
|
5
5
|
require "gherkin/rb_lexer/#{name}"
|
6
6
|
i18n_lexer_class_name = name.capitalize
|
7
7
|
const_get(i18n_lexer_class_name)
|
data/ragel/lexer_common.rl.erb
CHANGED
@@ -27,7 +27,7 @@
|
|
27
27
|
Step = space* I18N_Step %begin_content ^EOL+ %store_step_content :> EOL+;
|
28
28
|
Comment = space* '#' >begin_content ^EOL* %store_comment_content :> EOL+;
|
29
29
|
|
30
|
-
Tag = ( '@' [^@\r\n\t ]+ >begin_content ) %store_tag_content;
|
30
|
+
Tag = ( ('@' [^@\r\n\t ]+) >begin_content ) %store_tag_content;
|
31
31
|
Tags = space* (Tag space*)+ EOL+;
|
32
32
|
|
33
33
|
StartRow = space* '|' >start_row;
|