aslakhellesoy-gherkin 0.0.1
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.
- data/.document +5 -0
- data/.gitignore +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +37 -0
- data/Rakefile +25 -0
- data/VERSION.yml +4 -0
- data/ext/gherkin/extconf.rb +6 -0
- data/gherkin.gemspec +82 -0
- data/lib/gherkin.rb +1 -0
- data/lib/gherkin/i18n.yml +561 -0
- data/lib/gherkin/parser.rb +11 -0
- data/lib/gherkin/parser/.preserve +0 -0
- data/ragel/feature.rb.rl.erb +117 -0
- data/ragel/feature_common.rl.erb +37 -0
- data/ragel/misc.c.rl +4 -0
- data/ragel/misc.rb.rl +52 -0
- data/ragel/table.rb.rl +54 -0
- data/ragel/table_common.rl +12 -0
- data/spec/gherkin/feature_spec.rb +439 -0
- data/spec/gherkin/gherkin_parser/complex.feature +23 -0
- data/spec/gherkin/gherkin_parser/i18n_no.feature +6 -0
- data/spec/gherkin/gherkin_parser/simple.feature +3 -0
- data/spec/gherkin/gherkin_parser/simple_with_comments.feature +7 -0
- data/spec/gherkin/gherkin_parser/simple_with_tags.feature +5 -0
- data/spec/gherkin/i18n_spec.rb +28 -0
- data/spec/gherkin/multiline_step_args_spec.rb +98 -0
- data/spec/gherkin/sexp_recorder.rb +15 -0
- data/spec/gherkin/table_spec.rb +83 -0
- data/spec/gherkin/tags_spec.rb +57 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/ext.rake +35 -0
- data/tasks/ragel.rake +81 -0
- data/tasks/rdoc.rake +14 -0
- data/tasks/rspec.rake +16 -0
- metadata +102 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
#Comment on line 1
|
2
|
+
@tag1 @tag2
|
3
|
+
#Comment on line 3
|
4
|
+
Feature: Feature Text
|
5
|
+
In order to test multiline forms
|
6
|
+
As a ragel writer
|
7
|
+
I need to check for complex combinations
|
8
|
+
|
9
|
+
#Comment on line 9
|
10
|
+
|
11
|
+
#Comment on line 11
|
12
|
+
|
13
|
+
@tag3 @tag4
|
14
|
+
Scenario: Reading a Scenario
|
15
|
+
Given there is a step
|
16
|
+
But not another step
|
17
|
+
|
18
|
+
@tag3
|
19
|
+
Scenario: Reading a second scenario
|
20
|
+
#Comment on line 20
|
21
|
+
Given a third step
|
22
|
+
#Comment on line 22
|
23
|
+
Then I am happy
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
describe "i18n parsing" do
|
7
|
+
before do
|
8
|
+
@listener = Gherkin::SexpRecorder.new
|
9
|
+
@parser = Parser['no'].new(@listener)
|
10
|
+
end
|
11
|
+
|
12
|
+
def scan_file(file)
|
13
|
+
@parser.scan(File.new(File.dirname(__FILE__) + "/gherkin_parser/" + file).read)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should recognize keywords in the language of the parser" do
|
17
|
+
scan_file("i18n_no.feature")
|
18
|
+
@listener.to_sexp.should == [
|
19
|
+
[:feature, "Egenskap", "i18n support", 1],
|
20
|
+
[:scenario, "Scenario", "Parsing many languages", 3],
|
21
|
+
[:step, "Gitt", "Gherkin supports many languages", 4],
|
22
|
+
[:step, "Når", "Norwegian keywords are parsed", 5],
|
23
|
+
[:step, "Så", "they should be recognized", 6]
|
24
|
+
]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
describe "parsing multiline step arguments (pystrings)" do
|
7
|
+
|
8
|
+
def ps(content)
|
9
|
+
'"""%s"""' % ("\n" + content + "\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
def indent(s, n)
|
13
|
+
if n >= 0
|
14
|
+
s.gsub(/^/, ' ' * n)
|
15
|
+
else
|
16
|
+
s.gsub(/^ {0,#{-n}}/, "")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
@listener = mock('listener')
|
22
|
+
@parser = Misc.new(@listener)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should provide the amount of indentation to the listener"
|
26
|
+
|
27
|
+
it "should parse a simple pystring" do
|
28
|
+
@listener.should_receive(:pystring).with("I am a pystring")
|
29
|
+
@parser.scan ps("I am a pystring")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse an empty pystring" do
|
33
|
+
@listener.should_receive(:pystring).with("")
|
34
|
+
@parser.scan ps("")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should parse a string containing only newlines" do
|
38
|
+
@listener.should_receive(:pystring).with("\n\n")
|
39
|
+
@parser.scan ps("\n\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should parse a content separated by two newlines" do
|
43
|
+
@listener.should_receive(:pystring).with("A\n\nB")
|
44
|
+
@parser.scan ps("A\n\nB")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should parse a multiline string" do
|
48
|
+
@listener.should_receive(:pystring).with("A\nB\nC\nD")
|
49
|
+
@parser.scan ps("A\nB\nC\nD")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should ignore unescaped quotes inside the string delimeters" do
|
53
|
+
@listener.should_receive(:pystring).with("What does \"this\" mean?")
|
54
|
+
@parser.scan ps('What does "this" mean?')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should remove whitespace up to the column of the opening quote" do
|
58
|
+
@listener.should_receive(:pystring).with("I have been indented for reasons of style")
|
59
|
+
@parser.scan indent(ps('I have been indented for reasons of style'), 4)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should preserve whitespace after the column of the opening quote" do
|
63
|
+
@listener.should_receive(:pystring).with(" I have been indented to preserve whitespace")
|
64
|
+
@parser.scan indent(ps(' I have been indented to preserve whitespace'), 4)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should preserve tabs within the content" do
|
68
|
+
@listener.should_receive(:pystring).with("I have\tsome tabs\nInside\t\tthe content")
|
69
|
+
@parser.scan ps("I have\tsome tabs\nInside\t\tthe content")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should handle complex pystrings" do
|
73
|
+
pystring = %{
|
74
|
+
# Feature comment
|
75
|
+
@one
|
76
|
+
Feature: Sample
|
77
|
+
|
78
|
+
@two @three
|
79
|
+
Scenario: Missing
|
80
|
+
Given missing
|
81
|
+
|
82
|
+
1 scenario (1 passed)
|
83
|
+
1 step (1 passed)
|
84
|
+
|
85
|
+
}
|
86
|
+
|
87
|
+
@listener.should_receive(:pystring).with(pystring)
|
88
|
+
@parser.scan ps(pystring)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should set indentation to zero if the content begins before the start delimeter" do
|
92
|
+
pystring = " \"\"\"\nContent\n\"\"\""
|
93
|
+
@listener.should_receive(:pystring).with("Content")
|
94
|
+
@parser.scan(pystring)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
describe Table do
|
7
|
+
before do
|
8
|
+
@listener = Gherkin::SexpRecorder.new
|
9
|
+
@table = Table.new(@listener)
|
10
|
+
end
|
11
|
+
|
12
|
+
tables = {
|
13
|
+
"|a|b|\n" => [%w{a b}],
|
14
|
+
"|a|b|c|\n" => [%w{a b c}],
|
15
|
+
"|c|d|\n|e|f|\n" => [%w{c d}, %w{e f}]
|
16
|
+
}
|
17
|
+
|
18
|
+
tables.each do |text, expected|
|
19
|
+
it "should parse #{text}" do
|
20
|
+
@listener.should_receive(:table).with(expected)
|
21
|
+
@table.scan(text)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should parse a multicharacter cell content" do
|
26
|
+
@listener.should_receive(:table).with([%w{foo bar}])
|
27
|
+
@table.scan("| foo | bar |\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should parse cells with spaces within the content" do
|
31
|
+
@listener.should_receive(:table).with([["Dill pickle", "Valencia orange"], ["Ruby red grapefruit", "Tire iron"]])
|
32
|
+
@table.scan("| Dill pickle | Valencia orange |\n| Ruby red grapefruit | Tire iron |\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should parse a 1x2 table with newline" do
|
36
|
+
@listener.should_receive(:table).with([%w{1 2}])
|
37
|
+
@table.scan("| 1 | 2 |\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should allow utf-8" do
|
41
|
+
@listener.should_receive(:table).with([%w{ůﻚ 2}])
|
42
|
+
@table.scan(" | ůﻚ | 2 | \n")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should parse a 2x2 table" do
|
46
|
+
@listener.should_receive(:table).with([%w{1 2}, %w{3 4}])
|
47
|
+
@table.scan("| 1 | 2 |\n| 3 | 4 |\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should parse a 2x2 table with several newlines" do
|
51
|
+
@listener.should_receive(:table).with([%w{1 2}, %w{3 4}])
|
52
|
+
@table.scan("| 1 | 2 |\n| 3 | 4 |\n\n\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should parse a 2x2 table with empty cells" do
|
56
|
+
@listener.should_receive(:table).with([['1', nil], [nil, '4']])
|
57
|
+
@table.scan("| 1 | |\n|| 4 |\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should parse a 1x2 table without newline" do
|
61
|
+
@listener.should_receive(:table).with([%w{1 2}])
|
62
|
+
@table.scan("| 1 | 2 |")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should parse a 1x2 table without spaces and newline" do
|
66
|
+
@listener.should_receive(:table).with([%w{1 2}])
|
67
|
+
@table.scan("|1|2|")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not parse a 2x2 table that isn't closed" do
|
71
|
+
@table.scan("| 1 | |\n|| 4 ")
|
72
|
+
@listener.to_sexp.should == [
|
73
|
+
[:table, [['1', nil]]]
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should parse a table with tab spacing" do
|
78
|
+
@listener.should_receive(:table).with([["abc", "123"]])
|
79
|
+
@table.scan("|\tabc\t|\t123\t\t\t|\n")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
module Gherkin
|
5
|
+
module Parser
|
6
|
+
describe "parsing" do
|
7
|
+
describe "tags" do
|
8
|
+
before do
|
9
|
+
@listener = mock('listener')
|
10
|
+
@feature = Parser['en'].new(@listener)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should parse a single tag" do
|
14
|
+
@listener.should_receive(:tag).with("dog", 1)
|
15
|
+
@feature.scan("@dog\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should parse multiple tags" do
|
19
|
+
@listener.should_receive(:tag).twice
|
20
|
+
@feature.scan("@dog @cat\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should parse UTF-8 tags" do
|
24
|
+
@listener.should_receive(:tag).with("シナリオテンプレート", 1)
|
25
|
+
@feature.scan("@シナリオテンプレート\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should parse mixed tags" do
|
29
|
+
@listener.should_receive(:tag).with("wip", 1).ordered
|
30
|
+
@listener.should_receive(:tag).with("Значения", 1).ordered
|
31
|
+
@feature.scan("@wip @Значения\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should parse wacky identifiers" do
|
35
|
+
@listener.should_receive(:tag).exactly(4).times
|
36
|
+
@feature.scan("@BJ-x98.77 @BJ-z12.33 @O_o" "@#not_a_comment\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: Ask on ML for opinions about this one
|
40
|
+
it "should parse tags without spaces between them?" do
|
41
|
+
@listener.should_receive(:tag).twice
|
42
|
+
@feature.scan("@one@two\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not parse tags beginning with two @@ signs" do
|
46
|
+
@listener.should_not_receive(:tag)
|
47
|
+
@feature.scan("@@test\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not parse a lone @ sign" do
|
51
|
+
@listener.should_not_receive(:tag)
|
52
|
+
@feature.scan("@\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tasks/ext.rake
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
CLEAN.include %w(**/*.{o,bundle,jar,so,obj,pdb,lib,def,exp,log} ext/*/Makefile ext/*/*.c ext/*/conftest.dSYM)
|
2
|
+
WIN = (RUBY_PLATFORM =~ /mswin|cygwin/)
|
3
|
+
|
4
|
+
def ext_task(name)
|
5
|
+
ext_dir = "ext/#{name}"
|
6
|
+
ext_bundle = "#{ext_dir}/#{name}.#{Config::CONFIG['DLEXT']}"
|
7
|
+
ext_files = FileList[
|
8
|
+
"#{ext_dir}/*.c",
|
9
|
+
"#{ext_dir}/*.h",
|
10
|
+
"#{ext_dir}/*.rl",
|
11
|
+
"#{ext_dir}/extconf.rb",
|
12
|
+
"#{ext_dir}/Makefile",
|
13
|
+
"lib"
|
14
|
+
]
|
15
|
+
|
16
|
+
task "compile:#{name}" => ["#{ext_dir}/Makefile", ext_bundle]
|
17
|
+
task :compile => ['ragel:c', "compile:#{name}"]
|
18
|
+
|
19
|
+
file "#{ext_dir}/Makefile" => ["#{ext_dir}/extconf.rb"] do
|
20
|
+
cd(ext_dir) { ruby "extconf.rb" }
|
21
|
+
end
|
22
|
+
|
23
|
+
file ext_bundle => ext_files do
|
24
|
+
cd ext_dir do
|
25
|
+
sh(WIN ? 'nmake' : 'make')
|
26
|
+
end
|
27
|
+
cp ext_bundle, 'lib/'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
ext_task :gherkin
|
32
|
+
|
33
|
+
desc "Compile the C extensions"
|
34
|
+
task :compile
|
35
|
+
task :package => :compile
|
data/tasks/ragel.rake
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
class RagelCompiler
|
2
|
+
def initialize
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
@impl = ERB.new(IO.read(File.dirname(__FILE__) + '/../ragel/feature.rb.rl.erb'))
|
7
|
+
@common = ERB.new(IO.read(File.dirname(__FILE__) + '/../ragel/feature_common.rl.erb'))
|
8
|
+
@langs = YAML.load_file(File.dirname(__FILE__) + '/../lib/gherkin/i18n.yml')
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile_all
|
12
|
+
@langs.keys.each do |lang|
|
13
|
+
compile(lang)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def compile(lang)
|
18
|
+
i18n = @langs['en'].merge(@langs[lang])
|
19
|
+
common_file = File.dirname(__FILE__) + "/../ragel/feature_common.#{lang}.rl"
|
20
|
+
impl_file = File.dirname(__FILE__) + "/../ragel/feature_#{lang}.rb.rl"
|
21
|
+
|
22
|
+
common = @common.result(binding)
|
23
|
+
impl = @impl.result(binding)
|
24
|
+
|
25
|
+
write common, common_file
|
26
|
+
write impl, impl_file
|
27
|
+
|
28
|
+
sh "ragel -R #{impl_file} -o lib/gherkin/parser/feature_#{lang}.rb"
|
29
|
+
|
30
|
+
FileUtils.rm([impl_file, common_file])
|
31
|
+
end
|
32
|
+
|
33
|
+
def write(content, filename)
|
34
|
+
File.open(filename, "wb") do |file|
|
35
|
+
file.write(content)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
namespace :ragel do
|
41
|
+
desc "Generate Ruby from the Ragel rule files"
|
42
|
+
task :rb => :i18n_en do
|
43
|
+
Dir["ragel/*.rb.rl"].each do |rl|
|
44
|
+
basename = File.basename(rl[0..-4])
|
45
|
+
sh "ragel -R #{rl} -o lib/gherkin/parser/#{basename}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Generate C from the Ragel rule files"
|
50
|
+
task :c do
|
51
|
+
Dir["ragel/*.c.rl"].each do |rl|
|
52
|
+
basename = File.basename(rl[0..-3])
|
53
|
+
sh "ragel -C #{rl} -o ext/gherkin/#{basename}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Generate all Ruby i18n parsers"
|
58
|
+
task :i18n do
|
59
|
+
RagelCompiler.new.compile_all
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Generate Ruby English language parser"
|
63
|
+
task :i18n_en do
|
64
|
+
RagelCompiler.new.compile('en')
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Generate a dot file of the Ragel state machine"
|
68
|
+
task :dot do
|
69
|
+
Dir["ragel/*.rb.rl"].each do |path|
|
70
|
+
sh "ragel -V #{path} -o #{File.basename(path, '.rl')}.dot"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Generate a png diagram of the Ragel state machine"
|
75
|
+
task :png => :dot do
|
76
|
+
Dir["*dot"].each do |path|
|
77
|
+
sh "dot -Tpng #{path} > #{File.basename(path, '.dot')}.png"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|