gherkin 1.0.3-i386-mswin32 → 1.0.4-i386-mswin32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|