gherkin_jruby 0.3.1.pre.jruby
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/codegram/gherkin-ruby.png)](http://travis-ci.org/codegram/gherkin-ruby) [![Dependency Status](https://gemnasium.com/codegram/gherkin-ruby.png)](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
|