gherkin 1.0.30-universal-dotnet

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 (104) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +9 -0
  3. data/.mailmap +2 -0
  4. data/History.txt +187 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +59 -0
  7. data/Rakefile +58 -0
  8. data/VERSION.yml +5 -0
  9. data/bin/gherkin +5 -0
  10. data/cucumber.yml +3 -0
  11. data/features/escaped_pipes.feature +8 -0
  12. data/features/feature_parser.feature +226 -0
  13. data/features/native_lexer.feature +19 -0
  14. data/features/parser_with_native_lexer.feature +205 -0
  15. data/features/pretty_printer.feature +14 -0
  16. data/features/step_definitions/eyeball_steps.rb +3 -0
  17. data/features/step_definitions/gherkin_steps.rb +30 -0
  18. data/features/step_definitions/pretty_formatter_steps.rb +55 -0
  19. data/features/steps_parser.feature +46 -0
  20. data/features/support/env.rb +33 -0
  21. data/ikvm/.gitignore +3 -0
  22. data/java/.gitignore +2 -0
  23. data/java/src/main/java/gherkin/lexer/.gitignore +1 -0
  24. data/java/src/main/resources/gherkin/.gitignore +1 -0
  25. data/lib/.gitignore +4 -0
  26. data/lib/gherkin.rb +2 -0
  27. data/lib/gherkin/c_lexer.rb +17 -0
  28. data/lib/gherkin/cli/main.rb +33 -0
  29. data/lib/gherkin/formatter/argument.rb +27 -0
  30. data/lib/gherkin/formatter/colors.rb +119 -0
  31. data/lib/gherkin/formatter/escaping.rb +15 -0
  32. data/lib/gherkin/formatter/monochrome_format.rb +9 -0
  33. data/lib/gherkin/formatter/pretty_formatter.rb +168 -0
  34. data/lib/gherkin/i18n.rb +176 -0
  35. data/lib/gherkin/i18n.yml +588 -0
  36. data/lib/gherkin/i18n_lexer.rb +38 -0
  37. data/lib/gherkin/native.rb +7 -0
  38. data/lib/gherkin/native/ikvm.rb +55 -0
  39. data/lib/gherkin/native/java.rb +47 -0
  40. data/lib/gherkin/native/null.rb +9 -0
  41. data/lib/gherkin/parser/event.rb +45 -0
  42. data/lib/gherkin/parser/filter_listener.rb +199 -0
  43. data/lib/gherkin/parser/meta.txt +5 -0
  44. data/lib/gherkin/parser/parser.rb +142 -0
  45. data/lib/gherkin/parser/root.txt +11 -0
  46. data/lib/gherkin/parser/steps.txt +4 -0
  47. data/lib/gherkin/parser/tag_expression.rb +50 -0
  48. data/lib/gherkin/rb_lexer.rb +8 -0
  49. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  50. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  51. data/lib/gherkin/rubify.rb +18 -0
  52. data/lib/gherkin/tools.rb +8 -0
  53. data/lib/gherkin/tools/files.rb +35 -0
  54. data/lib/gherkin/tools/reformat.rb +19 -0
  55. data/lib/gherkin/tools/stats.rb +21 -0
  56. data/lib/gherkin/tools/stats_listener.rb +57 -0
  57. data/ragel/i18n/.gitignore +1 -0
  58. data/ragel/lexer.c.rl.erb +425 -0
  59. data/ragel/lexer.java.rl.erb +216 -0
  60. data/ragel/lexer.rb.rl.erb +173 -0
  61. data/ragel/lexer_common.rl.erb +50 -0
  62. data/spec/gherkin/c_lexer_spec.rb +21 -0
  63. data/spec/gherkin/csharp_lexer_spec.rb +20 -0
  64. data/spec/gherkin/fixtures/1.feature +8 -0
  65. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  66. data/spec/gherkin/fixtures/complex.feature +45 -0
  67. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  68. data/spec/gherkin/fixtures/i18n_fr.feature +14 -0
  69. data/spec/gherkin/fixtures/i18n_no.feature +7 -0
  70. data/spec/gherkin/fixtures/i18n_zh-CN.feature +9 -0
  71. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  72. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  73. data/spec/gherkin/fixtures/with_bom.feature +3 -0
  74. data/spec/gherkin/formatter/argument_spec.rb +28 -0
  75. data/spec/gherkin/formatter/colors_spec.rb +19 -0
  76. data/spec/gherkin/formatter/pretty_formatter_spec.rb +162 -0
  77. data/spec/gherkin/formatter/spaces.feature +9 -0
  78. data/spec/gherkin/formatter/tabs.feature +9 -0
  79. data/spec/gherkin/i18n_lexer_spec.rb +26 -0
  80. data/spec/gherkin/i18n_spec.rb +144 -0
  81. data/spec/gherkin/java_lexer_spec.rb +21 -0
  82. data/spec/gherkin/parser/filter_listener_spec.rb +390 -0
  83. data/spec/gherkin/parser/parser_spec.rb +50 -0
  84. data/spec/gherkin/parser/tag_expression_spec.rb +116 -0
  85. data/spec/gherkin/rb_lexer_spec.rb +19 -0
  86. data/spec/gherkin/sexp_recorder.rb +32 -0
  87. data/spec/gherkin/shared/lexer_spec.rb +550 -0
  88. data/spec/gherkin/shared/py_string_spec.rb +150 -0
  89. data/spec/gherkin/shared/row_spec.rb +104 -0
  90. data/spec/gherkin/shared/tags_spec.rb +50 -0
  91. data/spec/spec_helper.rb +87 -0
  92. data/tasks/bench.rake +188 -0
  93. data/tasks/bench/feature_builder.rb +49 -0
  94. data/tasks/bench/generated/.gitignore +1 -0
  95. data/tasks/bench/null_listener.rb +4 -0
  96. data/tasks/compile.rake +89 -0
  97. data/tasks/cucumber.rake +26 -0
  98. data/tasks/gems.rake +45 -0
  99. data/tasks/ikvm.rake +47 -0
  100. data/tasks/ragel_task.rb +70 -0
  101. data/tasks/rdoc.rake +12 -0
  102. data/tasks/release.rake +26 -0
  103. data/tasks/rspec.rake +15 -0
  104. metadata +257 -0
@@ -0,0 +1,38 @@
1
+ require 'gherkin/i18n'
2
+ require 'gherkin/native'
3
+
4
+ module Gherkin
5
+ I18nLexerNotFound = Class.new(LoadError)
6
+ LexingError = Class.new(StandardError)
7
+
8
+ # The main entry point to lexing Gherkin source.
9
+ class I18nLexer
10
+ native_impl('gherkin')
11
+
12
+ LANGUAGE_PATTERN = /language\s*:\s*(.*)/ #:nodoc:
13
+ attr_reader :i18n_language
14
+
15
+ def initialize(listener, force_ruby=false)
16
+ @listener = listener
17
+ @force_ruby = force_ruby
18
+ end
19
+
20
+ def scan(source)
21
+ create_delegate(source).scan(source)
22
+ end
23
+
24
+ private
25
+
26
+ def create_delegate(source)
27
+ @i18n_language = lang(source)
28
+ @i18n_language.lexer(@listener, @force_ruby)
29
+ end
30
+
31
+ def lang(source)
32
+ line_one = source.split(/\n/)[0]
33
+ match = LANGUAGE_PATTERN.match(line_one)
34
+ I18n.get(match ? match[1] : 'en')
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby"
2
+ require 'gherkin/native/ikvm'
3
+ elsif defined?(JRUBY_VERSION)
4
+ require 'gherkin/native/java'
5
+ else
6
+ require 'gherkin/native/null'
7
+ end
@@ -0,0 +1,55 @@
1
+ class Class
2
+
3
+ def implements(java_class_name)
4
+ m = java_class_name.split('.').inject(Object) do |mod, name|
5
+ mod = mod.const_get(name)
6
+ end
7
+ include m
8
+ end
9
+
10
+ # Causes a .NET class to be instantiated instead of the Ruby class when
11
+ # running on IronRuby. This is used to test both pure .NET and pure Ruby classes
12
+ # from the same Ruby based test suite. The .NET Class must have a package name
13
+ # that corresponds with the Ruby class.
14
+ def native_impl(lib)
15
+ begin
16
+ load_assembly(lib)
17
+ rescue LoadError => e
18
+ e.message << "\nTry this: SET MONO_PATH=#{File.expand_path(File.dirname(__FILE__) + '/../..')} (or export MONO_PATH=...)"
19
+ raise e
20
+ end
21
+
22
+ class << self
23
+ def ikvmify(arg)
24
+ if Array === arg
25
+ arg.map{|a| ikvmify(a)}
26
+ else
27
+ case(arg)
28
+ when Regexp
29
+ Object.const_get('java').const_get('util').const_get('regex').const_get('Pattern').compile(arg.source)
30
+ else
31
+ arg
32
+ end
33
+ end
34
+ end
35
+
36
+ def new(*args)
37
+ ikvm_class.new(*ikvmify(args))
38
+ end
39
+
40
+ def ===(object)
41
+ super || object.java_kind_of?(java_class)
42
+ end
43
+
44
+ def ikvm_class
45
+ names = self.name.split('::')
46
+ namespace = Object
47
+ names[0..-2].each do |module_name|
48
+ namespace = namespace.const_get(module_name.downcase)
49
+ end
50
+
51
+ namespace.const_get(names[-1])
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ class Class
2
+
3
+ def implements(java_class_name)
4
+ # no-op
5
+ end
6
+
7
+ # Causes a Java class to be instantiated instead of the Ruby class when
8
+ # running on JRuby. This is used to test both pure Java and pure Ruby classes
9
+ # from the same Ruby based test suite. The Java Class must have a package name
10
+ # that corresponds with the Ruby class.
11
+ def native_impl(lib)
12
+ require "#{lib}.jar"
13
+
14
+ class << self
15
+ def javaify(arg)
16
+ if Array === arg
17
+ arg.map{|a| javaify(a)}
18
+ else
19
+ case(arg)
20
+ when Regexp
21
+ java.util.regex.Pattern.compile(arg.source)
22
+ else
23
+ arg
24
+ end
25
+ end
26
+ end
27
+
28
+ def new(*args)
29
+ java_class.new(*javaify(args))
30
+ end
31
+
32
+ def ===(object)
33
+ super || object.java_kind_of?(java_class)
34
+ end
35
+
36
+ def java_class
37
+ names = self.name.split('::')
38
+ package = Java
39
+ names[0..-2].each do |module_name|
40
+ package = package.__send__(module_name.downcase)
41
+ end
42
+
43
+ package.__send__(names[-1])
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ class Class
2
+ def implements(java_class_name)
3
+ # no-op
4
+ end
5
+
6
+ def native_impl(lib)
7
+ # no-op
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ module Gherkin
2
+ module Parser
3
+ class Event < 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, :background, :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,199 @@
1
+ require 'gherkin/parser/event'
2
+ require 'gherkin/parser/tag_expression'
3
+ require 'gherkin/native'
4
+
5
+ module Gherkin
6
+ module Parser
7
+ # This class filters events based on filter criteria.
8
+ class FilterListener
9
+ native_impl('gherkin')
10
+
11
+ # Creates a new instance that replays events to +listener+, filtered by +filters+,
12
+ # an Array that can contain one of the following:
13
+ #
14
+ # * Line numbers (Fixnum) to filter on.
15
+ # * Name regexen (Regexp) to filter on. Matches against :feature, :background, :scenario, :scenario_outline and :examples
16
+ # * Tag expressions (String) to filter on.
17
+ #
18
+ def initialize(listener, filters)
19
+ @listener = listener
20
+ @filter_method = detect_filter(filters)
21
+
22
+ @meta_buffer = []
23
+ @feature_buffer = []
24
+ @scenario_buffer = []
25
+ @examples_buffer = []
26
+ @examples_rows_buffer = []
27
+
28
+ @feature_tags = []
29
+ @scenario_tags = []
30
+ @example_tags = []
31
+
32
+ @table_state = :step
33
+ end
34
+
35
+ private
36
+
37
+ def method_missing(*event_args)
38
+ event = Event.new(event_args)
39
+
40
+ return event.replay(@listener) if no_filters?
41
+
42
+ case(event.event)
43
+ when :tag
44
+ @meta_buffer << event
45
+ when :comment
46
+ @meta_buffer << event
47
+ when :feature
48
+ @feature_buffer = @meta_buffer
49
+ @feature_buffer << event
50
+ @feature_tags = extract_tags
51
+ @meta_buffer = []
52
+ @feature_ok = true if filter_match?(event)
53
+ when :background
54
+ @feature_buffer += @meta_buffer
55
+ @feature_buffer << event
56
+ @meta_buffer = []
57
+ @table_state = :background
58
+ @background_ok = true if filter_match?(event)
59
+ when :scenario
60
+ replay_examples_rows_buffer
61
+ @scenario_buffer = @meta_buffer
62
+ @scenario_buffer << event
63
+ @scenario_tags = extract_tags
64
+ @example_tags = []
65
+ @meta_buffer = []
66
+ @scenario_ok = filter_match?(*@scenario_buffer) || tag_match?
67
+ @examples_ok = false
68
+ @background_ok = false
69
+ @table_state = :step
70
+ when :scenario_outline
71
+ replay_examples_rows_buffer
72
+ @scenario_buffer = @meta_buffer
73
+ @scenario_buffer << event
74
+ @scenario_tags = extract_tags
75
+ @example_tags = []
76
+ @meta_buffer = []
77
+ @scenario_ok = filter_match?(*@scenario_buffer)
78
+ @examples_ok = false
79
+ @background_ok = false
80
+ @table_state = :step
81
+ when :examples
82
+ replay_examples_rows_buffer
83
+ @examples_buffer = @meta_buffer
84
+ @examples_buffer << event
85
+ @example_tags = extract_tags
86
+ @meta_buffer = []
87
+ @examples_rows_buffer = []
88
+ @examples_ok = filter_match?(*@examples_buffer) || tag_match?
89
+ @table_state = :examples
90
+ when :step
91
+ case(@table_state)
92
+ when :background
93
+ @feature_buffer += @meta_buffer
94
+ @feature_buffer << event
95
+ @meta_buffer = []
96
+ @background_ok = true if filter_match?(event)
97
+ else
98
+ @scenario_buffer << event
99
+ @scenario_ok ||= filter_match?(*@scenario_buffer)
100
+ @table_state = :step
101
+ end
102
+ when :row
103
+ case(@table_state)
104
+ when :examples
105
+ unless header_row_already_buffered?
106
+ @examples_buffer << event
107
+ @examples_ok = true if filter_match?(*@examples_buffer)
108
+ else
109
+ @examples_rows_buffer << event if @scenario_ok || @examples_ok || @feature_ok || filter_match?(event)
110
+ end
111
+ when :step
112
+ @scenario_buffer << event
113
+ @scenario_ok ||= filter_match?(*@scenario_buffer)
114
+ when :background
115
+ @feature_buffer += @meta_buffer
116
+ @feature_buffer << event
117
+ @meta_buffer = []
118
+ else
119
+ raise "Bad table_state:#{@table_state.inspect}"
120
+ end
121
+ when :py_string
122
+ if @table_state == :background
123
+ @feature_buffer << event
124
+ @feature_ok ||= filter_match?(*@feature_buffer)
125
+ else
126
+ @scenario_buffer << event
127
+ @scenario_ok ||= filter_match?(*@scenario_buffer)
128
+ end
129
+ when :eof
130
+ replay_examples_rows_buffer
131
+ event.replay(@listener)
132
+ return
133
+ else
134
+ super
135
+ end
136
+
137
+ if @scenario_ok || @examples_ok || @feature_ok || @background_ok
138
+ replay_buffers
139
+ end
140
+ end
141
+
142
+ def detect_filter(filters)
143
+ @filters = filters
144
+ raise "Bad filter: #{filters.inspect}" if filters.map{|filter| filter.class}.uniq.length > 1
145
+ @filter_method = case(filters[0])
146
+ when Fixnum
147
+ :line_match?
148
+ when Regexp
149
+ :name_match?
150
+ when String
151
+ TagExpression.new(filters)
152
+ end
153
+ end
154
+
155
+ def no_filters?
156
+ @filters.empty?
157
+ end
158
+
159
+ def header_row_already_buffered?
160
+ return @examples_buffer.any? && @examples_buffer[-1].event == :row
161
+ end
162
+
163
+ def filter_match?(*events)
164
+ return false unless[:name_match?, :line_match?].include?(@filter_method)
165
+ events.detect{|event| event.__send__(@filter_method, @filters)}
166
+ end
167
+
168
+ def tag_match?
169
+ return TagExpression === @filter_method && @filter_method.eval(current_tags)
170
+ end
171
+
172
+ def replay_buffers
173
+ (@feature_buffer + @scenario_buffer).each do |event|
174
+ event.replay(@listener)
175
+ end
176
+ @feature_buffer = []
177
+ @scenario_buffer = []
178
+ end
179
+
180
+ def replay_examples_rows_buffer
181
+ if @examples_rows_buffer.any?
182
+ replay_buffers
183
+ (@examples_buffer + @examples_rows_buffer).each do |event|
184
+ event.replay(@listener)
185
+ end
186
+ @examples_rows_buffer = []
187
+ end
188
+ end
189
+
190
+ def current_tags
191
+ @feature_tags + @scenario_tags + @example_tags
192
+ end
193
+
194
+ def extract_tags
195
+ @meta_buffer.select { |event| event.event == :tag }.map { |event| event.keyword }
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,5 @@
1
+ | | feature | background | scenario | scenario_outline | examples | step | row | py_string | eof | comment | tag |
2
+ | meta | E | E | E | E | E | E | E | E | eof | comment | tag |
3
+ | comment | pop() | pop() | pop() | pop() | pop() | pop() | pop() | pop() | eof | pop() | tag |
4
+ | tag | pop() | E | pop() | pop() | pop() | E | E | E | E | E | tag |
5
+ | eof | E | E | E | E | E | E | E | E | E | E | E |
@@ -0,0 +1,142 @@
1
+ require 'gherkin/native'
2
+
3
+ module Gherkin
4
+ module Parser
5
+ class ParseError < StandardError
6
+ def initialize(state, new_state, expected_states, line)
7
+ super("Parse error on line #{line}. Found #{new_state} when expecting one of: #{expected_states.join(', ')}. (Current state: #{state}).")
8
+ end
9
+ end
10
+
11
+ class Parser
12
+ native_impl('gherkin')
13
+
14
+ # Initialize the parser. +machine_name+ refers to a state machine table.
15
+ def initialize(listener, raise_on_error=true, machine_name='root')
16
+ @listener = listener
17
+ @raise_on_error = raise_on_error
18
+ @machines = []
19
+ @machine_name = machine_name
20
+ push_machine(@machine_name)
21
+ end
22
+
23
+ # Doesn't yet fall back to super
24
+ def method_missing(method, *args)
25
+ # TODO: Catch exception and call super
26
+ if(event(method.to_s, args[-1]))
27
+ @listener.send(method, *args)
28
+ end
29
+ if method == :eof
30
+ pop_machine
31
+ push_machine(@machine_name)
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)
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