gherkin 0.0.3-universal-java-1.5

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 (78) hide show
  1. data/.gitignore +8 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +47 -0
  4. data/Rakefile +48 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/gherkin +10 -0
  7. data/cucumber.yml +3 -0
  8. data/ext/gherkin_lexer/.gitignore +6 -0
  9. data/ext/gherkin_lexer/extconf.rb +6 -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 +11 -0
  14. data/features/step_definitions/gherkin_steps.rb +34 -0
  15. data/features/step_definitions/pretty_printer_steps.rb +51 -0
  16. data/features/steps_parser.feature +46 -0
  17. data/features/support/env.rb +33 -0
  18. data/gherkin.gemspec +177 -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 +2 -0
  31. data/lib/gherkin.rb +2 -0
  32. data/lib/gherkin/c_lexer.rb +10 -0
  33. data/lib/gherkin/i18n.yml +535 -0
  34. data/lib/gherkin/i18n_lexer.rb +29 -0
  35. data/lib/gherkin/java_lexer.rb +10 -0
  36. data/lib/gherkin/lexer.rb +42 -0
  37. data/lib/gherkin/parser.rb +19 -0
  38. data/lib/gherkin/parser/meta.txt +4 -0
  39. data/lib/gherkin/parser/root.txt +9 -0
  40. data/lib/gherkin/parser/steps.txt +3 -0
  41. data/lib/gherkin/rb_lexer.rb +9 -0
  42. data/lib/gherkin/rb_lexer/.gitignore +1 -0
  43. data/lib/gherkin/rb_lexer/README.rdoc +8 -0
  44. data/lib/gherkin/rb_parser.rb +117 -0
  45. data/lib/gherkin/tools/pretty_printer.rb +77 -0
  46. data/ragel/i18n/.gitignore +1 -0
  47. data/ragel/lexer.c.rl.erb +385 -0
  48. data/ragel/lexer.java.rl.erb +198 -0
  49. data/ragel/lexer.rb.rl.erb +172 -0
  50. data/ragel/lexer_common.rl.erb +46 -0
  51. data/spec/gherkin/c_lexer_spec.rb +21 -0
  52. data/spec/gherkin/fixtures/complex.feature +43 -0
  53. data/spec/gherkin/fixtures/i18n_fr.feature +13 -0
  54. data/spec/gherkin/fixtures/i18n_no.feature +6 -0
  55. data/spec/gherkin/fixtures/i18n_zh-CN.feature +8 -0
  56. data/spec/gherkin/fixtures/simple.feature +3 -0
  57. data/spec/gherkin/fixtures/simple_with_comments.feature +7 -0
  58. data/spec/gherkin/fixtures/simple_with_tags.feature +11 -0
  59. data/spec/gherkin/i18n_spec.rb +57 -0
  60. data/spec/gherkin/java_lexer_spec.rb +20 -0
  61. data/spec/gherkin/parser_spec.rb +28 -0
  62. data/spec/gherkin/rb_lexer_spec.rb +18 -0
  63. data/spec/gherkin/sexp_recorder.rb +29 -0
  64. data/spec/gherkin/shared/lexer_spec.rb +420 -0
  65. data/spec/gherkin/shared/py_string_spec.rb +112 -0
  66. data/spec/gherkin/shared/table_spec.rb +97 -0
  67. data/spec/gherkin/shared/tags_spec.rb +50 -0
  68. data/spec/spec_helper.rb +53 -0
  69. data/tasks/bench.rake +176 -0
  70. data/tasks/bench/feature_builder.rb +49 -0
  71. data/tasks/bench/generated/.gitignore +1 -0
  72. data/tasks/bench/null_listener.rb +4 -0
  73. data/tasks/cucumber.rake +20 -0
  74. data/tasks/ext.rake +49 -0
  75. data/tasks/ragel.rake +94 -0
  76. data/tasks/rdoc.rake +12 -0
  77. data/tasks/rspec.rake +15 -0
  78. metadata +204 -0
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing py_strings" do
7
+
8
+ def ps(content)
9
+ '"""%s"""' % ("\n" + content + "\n")
10
+ end
11
+
12
+ it "should provide the amount of indentation of the triple quotes to the listener" do
13
+ str = <<EOS
14
+ Feature: some feature
15
+ Scenario: some scenario
16
+ Given foo
17
+ """
18
+ Hello
19
+ Goodbye
20
+ """
21
+ Then bar
22
+ EOS
23
+ @listener.should_receive(:py_string).with(" Hello\nGoodbye", 4)
24
+ @lexer.scan(str)
25
+ end
26
+
27
+ it "should parse a simple py_string" do
28
+ @listener.should_receive(:py_string).with("I am a py_string", 1)
29
+ @lexer.scan ps("I am a py_string")
30
+ end
31
+
32
+ it "should parse an empty py_string" do
33
+ @listener.should_receive(:py_string).with("", 4)
34
+ @lexer.scan("Feature: Hi\nScenario: Hi\nGiven a step\n\"\"\"\n\"\"\"")
35
+ end
36
+
37
+ it "should treat a string containing only newlines as only newlines" do
38
+ py_string = <<EOS
39
+ """
40
+
41
+
42
+
43
+ """
44
+ EOS
45
+ @listener.should_receive(:py_string).with("\n\n", 1)
46
+ @lexer.scan(py_string)
47
+ end
48
+
49
+ it "should parse content separated by two newlines" do
50
+ @lexer.scan ps("A\n\nB")
51
+ @listener.to_sexp.should == [
52
+ [:py_string, "A\n\nB", 1],
53
+ ]
54
+ end
55
+
56
+ it "should parse a multiline string" do
57
+ @listener.should_receive(:py_string).with("A\nB\nC\nD", 1)
58
+ @lexer.scan ps("A\nB\nC\nD")
59
+ end
60
+
61
+ it "should ignore unescaped quotes inside the string delimeters" do
62
+ @listener.should_receive(:py_string).with("What does \"this\" mean?", 1)
63
+ @lexer.scan ps('What does "this" mean?')
64
+ end
65
+
66
+ it "should preserve whitespace within the triple quotes" do
67
+ str = <<EOS
68
+ """
69
+ Line one
70
+ Line two
71
+ """
72
+ EOS
73
+ @listener.should_receive(:py_string).with(" Line one\nLine two", 1)
74
+ @lexer.scan(str)
75
+ end
76
+
77
+ it "should preserve tabs within the content" do
78
+ @listener.should_receive(:py_string).with("I have\tsome tabs\nInside\t\tthe content", 1)
79
+ @lexer.scan ps("I have\tsome tabs\nInside\t\tthe content")
80
+ end
81
+
82
+ it "should handle complex py_strings" do
83
+ py_string = <<EOS
84
+ # Feature comment
85
+ @one
86
+ Feature: Sample
87
+
88
+ @two @three
89
+ Scenario: Missing
90
+ Given missing
91
+
92
+ 1 scenario (1 passed)
93
+ 1 step (1 passed)
94
+
95
+ EOS
96
+
97
+ @listener.should_receive(:py_string).with(py_string, 1)
98
+ @lexer.scan ps(py_string)
99
+ end
100
+
101
+ it "should allow whitespace after the closing py_string delimiter" do
102
+ str = <<EOS
103
+ """
104
+ Line one
105
+ """
106
+ EOS
107
+ @listener.should_receive(:py_string).with(" Line one", 1)
108
+ @lexer.scan(str)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,97 @@
1
+ #encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing tables" do
7
+ tables = {
8
+ "|a|b|\n" => [%w{a b}],
9
+ "|a|b|c|\n" => [%w{a b c}],
10
+ "|c|d|\n|e|f|\n" => [%w{c d}, %w{e f}]
11
+ }
12
+
13
+ tables.each do |text, expected|
14
+ it "should parse #{text}" do
15
+ @listener.should_receive(:table).with(t(expected), 1)
16
+ @lexer.scan(text.dup)
17
+ end
18
+ end
19
+
20
+ it "should parse a table with many columns" do
21
+ @listener.should_receive(:table).with(t([%w{a b c d e f g h i j k l m n o p}]), 1)
22
+ @lexer.scan("|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|\n")
23
+ end
24
+
25
+ it "should parse a multicharacter cell content" do
26
+ @listener.should_receive(:table).with(t([%w{foo bar}]), 1)
27
+ @lexer.scan("| foo | bar |\n")
28
+ end
29
+
30
+ it "should parse cells with spaces within the content" do
31
+ @listener.should_receive(:table).with(t([["Dill pickle", "Valencia orange"], ["Ruby red grapefruit", "Tire iron"]]), 1)
32
+ @lexer.scan("| Dill pickle | Valencia orange |\n| Ruby red grapefruit | Tire iron |\n")
33
+ end
34
+
35
+ it "should allow utf-8" do
36
+ # Fails in 1.9.1!
37
+ # 'Gherkin::Lexer::Table should allow utf-8 with using == to evaluate' FAILED
38
+ # expected: [[:table, [["ůﻚ", "2"]], 1]],
39
+ # got: [[:table, [["\xC5\xAF\xEF\xBB\x9A", "2"]], 1]] (using ==)
40
+ # BUT, simply running:
41
+ # [[:table, [["ůﻚ", "2"]], 1]].should == [[:table, [["\xC5\xAF\xEF\xBB\x9A", "2"]], 1]]
42
+ # passes
43
+ #
44
+ @lexer.scan(" | ůﻚ | 2 | \n")
45
+ @listener.to_sexp.should == [
46
+ [:table, [["ůﻚ", "2"]], 1]
47
+ ]
48
+ end
49
+
50
+ it "should allow utf-8 using should_receive" do
51
+ @listener.should_receive(:table).with(t([['繁體中文 而且','並且','繁體中文 而且','並且']]), 1)
52
+ @lexer.scan("| 繁體中文 而且|並且| 繁體中文 而且|並且|\n")
53
+ end
54
+
55
+ it "should parse a 2x2 table" do
56
+ @listener.should_receive(:table).with(t([%w{1 2}, %w{3 4}]), 1)
57
+ @lexer.scan("| 1 | 2 |\n| 3 | 4 |\n")
58
+ end
59
+
60
+ it "should parse a 2x2 table with several newlines" do
61
+ @listener.should_receive(:table).with(t([%w{1 2}, %w{3 4}]), 1)
62
+ @lexer.scan("| 1 | 2 |\n| 3 | 4 |\n\n\n")
63
+ end
64
+
65
+ it "should parse a 2x2 table with empty cells" do
66
+ @listener.should_receive(:table).with(t([['1', ''], ['', '4']]), 1)
67
+ @lexer.scan("| 1 | |\n|| 4 |\n")
68
+ end
69
+
70
+ it "should parse a 1x2 table that does not end in a newline" do
71
+ @listener.should_receive(:table).with(t([%w{1 2}]), 1)
72
+ @lexer.scan("| 1 | 2 |")
73
+ end
74
+
75
+ it "should parse a 1x2 table without spaces and newline" do
76
+ @listener.should_receive(:table).with(t([%w{1 2}]), 1)
77
+ @lexer.scan("|1|2|\n")
78
+ end
79
+
80
+ it "should parse a row with whitespace after the rows" do
81
+ @listener.should_receive(:table).with(t([%w{1 2}, %w{a b}]), 1)
82
+ @lexer.scan("| 1 | 2 | \n | a | b | \n")
83
+ end
84
+
85
+ it "should parse a table with lots of whitespace" do
86
+ @listener.should_receive(:table).with(t([["abc", "123"]]), 1)
87
+ @lexer.scan(" \t| \t abc\t| \t123\t \t\t| \t\t \t \t\n ")
88
+ end
89
+
90
+ it "should raise LexingError for rows that aren't closed" do
91
+ lambda {
92
+ @lexer.scan("|| oh hello \n")
93
+ }.should raise_error(/Parsing error on line 1: '|| oh hello/)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,50 @@
1
+ #encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ module Gherkin
5
+ module Lexer
6
+ shared_examples_for "a Gherkin lexer lexing tags" do
7
+ it "should lex a single tag" do
8
+ @listener.should_receive(:tag).with("dog", 1)
9
+ @lexer.scan("@dog\n")
10
+ end
11
+
12
+ it "should lex multiple tags" do
13
+ @listener.should_receive(:tag).twice
14
+ @lexer.scan("@dog @cat\n")
15
+ end
16
+
17
+ it "should lex UTF-8 tags" do
18
+ @listener.should_receive(:tag).with("シナリオテンプレート", 1)
19
+ @lexer.scan("@シナリオテンプレート\n")
20
+ end
21
+
22
+ it "should lex mixed tags" do
23
+ @listener.should_receive(:tag).with("wip", 1).ordered
24
+ @listener.should_receive(:tag).with("Значения", 1).ordered
25
+ @lexer.scan("@wip @Значения\n")
26
+ end
27
+
28
+ it "should lex wacky identifiers" do
29
+ @listener.should_receive(:tag).exactly(4).times
30
+ @lexer.scan("@BJ-x98.77 @BJ-z12.33 @O_o" "@#not_a_comment\n")
31
+ end
32
+
33
+ # TODO: Ask on ML for opinions about this one
34
+ it "should lex tags without spaces between them?" do
35
+ @listener.should_receive(:tag).twice
36
+ @lexer.scan("@one@two\n")
37
+ end
38
+
39
+ it "should not lex tags beginning with two @@ signs" do
40
+ @listener.should_not_receive(:tag)
41
+ lambda { @lexer.scan("@@test\n") }.should raise_error(/Lexing error on line 1/)
42
+ end
43
+
44
+ it "should not lex a lone @ sign" do
45
+ @listener.should_not_receive(:tag)
46
+ lambda { @lexer.scan("@\n") }.should raise_error(/Lexing error on line 1/)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'spec/gherkin'))
4
+ require 'gherkin'
5
+ require 'gherkin/sexp_recorder'
6
+ require 'rubygems'
7
+ require 'spec'
8
+ require 'spec/autorun'
9
+ require 'shared/lexer_spec'
10
+ require 'shared/tags_spec'
11
+ require 'shared/py_string_spec'
12
+ require 'shared/table_spec'
13
+
14
+ module GherkinSpecHelper
15
+ def scan_file(file)
16
+ @lexer.scan(File.new(File.dirname(__FILE__) + "/gherkin/fixtures/" + file).read)
17
+ end
18
+ end
19
+
20
+ Spec::Runner.configure do |c|
21
+ c.include(GherkinSpecHelper)
22
+ end
23
+
24
+ # Allows comparison of Java List with Ruby Array (tables)
25
+ Spec::Matchers.define :t do |expected|
26
+ match do |table|
27
+ def table.inspect
28
+ "t " + self.map{|row| row.map{|cell| cell}}.inspect
29
+ end
30
+ table.map{|row| row.map{|cell| cell}}.should == expected
31
+ end
32
+ end
33
+
34
+ Spec::Matchers.define :a do |expected|
35
+ match do |array|
36
+ def array.inspect
37
+ "a " + self.map{|e| e.to_sym}.inspect
38
+ end
39
+ array.map{|e| e.to_sym}.should == expected
40
+ end
41
+ end
42
+
43
+ Spec::Matchers.define :sym do |expected|
44
+ match do |actual|
45
+ expected.to_s == actual.to_s
46
+ end
47
+ end
48
+
49
+ Spec::Matchers.define :allow do |event|
50
+ match do |parser|
51
+ parser.expected.index(event)
52
+ end
53
+ end
@@ -0,0 +1,176 @@
1
+ %w{/../lib /bench}.each do |l|
2
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + l)
3
+ end
4
+
5
+ require 'benchmark'
6
+
7
+ GENERATED_FEATURES = File.expand_path(File.dirname(__FILE__) + "/bench/generated")
8
+
9
+ class RandomFeatureGenerator
10
+ def initialize(number)
11
+ require 'faker'
12
+ require 'feature_builder'
13
+
14
+ @number = number
15
+ end
16
+
17
+ def generate
18
+ @number.times do
19
+ name = catch_phrase
20
+ feature = FeatureBuilder.new(name) do |f|
21
+ num_scenarios = rand_in(1..10)
22
+ num_scenarios.times do
23
+ f.scenario(bs) do |steps|
24
+ num_steps = rand_in(3..10)
25
+ num_steps.times do
26
+ steps.step(sentence, self)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ write feature.to_s, name
32
+ end
33
+ end
34
+
35
+ def write(content, name)
36
+ File.open(GENERATED_FEATURES + "/#{name.downcase.gsub(/[\s\-\/]/, '_')}.feature", "w+") do |file|
37
+ file << content
38
+ end
39
+ end
40
+
41
+ def rand_in(range)
42
+ ary = range.to_a
43
+ ary[rand(ary.length - 1)]
44
+ end
45
+
46
+ def catch_phrase
47
+ Faker::Company.catch_phrase
48
+ end
49
+
50
+ def bs
51
+ Faker::Company.bs.capitalize
52
+ end
53
+
54
+ def sentence
55
+ Faker::Lorem.sentence
56
+ end
57
+
58
+ def table_cell
59
+ Faker::Lorem.words(rand(2)+1).join(" ")
60
+ end
61
+ end
62
+
63
+ class Benchmarker
64
+ def initialize
65
+ @features = Dir[GENERATED_FEATURES + "/**/*feature"]
66
+ end
67
+
68
+ def report(lexer)
69
+ Benchmark.bm do |x|
70
+ x.report("#{lexer}:") { send :"run_#{lexer}" }
71
+ end
72
+ end
73
+
74
+ def report_all
75
+ Benchmark.bmbm do |x|
76
+ x.report("c_gherkin:") { run_c_gherkin }
77
+ x.report("rb_gherkin:") { run_rb_gherkin }
78
+ x.report("cucumber:") { run_cucumber }
79
+ x.report("tt:") { run_tt }
80
+ end
81
+ end
82
+
83
+ def run_cucumber
84
+ require 'cucumber'
85
+ require 'logger'
86
+ step_mother = Cucumber::StepMother.new
87
+ logger = Logger.new(STDOUT)
88
+ logger.level = Logger::INFO
89
+ step_mother.log = logger
90
+ step_mother.load_plain_text_features(@features)
91
+ end
92
+
93
+ def run_tt
94
+ require 'cucumber'
95
+ # Using Cucumber's Treetop lexer, but never calling #build to build the AST
96
+ lexer = Cucumber::Parser::NaturalLanguage.new(nil, 'en').lexer
97
+ @features.each do |file|
98
+ source = IO.read(file)
99
+ parse_tree = lexer.parse(source)
100
+ if parse_tree.nil?
101
+ raise Cucumber::Lexer::SyntaxError.new(lexer, file, 0)
102
+ end
103
+ end
104
+ end
105
+
106
+ def run_rb_gherkin
107
+ require 'gherkin'
108
+ require 'null_listener'
109
+ listener = NullListener.new
110
+ @features.each do |feature|
111
+ lexer = Gherkin::Feature.new('en', listener)
112
+ lexer.scan(File.read(feature))
113
+ end
114
+ end
115
+
116
+ def run_c_gherkin
117
+ require 'gherkin'
118
+ require 'null_listener'
119
+ listener = NullListener.new
120
+ @features.each_with_index do |feature, idx|
121
+ lexer = Gherkin::Feature.new('Native', listener)
122
+ lexer.scan(File.read(feature))
123
+ end
124
+ end
125
+ end
126
+
127
+ desc "Generate 500 random features and benchmark Cucumber, Treetop and Gherkin with them"
128
+ task :bench => ["bench:clean", "bench:gen"] do
129
+ benchmarker = Benchmarker.new
130
+ benchmarker.report_all
131
+ end
132
+
133
+ namespace :bench do
134
+ desc "Generate [number] features with random content, or 500 features if number is not provided"
135
+ task :gen, :number do |t, args|
136
+ args.with_defaults(:number => 500)
137
+ generator = RandomFeatureGenerator.new(args.number.to_i)
138
+ generator.generate
139
+ end
140
+
141
+ desc "Benchmark Cucumber AST building from the features in tasks/bench/generated"
142
+ task :cucumber do
143
+ benchmarker = Benchmarker.new
144
+ benchmarker.report("cucumber")
145
+ end
146
+
147
+ desc "Benchmark the Treetop lexer with the features in tasks/bench/generated"
148
+ task :tt do
149
+ benchmarker = Benchmarker.new
150
+ benchmarker.report("tt")
151
+ end
152
+
153
+ desc "Benchmark the Ruby Gherkin lexer with the features in tasks/bench/generated"
154
+ task :rb_gherkin do
155
+ benchmarker = Benchmarker.new
156
+ benchmarker.report("rb_gherkin")
157
+ end
158
+
159
+ desc "Benchmark the C Gherkin lexer with the features in tasks/bench/generated"
160
+ task :c_gherkin do
161
+ benchmarker = Benchmarker.new
162
+ benchmarker.report("c_gherkin")
163
+ end
164
+
165
+ desc "Show basic statistics about the features in tasks/bench/generated"
166
+ task :stats do
167
+ ["Feature", "Scenario", "Given"].each do |kw|
168
+ sh "grep #{kw} #{GENERATED_FEATURES}/* | wc -l"
169
+ end
170
+ end
171
+
172
+ desc "Remove all generated features in tasks/bench/generated"
173
+ task :clean do
174
+ rm_f FileList[GENERATED_FEATURES + "/**/*feature"]
175
+ end
176
+ end