gherkin 1.0.3-i386-mingw32 → 1.0.4-i386-mingw32

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.
Files changed (58) hide show
  1. data/History.txt +15 -6
  2. data/README.rdoc +15 -0
  3. data/Rakefile +1 -0
  4. data/VERSION.yml +1 -1
  5. data/features/step_definitions/gherkin_steps.rb +1 -1
  6. data/features/step_definitions/{pretty_printer_steps.rb → pretty_listener_step.rb} +2 -2
  7. data/gherkin.gemspec +24 -35
  8. data/java/Gherkin.iml +8 -14
  9. data/java/src/{gherkin → main/java/gherkin}/lexer/.gitignore +0 -0
  10. data/lib/gherkin.rb +1 -1
  11. data/lib/gherkin/c_lexer.rb +2 -2
  12. data/lib/gherkin/{format → formatter}/argument.rb +1 -1
  13. data/lib/gherkin/{tools → formatter}/colors.rb +1 -1
  14. data/lib/gherkin/{format → formatter}/monochrome_format.rb +1 -1
  15. data/lib/gherkin/{tools → formatter}/pretty_listener.rb +8 -9
  16. data/lib/gherkin/i18n.rb +27 -5
  17. data/lib/gherkin/i18n_lexer.rb +18 -44
  18. data/lib/gherkin/parser/filter_listener.rb +191 -0
  19. data/lib/gherkin/parser/parser.rb +142 -0
  20. data/lib/gherkin/parser/sexp.rb +45 -0
  21. data/lib/gherkin/parser/tag_expression.rb +46 -0
  22. data/lib/gherkin/rb_lexer.rb +2 -2
  23. data/ragel/lexer_common.rl.erb +1 -1
  24. data/spec/gherkin/{format → formatter}/argument_spec.rb +2 -2
  25. data/spec/gherkin/{tools → formatter}/colors_spec.rb +2 -2
  26. data/spec/gherkin/{tools → formatter}/pretty_listener_spec.rb +5 -5
  27. data/spec/gherkin/i18n_lexer_spec.rb +3 -3
  28. data/spec/gherkin/parser/filter_listener_spec.rb +363 -0
  29. data/spec/gherkin/parser/parser_spec.rb +35 -0
  30. data/spec/gherkin/parser/tag_expression_spec.rb +120 -0
  31. data/spec/gherkin/rb_lexer_spec.rb +0 -1
  32. data/spec/gherkin/sexp_recorder.rb +3 -3
  33. data/spec/gherkin/shared/lexer_spec.rb +19 -19
  34. data/spec/gherkin/shared/tags_spec.rb +4 -4
  35. data/tasks/bench.rake +2 -2
  36. data/tasks/compile.rake +1 -1
  37. data/tasks/ragel_task.rb +1 -1
  38. metadata +25 -36
  39. data/java/build.xml +0 -16
  40. data/java/src/gherkin/FixJava.java +0 -37
  41. data/java/src/gherkin/I18nLexer.java +0 -48
  42. data/java/src/gherkin/Lexer.java +0 -5
  43. data/java/src/gherkin/LexingError.java +0 -7
  44. data/java/src/gherkin/Listener.java +0 -29
  45. data/java/src/gherkin/Main.java +0 -17
  46. data/java/src/gherkin/ParseError.java +0 -22
  47. data/java/src/gherkin/Parser.java +0 -191
  48. data/java/src/gherkin/formatter/Argument.java +0 -39
  49. data/java/src/gherkin/formatter/ArgumentFormat.java +0 -17
  50. data/java/src/gherkin/formatter/Colors.java +0 -7
  51. data/java/src/gherkin/formatter/Formatter.java +0 -15
  52. data/java/src/gherkin/formatter/PrettyFormatter.java +0 -219
  53. data/java/src/gherkin/parser/StateMachineReader.java +0 -67
  54. data/java/test/gherkin/formatter/ArgumentTest.java +0 -17
  55. data/lib/gherkin/lexer.rb +0 -35
  56. data/lib/gherkin/parser.rb +0 -19
  57. data/lib/gherkin/rb_parser.rb +0 -125
  58. 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
@@ -1,7 +1,7 @@
1
1
  module Gherkin
2
2
  module RbLexer
3
- def self.[](i18n_language)
4
- name = i18n_language.gsub(/[\s-]/, '')
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)
@@ -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;