gherkin 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/History.txt +15 -6
  2. data/README.rdoc +23 -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;
@@ -1,9 +1,9 @@
1
1
  # encoding: utf-8
2
2
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
- require 'gherkin/format/argument'
3
+ require 'gherkin/formatter/argument'
4
4
 
5
5
  module Gherkin
6
- module Format
6
+ module Formatter
7
7
  class BracketFormat
8
8
  class << self
9
9
  def new