gherkin 1.0.2-java
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/.gitignore +7 -0
- data/.mailmap +2 -0
- data/History.txt +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +38 -0
- data/Rakefile +48 -0
- data/VERSION.yml +4 -0
- data/bin/gherkin +5 -0
- data/cucumber.yml +3 -0
- data/features/feature_parser.feature +206 -0
- data/features/native_lexer.feature +19 -0
- data/features/parser_with_native_lexer.feature +205 -0
- data/features/pretty_printer.feature +14 -0
- data/features/step_definitions/gherkin_steps.rb +34 -0
- data/features/step_definitions/pretty_printer_steps.rb +56 -0
- data/features/steps_parser.feature +46 -0
- data/features/support/env.rb +33 -0
- data/gherkin.gemspec +155 -0
- data/java/.gitignore +2 -0
- data/java/Gherkin.iml +24 -0
- data/java/build.xml +13 -0
- data/java/src/gherkin/FixJava.java +34 -0
- data/java/src/gherkin/Lexer.java +5 -0
- data/java/src/gherkin/LexingError.java +7 -0
- data/java/src/gherkin/Listener.java +27 -0
- data/java/src/gherkin/ParseError.java +22 -0
- data/java/src/gherkin/Parser.java +185 -0
- data/java/src/gherkin/lexer/.gitignore +1 -0
- data/java/src/gherkin/parser/StateMachineReader.java +62 -0
- data/lib/.gitignore +4 -0
- data/lib/gherkin.rb +2 -0
- data/lib/gherkin/c_lexer.rb +10 -0
- data/lib/gherkin/cli/main.rb +34 -0
- data/lib/gherkin/core_ext/array.rb +5 -0
- data/lib/gherkin/i18n.rb +87 -0
- data/lib/gherkin/i18n.yml +535 -0
- data/lib/gherkin/i18n_lexer.rb +29 -0
- data/lib/gherkin/java_lexer.rb +10 -0
- data/lib/gherkin/lexer.rb +44 -0
- data/lib/gherkin/parser.rb +19 -0
- data/lib/gherkin/parser/meta.txt +4 -0
- data/lib/gherkin/parser/root.txt +9 -0
- data/lib/gherkin/parser/steps.txt +3 -0
- data/lib/gherkin/rb_lexer.rb +10 -0
- data/lib/gherkin/rb_lexer/.gitignore +1 -0
- data/lib/gherkin/rb_lexer/README.rdoc +8 -0
- data/lib/gherkin/rb_parser.rb +117 -0
- data/lib/gherkin/tools.rb +8 -0
- data/lib/gherkin/tools/files.rb +30 -0
- data/lib/gherkin/tools/pretty_listener.rb +84 -0
- data/lib/gherkin/tools/reformat.rb +19 -0
- data/lib/gherkin/tools/stats.rb +21 -0
- data/lib/gherkin/tools/stats_listener.rb +50 -0
- data/nativegems.sh +5 -0
- data/ragel/i18n/.gitignore +1 -0
- data/ragel/lexer.c.rl.erb +403 -0
- data/ragel/lexer.java.rl.erb +200 -0
- data/ragel/lexer.rb.rl.erb +171 -0
- data/ragel/lexer_common.rl.erb +46 -0
- data/spec/gherkin/c_lexer_spec.rb +21 -0
- data/spec/gherkin/fixtures/1.feature +8 -0
- data/spec/gherkin/fixtures/complex.feature +43 -0
- data/spec/gherkin/fixtures/i18n_fr.feature +13 -0
- data/spec/gherkin/fixtures/i18n_no.feature +6 -0
- data/spec/gherkin/fixtures/i18n_zh-CN.feature +8 -0
- data/spec/gherkin/fixtures/simple.feature +3 -0
- data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
- data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
- data/spec/gherkin/i18n_lexer_spec.rb +22 -0
- data/spec/gherkin/i18n_spec.rb +57 -0
- data/spec/gherkin/java_lexer_spec.rb +20 -0
- data/spec/gherkin/parser_spec.rb +28 -0
- data/spec/gherkin/rb_lexer_spec.rb +18 -0
- data/spec/gherkin/sexp_recorder.rb +29 -0
- data/spec/gherkin/shared/lexer_spec.rb +433 -0
- data/spec/gherkin/shared/py_string_spec.rb +124 -0
- data/spec/gherkin/shared/table_spec.rb +97 -0
- data/spec/gherkin/shared/tags_spec.rb +50 -0
- data/spec/spec_helper.rb +53 -0
- data/tasks/bench.rake +186 -0
- data/tasks/bench/feature_builder.rb +49 -0
- data/tasks/bench/generated/.gitignore +1 -0
- data/tasks/bench/null_listener.rb +4 -0
- data/tasks/compile.rake +70 -0
- data/tasks/cucumber.rake +20 -0
- data/tasks/ragel_task.rb +70 -0
- data/tasks/rdoc.rake +12 -0
- data/tasks/rspec.rake +15 -0
- metadata +196 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'gherkin/lexer'
|
2
|
+
require 'gherkin/i18n'
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
# The main entry point to lexing Gherkin source.
|
6
|
+
class I18nLexer
|
7
|
+
LANGUAGE_PATTERN = /language\s*:\s*(.*)/ #:nodoc:
|
8
|
+
|
9
|
+
attr_reader :language
|
10
|
+
|
11
|
+
def initialize(parser)
|
12
|
+
@parser = parser
|
13
|
+
end
|
14
|
+
|
15
|
+
def scan(source)
|
16
|
+
@language = lang(source)
|
17
|
+
delegate = Lexer[@language.key].new(@parser)
|
18
|
+
delegate.scan(source)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def lang(source)
|
24
|
+
line_one = source.split(/\n/)[0]
|
25
|
+
match = LANGUAGE_PATTERN.match(line_one)
|
26
|
+
I18n.get(match ? match[1] : 'en')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gherkin
|
2
|
+
module Lexer
|
3
|
+
I18nLexerNotFound = Class.new(LoadError)
|
4
|
+
LexingError = Class.new(StandardError)
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def [](i18n_lang)
|
8
|
+
begin
|
9
|
+
# Uncomment the line below (during development) to force use of Ruby lexer
|
10
|
+
# return rb[i18n_lang]
|
11
|
+
|
12
|
+
if defined?(JRUBY_VERSION)
|
13
|
+
java[i18n_lang]
|
14
|
+
else
|
15
|
+
begin
|
16
|
+
c[i18n_lang]
|
17
|
+
rescue NameError, LoadError => e
|
18
|
+
warn("WARNING: #{e.message}. Reverting to Ruby lexer.") unless defined?(@warned)
|
19
|
+
@warned = true
|
20
|
+
rb[i18n_lang]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue LoadError => e
|
24
|
+
raise I18nLexerNotFound, "No lexer was found for #{i18n_lang} (#{e.message}). Supported languages are listed in gherkin/i18n.yml."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def c
|
29
|
+
require 'gherkin/c_lexer'
|
30
|
+
CLexer
|
31
|
+
end
|
32
|
+
|
33
|
+
def java
|
34
|
+
require 'gherkin/java_lexer'
|
35
|
+
JavaLexer
|
36
|
+
end
|
37
|
+
|
38
|
+
def rb
|
39
|
+
require 'gherkin/rb_lexer'
|
40
|
+
RbLexer
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Gherkin
|
2
|
+
class ParseError < StandardError
|
3
|
+
def initialize(state, new_state, expected_states, line)
|
4
|
+
super("Parse error on line #{line}. Found #{new_state} when expecting one of: #{expected_states.join(', ')}. (Current state: #{state}).")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
def self.new(listener, raise_on_error=false, machine_names='root')
|
10
|
+
if defined?(JRUBY_VERSION)
|
11
|
+
require 'gherkin.jar'
|
12
|
+
Java::Gherkin::Parser.new(listener, raise_on_error, machine_names)
|
13
|
+
else
|
14
|
+
require 'gherkin/rb_parser'
|
15
|
+
Gherkin::RbParser.new(listener, raise_on_error, machine_names)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
| | feature | background | scenario | scenario_outline | examples | step | table | py_string | comment | tag |
|
2
|
+
| meta | E | E | E | E | E | E | E | E | comment | tag |
|
3
|
+
| comment | pop() | pop() | pop() | pop() | pop() | pop() | pop() | pop() | pop() | tag |
|
4
|
+
| tag | pop() | E | pop() | pop() | pop() | E | E | E | E | tag |
|
@@ -0,0 +1,9 @@
|
|
1
|
+
| | feature | background | scenario | scenario_outline | examples | step | table | py_string | comment | tag |
|
2
|
+
| root | feature | E | E | E | E | E | E | E | push(meta) | push(meta) |
|
3
|
+
| feature | E | background | scenario | scenario_outline | E | E | E | E | push(meta) | push(meta) |
|
4
|
+
| step | E | E | scenario | scenario_outline | examples | step | step | step | push(meta) | push(meta) |
|
5
|
+
| background | E | E | scenario | scenario_outline | E | step | E | E | push(meta) | push(meta) |
|
6
|
+
| scenario | E | E | scenario | scenario_outline | E | step | E | E | push(meta) | push(meta) |
|
7
|
+
| scenario_outline | E | E | E | E | E | step | E | E | push(meta) | push(meta) |
|
8
|
+
| examples | E | E | E | E | E | E | examples_table | E | push(meta) | push(meta) |
|
9
|
+
| examples_table | E | E | scenario | scenario_outline | examples | E | E | E | push(meta) | push(meta) |
|
@@ -0,0 +1 @@
|
|
1
|
+
*.rb
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Gherkin
|
2
|
+
class RbParser
|
3
|
+
# Initialize the parser. +machine_name+ refers to a state machine table.
|
4
|
+
def initialize(listener, raise_on_error, machine_name)
|
5
|
+
@listener = listener
|
6
|
+
@raise_on_error = raise_on_error
|
7
|
+
@machines = []
|
8
|
+
push_machine(machine_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Doesn't yet fall back to super
|
12
|
+
def method_missing(method, *args)
|
13
|
+
# TODO: Catch exception and call super
|
14
|
+
if(event(method.to_s, args[-1]))
|
15
|
+
@listener.send(method, *args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def event(ev, line)
|
20
|
+
machine.event(ev, line) do |state, expected|
|
21
|
+
if @raise_on_error
|
22
|
+
raise ParseError.new(state, ev, expected, line)
|
23
|
+
else
|
24
|
+
@listener.syntax_error(state, ev, expected, line)
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def push_machine(name)
|
32
|
+
@machines.push(Machine.new(self, name))
|
33
|
+
end
|
34
|
+
|
35
|
+
def pop_machine
|
36
|
+
@machines.pop
|
37
|
+
end
|
38
|
+
|
39
|
+
def machine
|
40
|
+
@machines[-1]
|
41
|
+
end
|
42
|
+
|
43
|
+
def expected
|
44
|
+
machine.expected
|
45
|
+
end
|
46
|
+
|
47
|
+
def force_state(state)
|
48
|
+
machine.instance_variable_set('@state', state)
|
49
|
+
end
|
50
|
+
|
51
|
+
class Machine
|
52
|
+
def initialize(parser, name)
|
53
|
+
@parser = parser
|
54
|
+
@name = name
|
55
|
+
@transition_map = transition_map(name)
|
56
|
+
@state = name
|
57
|
+
end
|
58
|
+
|
59
|
+
def event(ev, line)
|
60
|
+
states = @transition_map[@state]
|
61
|
+
raise "Unknown state: #{@state.inspect} for machine #{@name}" if states.nil?
|
62
|
+
new_state = states[ev]
|
63
|
+
case new_state
|
64
|
+
when "E"
|
65
|
+
yield @state, expected
|
66
|
+
when /push\((.+)\)/
|
67
|
+
@parser.push_machine($1)
|
68
|
+
@parser.event(ev, line)
|
69
|
+
when "pop()"
|
70
|
+
@parser.pop_machine()
|
71
|
+
@parser.event(ev, line)
|
72
|
+
else
|
73
|
+
raise "Unknown transition: #{ev.inspect} among #{states.inspect} for machine #{@name}" if new_state.nil?
|
74
|
+
@state = new_state
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def expected
|
79
|
+
allowed = @transition_map[@state].find_all { |_, action| action != "E" }
|
80
|
+
allowed.collect { |state| state[0] }.sort
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
@@transition_maps = {}
|
86
|
+
|
87
|
+
def transition_map(name)
|
88
|
+
@@transition_maps[name] ||= build_transition_map(name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_transition_map(name)
|
92
|
+
table = transition_table(name)
|
93
|
+
events = table.shift[1..-1]
|
94
|
+
table.inject({}) do |machine, actions|
|
95
|
+
state = actions.shift
|
96
|
+
machine[state] = Hash[*events.zip(actions).flatten]
|
97
|
+
machine
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def transition_table(name)
|
102
|
+
state_machine_reader = StateMachineReader.new
|
103
|
+
lexer = Gherkin::Lexer['en'].new(state_machine_reader)
|
104
|
+
lexer.scan(File.read(File.dirname(__FILE__) + "/parser/#{name}.txt"))
|
105
|
+
state_machine_reader.rows
|
106
|
+
end
|
107
|
+
|
108
|
+
class StateMachineReader
|
109
|
+
attr_reader :rows
|
110
|
+
def table(rows, line_number)
|
111
|
+
@rows = rows
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'gherkin'
|
2
|
+
|
3
|
+
module Gherkin
|
4
|
+
module Tools
|
5
|
+
# Base class for file based operations
|
6
|
+
class Files
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(paths)
|
10
|
+
raise "Please specify one or more paths" if paths.empty?
|
11
|
+
@paths = paths
|
12
|
+
end
|
13
|
+
|
14
|
+
def each(&proc)
|
15
|
+
globs = @paths.map do |path|
|
16
|
+
raise "#{path} does not exist" unless File.exist?(path)
|
17
|
+
File.directory?(path) ? File.join(path, '**', '*.feature') : path
|
18
|
+
end
|
19
|
+
|
20
|
+
Dir[*globs].uniq.sort.each(&proc)
|
21
|
+
end
|
22
|
+
|
23
|
+
def scan(file, listener)
|
24
|
+
parser = Parser.new(listener, true)
|
25
|
+
lexer = I18nLexer.new(parser)
|
26
|
+
lexer.scan(IO.read(file))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Gherkin
|
3
|
+
module Tools
|
4
|
+
class PrettyListener
|
5
|
+
def initialize(io)
|
6
|
+
@io = io
|
7
|
+
@tags = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def tag(name, line)
|
11
|
+
@tags ||= []
|
12
|
+
@tags << "@#{name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def comment(content, line)
|
16
|
+
@io.puts content
|
17
|
+
end
|
18
|
+
|
19
|
+
def feature(keyword, name, line)
|
20
|
+
tags = @tags ? @tags.join(' ') + "\n" : ''
|
21
|
+
@tags = nil
|
22
|
+
@io.puts "#{tags}#{keyword}: #{indent(name, ' ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def background(keyword, name, line)
|
26
|
+
@io.puts "\n #{keyword}: #{indent(name, ' ')}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def scenario(keyword, name, line)
|
30
|
+
tags = @tags ? ' ' + @tags.join(' ') + "\n" : ''
|
31
|
+
@tags = nil
|
32
|
+
@io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def scenario_outline(keyword, name, line)
|
36
|
+
tags = @tags ? ' ' + @tags.join(' ') + "\n" : ''
|
37
|
+
@tags = nil
|
38
|
+
@io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def examples(keyword, name, line)
|
42
|
+
@io.puts "\n #{keyword}: #{indent(name, ' ')}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def step(keyword, name, line)
|
46
|
+
@io.puts " #{keyword} #{indent(name, ' ')}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def table(rows, line)
|
50
|
+
rows = rows.to_a.map {|row| row.to_a} if defined?(JRUBY_VERSION) # Convert ArrayList
|
51
|
+
max_lengths = rows.transpose.map { |col| col.map { |cell| cell.unpack("U*").length }.max }.flatten
|
52
|
+
rows.each do |table_line|
|
53
|
+
@io.puts ' | ' + table_line.zip(max_lengths).map { |cell, max_length| cell + ' ' * (max_length-cell.unpack("U*").length) }.join(' | ') + ' |'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def py_string(string, line)
|
58
|
+
@io.puts " \"\"\"\n" + string.gsub(START, ' ') + "\n \"\"\""
|
59
|
+
end
|
60
|
+
|
61
|
+
def syntax_error(state, event, legal_events, line)
|
62
|
+
raise "SYNTAX ERROR"
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
if(RUBY_VERSION =~ /^1\.9/)
|
67
|
+
START = /#{"^".encode('UTF-8')}/
|
68
|
+
NL = Regexp.new("\n".encode('UTF-8'))
|
69
|
+
else
|
70
|
+
START = /^/
|
71
|
+
NL = /\n/n
|
72
|
+
end
|
73
|
+
|
74
|
+
def indent(string, indentation)
|
75
|
+
indent = ""
|
76
|
+
string.split(NL).map do |l|
|
77
|
+
s = "#{indent}#{l}"
|
78
|
+
indent = indentation
|
79
|
+
s
|
80
|
+
end.join("\n")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'gherkin/tools/files'
|
3
|
+
require 'gherkin/tools/pretty_listener'
|
4
|
+
|
5
|
+
module Gherkin
|
6
|
+
module Tools
|
7
|
+
class Reformat < Files
|
8
|
+
def run
|
9
|
+
each do |file|
|
10
|
+
purdy = StringIO.new
|
11
|
+
listener = PrettyListener.new(purdy)
|
12
|
+
scan(file, listener)
|
13
|
+
purdy.rewind
|
14
|
+
File.open(file, 'w') {|io| io.write(purdy.read)}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'gherkin'
|
2
|
+
require 'gherkin/tools/files'
|
3
|
+
require 'gherkin/tools/stats_listener'
|
4
|
+
|
5
|
+
module Gherkin
|
6
|
+
module Tools
|
7
|
+
class Stats < Files
|
8
|
+
def run
|
9
|
+
listener = StatsListener.new
|
10
|
+
each do |f|
|
11
|
+
parser = Gherkin::Parser.new(listener, true)
|
12
|
+
lexer = Gherkin::I18nLexer.new(parser)
|
13
|
+
lexer.scan(IO.read(f))
|
14
|
+
end
|
15
|
+
puts "Features: #{listener.features}"
|
16
|
+
puts "Scenarios: #{listener.scenarios}"
|
17
|
+
puts "Steps: #{listener.steps}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Gherkin
|
3
|
+
module Tools
|
4
|
+
class StatsListener
|
5
|
+
attr_reader :features, :scenarios, :steps
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@features = 0
|
9
|
+
@scenarios = 0
|
10
|
+
@steps = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def tag(name, line)
|
14
|
+
end
|
15
|
+
|
16
|
+
def comment(content, line)
|
17
|
+
end
|
18
|
+
|
19
|
+
def feature(keyword, name, line)
|
20
|
+
@features += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def background(keyword, name, line)
|
24
|
+
end
|
25
|
+
|
26
|
+
def scenario(keyword, name, line)
|
27
|
+
@scenarios += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def scenario_outline(keyword, name, line)
|
31
|
+
end
|
32
|
+
|
33
|
+
def examples(keyword, name, line)
|
34
|
+
end
|
35
|
+
|
36
|
+
def step(keyword, name, line)
|
37
|
+
@steps += 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def table(rows, line)
|
41
|
+
end
|
42
|
+
|
43
|
+
def py_string(string, line)
|
44
|
+
end
|
45
|
+
|
46
|
+
def syntax_error(state, event, legal_events, line)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|