gherkin 0.0.3

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