gherkin 1.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +7 -0
  2. data/.mailmap +2 -0
  3. data/History.txt +9 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +38 -0
  6. data/Rakefile +48 -0
  7. data/VERSION.yml +4 -0
  8. data/bin/gherkin +5 -0
  9. data/cucumber.yml +3 -0
  10. data/features/feature_parser.feature +206 -0
  11. data/features/native_lexer.feature +19 -0
  12. data/features/parser_with_native_lexer.feature +205 -0
  13. data/features/pretty_printer.feature +14 -0
  14. data/features/step_definitions/gherkin_steps.rb +34 -0
  15. data/features/step_definitions/pretty_printer_steps.rb +56 -0
  16. data/features/steps_parser.feature +46 -0
  17. data/features/support/env.rb +33 -0
  18. data/gherkin.gemspec +155 -0
  19. data/java/.gitignore +2 -0
  20. data/java/Gherkin.iml +24 -0
  21. data/java/build.xml +13 -0
  22. data/java/src/gherkin/FixJava.java +34 -0
  23. data/java/src/gherkin/Lexer.java +5 -0
  24. data/java/src/gherkin/LexingError.java +7 -0
  25. data/java/src/gherkin/Listener.java +27 -0
  26. data/java/src/gherkin/ParseError.java +22 -0
  27. data/java/src/gherkin/Parser.java +185 -0
  28. data/java/src/gherkin/lexer/.gitignore +1 -0
  29. data/java/src/gherkin/parser/StateMachineReader.java +62 -0
  30. data/lib/.gitignore +4 -0
  31. data/lib/gherkin.rb +2 -0
  32. data/lib/gherkin/c_lexer.rb +10 -0
  33. data/lib/gherkin/cli/main.rb +34 -0
  34. data/lib/gherkin/core_ext/array.rb +5 -0
  35. data/lib/gherkin/i18n.rb +87 -0
  36. data/lib/gherkin/i18n.yml +535 -0
  37. data/lib/gherkin/i18n_lexer.rb +29 -0
  38. data/lib/gherkin/java_lexer.rb +10 -0
  39. data/lib/gherkin/lexer.rb +44 -0
  40. data/lib/gherkin/parser.rb +19 -0
  41. data/lib/gherkin/parser/meta.txt +4 -0
  42. data/lib/gherkin/parser/root.txt +9 -0
  43. data/lib/gherkin/parser/steps.txt +3 -0
  44. data/lib/gherkin/rb_lexer.rb +10 -0
  45. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  46. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  47. data/lib/gherkin/rb_parser.rb +117 -0
  48. data/lib/gherkin/tools.rb +8 -0
  49. data/lib/gherkin/tools/files.rb +30 -0
  50. data/lib/gherkin/tools/pretty_listener.rb +84 -0
  51. data/lib/gherkin/tools/reformat.rb +19 -0
  52. data/lib/gherkin/tools/stats.rb +21 -0
  53. data/lib/gherkin/tools/stats_listener.rb +50 -0
  54. data/nativegems.sh +5 -0
  55. data/ragel/i18n/.gitignore +1 -0
  56. data/ragel/lexer.c.rl.erb +403 -0
  57. data/ragel/lexer.java.rl.erb +200 -0
  58. data/ragel/lexer.rb.rl.erb +171 -0
  59. data/ragel/lexer_common.rl.erb +46 -0
  60. data/spec/gherkin/c_lexer_spec.rb +21 -0
  61. data/spec/gherkin/fixtures/1.feature +8 -0
  62. data/spec/gherkin/fixtures/complex.feature +43 -0
  63. data/spec/gherkin/fixtures/i18n_fr.feature +13 -0
  64. data/spec/gherkin/fixtures/i18n_no.feature +6 -0
  65. data/spec/gherkin/fixtures/i18n_zh-CN.feature +8 -0
  66. data/spec/gherkin/fixtures/simple.feature +3 -0
  67. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  68. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  69. data/spec/gherkin/i18n_lexer_spec.rb +22 -0
  70. data/spec/gherkin/i18n_spec.rb +57 -0
  71. data/spec/gherkin/java_lexer_spec.rb +20 -0
  72. data/spec/gherkin/parser_spec.rb +28 -0
  73. data/spec/gherkin/rb_lexer_spec.rb +18 -0
  74. data/spec/gherkin/sexp_recorder.rb +29 -0
  75. data/spec/gherkin/shared/lexer_spec.rb +433 -0
  76. data/spec/gherkin/shared/py_string_spec.rb +124 -0
  77. data/spec/gherkin/shared/table_spec.rb +97 -0
  78. data/spec/gherkin/shared/tags_spec.rb +50 -0
  79. data/spec/spec_helper.rb +53 -0
  80. data/tasks/bench.rake +186 -0
  81. data/tasks/bench/feature_builder.rb +49 -0
  82. data/tasks/bench/generated/.gitignore +1 -0
  83. data/tasks/bench/null_listener.rb +4 -0
  84. data/tasks/compile.rake +70 -0
  85. data/tasks/cucumber.rake +20 -0
  86. data/tasks/ragel_task.rb +70 -0
  87. data/tasks/rdoc.rake +12 -0
  88. data/tasks/rspec.rake +15 -0
  89. 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,10 @@
1
+ require 'gherkin.jar'
2
+
3
+ module Gherkin
4
+ module JavaLexer
5
+ def self.[](i18n_language)
6
+ i18n_lexer_class_name = i18n_language.gsub(/[\s-]/, '').capitalize
7
+ Java::GherkinLexer.__send__(i18n_lexer_class_name)
8
+ end
9
+ end
10
+ 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,3 @@
1
+ | | feature | background | scenario | scenario_outline | examples | step | table | py_string | comment | tag |
2
+ | steps | E | E | E | E | E | step | E | E | E | E |
3
+ | step | E | E | E | E | E | step | steps | steps | E | E |
@@ -0,0 +1,10 @@
1
+ module Gherkin
2
+ module RbLexer
3
+ def self.[](i18n_language)
4
+ name = i18n_language.gsub(/[\s-]/, '')
5
+ require "gherkin/rb_lexer/#{name}"
6
+ i18n_lexer_class_name = name.capitalize
7
+ const_get(i18n_lexer_class_name)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ *.rb
@@ -0,0 +1,8 @@
1
+ = Lexers
2
+
3
+ Gherkin support lexing of lots of natural languages, defined by gherkin/i18n.yml
4
+ The lexers are generated with the following command:
5
+
6
+ rake ragel:i18n
7
+
8
+ You have to run this command if you modify gherkin/i18n.yml
@@ -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,8 @@
1
+ module Gherkin
2
+ module Tools
3
+ SUB_COMMANDS = %w(stats reformat)
4
+ SUB_COMMANDS.each do |cmd|
5
+ autoload cmd.capitalize.to_sym, "gherkin/tools/#{cmd}"
6
+ end
7
+ end
8
+ 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