gherkin_jruby 0.3.1.pre.jruby
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -0
- data/Rakefile +29 -0
- data/Readme.md +98 -0
- data/gherkin_jruby.gemspec +21 -0
- data/lib/gherkin_jruby.rb +10 -0
- data/lib/gherkin_ruby/ast.rb +89 -0
- data/lib/gherkin_ruby/parser.rb +1 -0
- data/lib/gherkin_ruby/parser/gherkin.rex +44 -0
- data/lib/gherkin_ruby/parser/gherkin.y +108 -0
- data/lib/gherkin_ruby/parser/lexer.rb +120 -0
- data/lib/gherkin_ruby/parser/parser.rb +403 -0
- data/lib/gherkin_ruby/version.rb +3 -0
- data/test/gherkin/ast_test.rb +124 -0
- data/test/gherkin/parser/lexer_test.rb +45 -0
- data/test/gherkin/parser/parser_test.rb +132 -0
- data/test/gherkin/parser_test.rb +77 -0
- data/test/test_helper.rb +4 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 38b437fd9881eff6dfbe7bd8d3c3b7fae7635ab2
|
4
|
+
data.tar.gz: 6c7f61a399113bfeac0ae3115029a1974e8a9f90
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 068fb7ebc94d293c51a52734d13cfea9d14b66cd79ecf5b40064735511db9736471f7dbdedaa9317921bb96e53e9e15a06e7771f201c4a70bd0ea0e5333abd7c
|
7
|
+
data.tar.gz: 57137563b34f849e71ed29278983991488e2f87e7d7b78b28568a1028963c7c2572c90584c2f5d0dd4ec1025ea0db4a4d151cd05a464434b61f46af4d4a5d523
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'bundler'
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList['./test/**/*_test.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Regenerate Gherkin-ruby lexer and parser."
|
12
|
+
task :regenerate do
|
13
|
+
has_rex = `which rex`
|
14
|
+
has_racc = `which racc`
|
15
|
+
|
16
|
+
if has_rex && has_racc
|
17
|
+
`rex lib/gherkin_ruby/parser/gherkin.rex -o lib/gherkin_ruby/parser/lexer.rb`
|
18
|
+
`racc #{'--debug' if ENV['DEBUG_RACC']} lib/gherkin_ruby/parser/gherkin.y -o lib/gherkin_ruby/parser/parser.rb`
|
19
|
+
else
|
20
|
+
puts "You need both Rexical and Racc to do that. Install them by doing:"
|
21
|
+
puts
|
22
|
+
puts "\t\tgem install rexical"
|
23
|
+
puts "\t\tgem install racc"
|
24
|
+
puts
|
25
|
+
puts "Or just type `bundle install`."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:regenerate, :test]
|
data/Readme.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# gherkin-ruby [](http://travis-ci.org/codegram/gherkin-ruby) [](http://gemnasium.com/codegram/gherkin-ruby)
|
2
|
+
Gherkin-ruby is a pure Ruby implementation of a [Gherkin](http://github.com/cucumber/gherkin) parser.
|
3
|
+
|
4
|
+
Tested with MRI 1.9.3, 2.0.0, head, Rubinius 2.0.0-rc1 and Rubinius head.
|
5
|
+
|
6
|
+
## WARNING: Will be deprecated after Gherkin 3.0
|
7
|
+
|
8
|
+
A new rewrite of the Gherkin parser used by Cucumber is planned (for version
|
9
|
+
3.0) gherkin-ruby will not add any more features until then, and will
|
10
|
+
eventually be deprecated in favor of Gherkin 3.0.
|
11
|
+
|
12
|
+
## FAQ
|
13
|
+
|
14
|
+
### Why this one over the official, fast, Ragel-based Gherkin parser?
|
15
|
+
|
16
|
+
* Less than 200 LOC.
|
17
|
+
* No Java/.NET crap.
|
18
|
+
* Fast enough for our purposes (using it for the [Spinach](http://github.com/codegram/spinach) project)
|
19
|
+
|
20
|
+
### Why don't you support tables?
|
21
|
+
|
22
|
+
* Because we believe it's a BDD anti-pattern. Tables show the need for more
|
23
|
+
unit tests.
|
24
|
+
|
25
|
+
## Install
|
26
|
+
|
27
|
+
$ gem install gherkin-ruby
|
28
|
+
|
29
|
+
Or in your Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Gemfile
|
33
|
+
|
34
|
+
gem 'gherkin-ruby'
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
You can easily implement your own visitors to traverse the Abstract Syntax Tree. The following example just prints the step names to standard output:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class MyVisitor
|
42
|
+
def visit(ast)
|
43
|
+
ast.accept(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_Feature(feature)
|
47
|
+
# Do something nasty with the feature
|
48
|
+
# Set whatever state you want:
|
49
|
+
# @current_feature = feature
|
50
|
+
# etc etc
|
51
|
+
# And keep visiting its children:
|
52
|
+
|
53
|
+
feature.each { |scenario| scenario.accept(self) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_Scenario(scenario)
|
57
|
+
# Do something nasty with the scenario
|
58
|
+
# Set whatever state you want:
|
59
|
+
# @current_scenario = scenario
|
60
|
+
# etc etc
|
61
|
+
# And keep visiting its children:
|
62
|
+
|
63
|
+
scenario.each { |step| step.accept(self) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_Background(background)
|
67
|
+
# Do something nasty with the background
|
68
|
+
# And keep visiting its children:
|
69
|
+
|
70
|
+
background.each { |step| step.accept(self) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def visit_Tag(tag)
|
74
|
+
# Do something nasty with the tag
|
75
|
+
end
|
76
|
+
|
77
|
+
def visit_Step(step)
|
78
|
+
# Finally, print the step name.
|
79
|
+
puts "STEP: #{step.name}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
ast = Gherkin.parse(File.read('some.feature'))
|
84
|
+
visitor = MyVisitor.new
|
85
|
+
visitor.visit(ast)
|
86
|
+
```
|
87
|
+
|
88
|
+
## Todo
|
89
|
+
|
90
|
+
* Some optimization
|
91
|
+
|
92
|
+
## FAQ
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
## License
|
97
|
+
|
98
|
+
MIT (Expat) License. Copyright 2011-2013 [Codegram Technologies](http://codegram.com)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gherkin_ruby/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gherkin_jruby"
|
7
|
+
s.version = GherkinRuby::VERSION
|
8
|
+
s.authors = ["Marc Divins", "Josep M. Bach", "Rodrigo Alvarez"]
|
9
|
+
s.email = ["marcdivc@gmail.com", "josep.m.bach@gmail.com", "papipo@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/Papipo/gherkin-jruby"
|
11
|
+
s.summary = %q{Gherkin-ruby is a Gherkin parser in pure Ruby using Rexical and Racc that works on jruby}
|
12
|
+
s.description = %q{Gherkin-ruby is a Gherkin parser in pure Ruby using Rexical and Racc that works on jruby}
|
13
|
+
|
14
|
+
s.add_development_dependency 'minitest'
|
15
|
+
s.add_development_dependency 'rexical'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module GherkinRuby
|
2
|
+
module AST
|
3
|
+
class Node
|
4
|
+
attr_reader :filename, :line
|
5
|
+
|
6
|
+
def accept(visitor)
|
7
|
+
name = self.class.name.split('::').last
|
8
|
+
visitor.send("visit_#{name}".to_sym, self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def pos(filename, line=nil)
|
12
|
+
@filename, @line = filename, line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Feature < Node
|
17
|
+
attr_reader :name, :background, :scenarios, :tags
|
18
|
+
attr_writer :background, :scenarios, :tags
|
19
|
+
attr_accessor :description
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def initialize(name, scenarios=[], tags=[], background=nil)
|
24
|
+
@name = name
|
25
|
+
@background = background
|
26
|
+
@tags = tags
|
27
|
+
@scenarios = scenarios
|
28
|
+
end
|
29
|
+
|
30
|
+
def each
|
31
|
+
@scenarios.each
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Background < Node
|
36
|
+
attr_reader :steps
|
37
|
+
attr_writer :steps
|
38
|
+
|
39
|
+
include Enumerable
|
40
|
+
|
41
|
+
def initialize(steps=[])
|
42
|
+
@steps = steps
|
43
|
+
end
|
44
|
+
|
45
|
+
def line
|
46
|
+
@steps.first.line - 1 if @steps.any?
|
47
|
+
end
|
48
|
+
|
49
|
+
def each
|
50
|
+
@steps.each
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Scenario < Node
|
55
|
+
attr_reader :name, :steps, :tags
|
56
|
+
|
57
|
+
include Enumerable
|
58
|
+
|
59
|
+
def initialize(name, steps=[], tags=[])
|
60
|
+
@name = name.to_s
|
61
|
+
@steps = steps
|
62
|
+
@tags = tags
|
63
|
+
end
|
64
|
+
|
65
|
+
def line
|
66
|
+
@steps.first.line - 1 if @steps.any?
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
@steps.each
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Step < Node
|
75
|
+
attr_reader :name, :keyword
|
76
|
+
def initialize(name, keyword)
|
77
|
+
@name = name.to_s
|
78
|
+
@keyword = keyword.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Tag < Node
|
83
|
+
attr_reader :name
|
84
|
+
def initialize(name)
|
85
|
+
@name = name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'parser/parser'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Compile with: rex gherkin.rex -o lexer.rb
|
2
|
+
|
3
|
+
class GherkinRuby::Parser
|
4
|
+
|
5
|
+
macro
|
6
|
+
BLANK [\ \t]+
|
7
|
+
|
8
|
+
rule
|
9
|
+
# Whitespace
|
10
|
+
{BLANK} # no action
|
11
|
+
\#.*$
|
12
|
+
|
13
|
+
# Literals
|
14
|
+
\n { [:NEWLINE, text] }
|
15
|
+
|
16
|
+
# Keywords
|
17
|
+
Feature: { [:FEATURE, text[0..-2]] }
|
18
|
+
Background: { [:BACKGROUND, text[0..-2]] }
|
19
|
+
Scenario: { [:SCENARIO, text[0..-2]] }
|
20
|
+
|
21
|
+
# Tags
|
22
|
+
@(\w|-)+ { [:TAG, text[1..-1]] }
|
23
|
+
|
24
|
+
# Step keywords
|
25
|
+
Given { [:GIVEN, text] }
|
26
|
+
When { [:WHEN, text] }
|
27
|
+
Then { [:THEN, text] }
|
28
|
+
And { [:AND, text] }
|
29
|
+
But { [:BUT, text] }
|
30
|
+
|
31
|
+
# Text
|
32
|
+
[^#\n]* { [:TEXT, text.strip] }
|
33
|
+
|
34
|
+
inner
|
35
|
+
def tokenize(code)
|
36
|
+
scan_setup(code)
|
37
|
+
tokens = []
|
38
|
+
while token = next_token
|
39
|
+
tokens << token
|
40
|
+
end
|
41
|
+
tokens
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Compile with: racc gherkin.y -o parser.rb
|
2
|
+
|
3
|
+
class GherkinRuby::Parser
|
4
|
+
|
5
|
+
# Declare tokens produced by the lexer
|
6
|
+
token NEWLINE
|
7
|
+
token FEATURE BACKGROUND SCENARIO
|
8
|
+
token TAG
|
9
|
+
token GIVEN WHEN THEN AND BUT
|
10
|
+
token TEXT
|
11
|
+
|
12
|
+
rule
|
13
|
+
|
14
|
+
Root:
|
15
|
+
Feature { result = val[0]; }
|
16
|
+
|
|
17
|
+
Feature
|
18
|
+
Scenarios { result = val[0]; result.scenarios = val[1] }
|
19
|
+
| FeatureTags Feature { result = val[1]; result.tags = val[0] }
|
20
|
+
| FeatureTags Feature
|
21
|
+
Scenarios { result = val[1]; result.scenarios = val[2]; result.tags = val[0] }
|
22
|
+
;
|
23
|
+
|
24
|
+
Newline:
|
25
|
+
NEWLINE
|
26
|
+
| Newline NEWLINE
|
27
|
+
;
|
28
|
+
|
29
|
+
FeatureTags:
|
30
|
+
Tags { result = val[0] }
|
31
|
+
| Newline Tags { result = val[1] }
|
32
|
+
|
33
|
+
Feature:
|
34
|
+
FeatureHeader { result = val[0] }
|
35
|
+
| FeatureHeader
|
36
|
+
Background { result = val[0]; result.background = val[1] }
|
37
|
+
;
|
38
|
+
|
39
|
+
FeatureHeader:
|
40
|
+
FeatureName { result = val[0] }
|
41
|
+
| FeatureName Newline { result = val[0] }
|
42
|
+
| FeatureName Newline
|
43
|
+
Description { result = val[0]; result.description = val[2] }
|
44
|
+
;
|
45
|
+
|
46
|
+
FeatureName:
|
47
|
+
FEATURE TEXT { result = AST::Feature.new(val[1]); result.pos(filename, lineno) }
|
48
|
+
| Newline FEATURE TEXT { result = AST::Feature.new(val[2]); result.pos(filename, lineno) }
|
49
|
+
;
|
50
|
+
|
51
|
+
Description:
|
52
|
+
TEXT Newline { result = val[0] }
|
53
|
+
| Description TEXT Newline { result = val[0...-1].flatten }
|
54
|
+
;
|
55
|
+
|
56
|
+
Background:
|
57
|
+
BackgroundHeader
|
58
|
+
Steps { result = val[0]; result.steps = val[1] }
|
59
|
+
;
|
60
|
+
|
61
|
+
BackgroundHeader:
|
62
|
+
BACKGROUND Newline { result = AST::Background.new; result.pos(filename, lineno) }
|
63
|
+
;
|
64
|
+
|
65
|
+
Steps:
|
66
|
+
Step { result = [val[0]] }
|
67
|
+
| Step Newline { result = [val[0]] }
|
68
|
+
| Step Newline Steps { val[2].unshift(val[0]); result = val[2] }
|
69
|
+
;
|
70
|
+
|
71
|
+
Step:
|
72
|
+
Keyword TEXT { result = AST::Step.new(val[1], val[0]); result.pos(filename, lineno) }
|
73
|
+
;
|
74
|
+
|
75
|
+
Keyword:
|
76
|
+
GIVEN | WHEN | THEN | AND | BUT
|
77
|
+
;
|
78
|
+
|
79
|
+
Scenarios:
|
80
|
+
Scenario { result = [val[0]] }
|
81
|
+
| Scenarios Scenario { result = val[0] << val[1] }
|
82
|
+
;
|
83
|
+
|
84
|
+
Scenario:
|
85
|
+
SCENARIO TEXT Newline
|
86
|
+
Steps { result = AST::Scenario.new(val[1], val[3]); result.pos(filename, lineno - 1) }
|
87
|
+
| Tags Newline
|
88
|
+
SCENARIO TEXT Newline
|
89
|
+
Steps { result = AST::Scenario.new(val[3], val[5], val[0]); result.pos(filename, lineno - 2) }
|
90
|
+
;
|
91
|
+
|
92
|
+
Tags:
|
93
|
+
TAG { result = [AST::Tag.new(val[0])] }
|
94
|
+
| Tags TAG { result = val[0] << AST::Tag.new(val[1]) }
|
95
|
+
;
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
---- header
|
100
|
+
require_relative "lexer"
|
101
|
+
require_relative "../ast"
|
102
|
+
|
103
|
+
---- inner
|
104
|
+
|
105
|
+
def parse(input)
|
106
|
+
@yydebug = true if ENV['DEBUG_RACC']
|
107
|
+
scan_str(input)
|
108
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#--
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by rex 1.0.5
|
4
|
+
# from lexical definition file "lib/gherkin_ruby/parser/gherkin.rex".
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'racc/parser'
|
8
|
+
# Compile with: rex gherkin.rex -o lexer.rb
|
9
|
+
|
10
|
+
class GherkinRuby::Parser < Racc::Parser
|
11
|
+
require 'strscan'
|
12
|
+
|
13
|
+
class ScanError < StandardError ; end
|
14
|
+
|
15
|
+
attr_reader :lineno
|
16
|
+
attr_reader :filename
|
17
|
+
attr_accessor :state
|
18
|
+
|
19
|
+
def scan_setup(str)
|
20
|
+
@ss = StringScanner.new(str)
|
21
|
+
@lineno = 1
|
22
|
+
@state = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def action
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
|
29
|
+
def scan_str(str)
|
30
|
+
scan_setup(str)
|
31
|
+
do_parse
|
32
|
+
end
|
33
|
+
alias :scan :scan_str
|
34
|
+
|
35
|
+
def load_file( filename )
|
36
|
+
@filename = filename
|
37
|
+
open(filename, "r") do |f|
|
38
|
+
scan_setup(f.read)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def scan_file( filename )
|
43
|
+
load_file(filename)
|
44
|
+
do_parse
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def next_token
|
49
|
+
return if @ss.eos?
|
50
|
+
|
51
|
+
# skips empty actions
|
52
|
+
until token = _next_token or @ss.eos?; end
|
53
|
+
token
|
54
|
+
end
|
55
|
+
|
56
|
+
def _next_token
|
57
|
+
text = @ss.peek(1)
|
58
|
+
@lineno += 1 if text == "\n"
|
59
|
+
token = case @state
|
60
|
+
when nil
|
61
|
+
case
|
62
|
+
when (text = @ss.scan(/[ \t]+/))
|
63
|
+
;
|
64
|
+
|
65
|
+
when (text = @ss.scan(/\#.*$/))
|
66
|
+
;
|
67
|
+
|
68
|
+
when (text = @ss.scan(/\n/))
|
69
|
+
action { [:NEWLINE, text] }
|
70
|
+
|
71
|
+
when (text = @ss.scan(/Feature:/))
|
72
|
+
action { [:FEATURE, text[0..-2]] }
|
73
|
+
|
74
|
+
when (text = @ss.scan(/Background:/))
|
75
|
+
action { [:BACKGROUND, text[0..-2]] }
|
76
|
+
|
77
|
+
when (text = @ss.scan(/Scenario:/))
|
78
|
+
action { [:SCENARIO, text[0..-2]] }
|
79
|
+
|
80
|
+
when (text = @ss.scan(/@(\w|-)+/))
|
81
|
+
action { [:TAG, text[1..-1]] }
|
82
|
+
|
83
|
+
when (text = @ss.scan(/Given/))
|
84
|
+
action { [:GIVEN, text] }
|
85
|
+
|
86
|
+
when (text = @ss.scan(/When/))
|
87
|
+
action { [:WHEN, text] }
|
88
|
+
|
89
|
+
when (text = @ss.scan(/Then/))
|
90
|
+
action { [:THEN, text] }
|
91
|
+
|
92
|
+
when (text = @ss.scan(/And/))
|
93
|
+
action { [:AND, text] }
|
94
|
+
|
95
|
+
when (text = @ss.scan(/But/))
|
96
|
+
action { [:BUT, text] }
|
97
|
+
|
98
|
+
when (text = @ss.scan(/[^#\n]*/))
|
99
|
+
action { [:TEXT, text.strip] }
|
100
|
+
|
101
|
+
else
|
102
|
+
text = @ss.string[@ss.pos .. -1]
|
103
|
+
raise ScanError, "can not match: '" + text + "'"
|
104
|
+
end # if
|
105
|
+
|
106
|
+
else
|
107
|
+
raise ScanError, "undefined state: '" + state.to_s + "'"
|
108
|
+
end # case state
|
109
|
+
token
|
110
|
+
end # def _next_token
|
111
|
+
|
112
|
+
def tokenize(code)
|
113
|
+
scan_setup(code)
|
114
|
+
tokens = []
|
115
|
+
while token = next_token
|
116
|
+
tokens << token
|
117
|
+
end
|
118
|
+
tokens
|
119
|
+
end
|
120
|
+
end # class
|