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.
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