gherkin 1.0.2-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|