gherkin3 3.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/Makefile +63 -0
- data/README.md +3 -0
- data/Rakefile +24 -0
- data/bin/gherkin-generate-ast +24 -0
- data/bin/gherkin-generate-tokens +14 -0
- data/gherkin-ruby.razor +211 -0
- data/gherkin3.gemspec +26 -0
- data/lib/gherkin3/ast_builder.rb +239 -0
- data/lib/gherkin3/ast_node.rb +30 -0
- data/lib/gherkin3/dialect.rb +58 -0
- data/lib/gherkin3/errors.rb +45 -0
- data/lib/gherkin3/gherkin-languages.json +2835 -0
- data/lib/gherkin3/gherkin_line.rb +66 -0
- data/lib/gherkin3/parser.rb +1903 -0
- data/lib/gherkin3/token.rb +18 -0
- data/lib/gherkin3/token_formatter_builder.rb +35 -0
- data/lib/gherkin3/token_matcher.rb +163 -0
- data/lib/gherkin3/token_scanner.rb +33 -0
- data/spec/capture_warnings.rb +68 -0
- data/spec/coverage.rb +10 -0
- data/spec/gherkin3/parser_spec.rb +26 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0dc70f9552b102602504fff5b292d8eea53e0b4e
|
4
|
+
data.tar.gz: d802c01c8500f2bcc7e24f699aa4e3667b47e10f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e8a7e4f1065ada8af02b0c10737689f0b7ec080a79a8698c39c3aa9b0e9955a38122061a5eac960225a61e0fa1a90a1dc22fbfdd8a41022c6b6211fd88845b4
|
7
|
+
data.tar.gz: d5ca28a5a800909e3d334cca859fa0a3d96e42ddef04da103d77e1be899bbb94972369e1bae14260927e1bc5e8744eda424173c790e0c8335c032ab02258aabf
|
data/.travis.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
language: ruby
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Please read [CONTRIBUTING](https://github.com/cucumber/gherkin3/blob/master/CONTRIBUTING.md) first.
|
2
|
+
You should clone the [cucumber/gherkin3](https://github.com/cucumber/gherkin3) repo if you want
|
3
|
+
to contribute.
|
4
|
+
|
5
|
+
## Using make
|
6
|
+
|
7
|
+
Just run `make` from this directory.
|
8
|
+
|
9
|
+
Even if you prefer `make` - run `rake` occasionally, as it reports better warnings.
|
10
|
+
|
11
|
+
## Using rake
|
12
|
+
|
13
|
+
Just run `rake` from this directory.
|
14
|
+
|
15
|
+
Keep in mind that this will only run unit tests. The acceptance tests are only
|
16
|
+
run when you build with `make`.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014-2015 Cucumber Ltd, Gaspar Nagy, TechTalk
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Makefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature")
|
2
|
+
BAD_FEATURE_FILES = $(shell find ../testdata/bad -name "*.feature")
|
3
|
+
|
4
|
+
TOKENS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.tokens,$(GOOD_FEATURE_FILES))
|
5
|
+
ASTS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.ast.json,$(GOOD_FEATURE_FILES))
|
6
|
+
ERRORS = $(patsubst ../testdata/%.feature,acceptance/testdata/%.feature.errors,$(BAD_FEATURE_FILES))
|
7
|
+
|
8
|
+
RUBY_FILES = $(shell find . -name "*.rb")
|
9
|
+
|
10
|
+
all: .compared
|
11
|
+
.PHONY: all
|
12
|
+
|
13
|
+
.compared: .built $(TOKENS) $(ASTS) $(ERRORS)
|
14
|
+
touch $@
|
15
|
+
|
16
|
+
.built: show-version-info lib/gherkin3/parser.rb lib/gherkin3/gherkin-languages.json $(RUBY_FILES) Gemfile.lock LICENSE
|
17
|
+
bundle exec rspec --color
|
18
|
+
touch $@
|
19
|
+
|
20
|
+
show-version-info:
|
21
|
+
ruby --version
|
22
|
+
.PHONY: show-version-info
|
23
|
+
|
24
|
+
acceptance/testdata/%.feature.tokens: ../testdata/%.feature ../testdata/%.feature.tokens .built
|
25
|
+
mkdir -p `dirname $@`
|
26
|
+
bin/gherkin-generate-tokens $< > $@
|
27
|
+
diff --unified $<.tokens $@
|
28
|
+
.DELETE_ON_ERROR: acceptance/testdata/%.feature.tokens
|
29
|
+
|
30
|
+
acceptance/testdata/%.feature.ast.json: ../testdata/%.feature ../testdata/%.feature.ast.json .built
|
31
|
+
mkdir -p `dirname $@`
|
32
|
+
bin/gherkin-generate-ast $< | jq --sort-keys "." > $@
|
33
|
+
diff --unified $<.ast.json $@
|
34
|
+
.DELETE_ON_ERROR: acceptance/testdata/%.feature.ast.json
|
35
|
+
|
36
|
+
acceptance/testdata/%.feature.errors: ../testdata/%.feature ../testdata/%.feature.errors .built
|
37
|
+
mkdir -p `dirname $@`
|
38
|
+
! bin/gherkin-generate-ast $< 2> $@
|
39
|
+
diff --unified $<.errors $@
|
40
|
+
.DELETE_ON_ERROR: acceptance/testdata/%.feature.errors
|
41
|
+
|
42
|
+
lib/gherkin3/gherkin-languages.json: ../gherkin-languages.json
|
43
|
+
cp $^ $@
|
44
|
+
|
45
|
+
clean:
|
46
|
+
rm -rf .compared .built acceptance lib/gherkin3/parser.rb lib/gherkin3/gherkin-languages.json coverage
|
47
|
+
.PHONY: clean
|
48
|
+
|
49
|
+
lib/gherkin3/parser.rb: ../gherkin.berp gherkin-ruby.razor ../bin/berp.exe
|
50
|
+
mono ../bin/berp.exe -g ../gherkin.berp -t gherkin-ruby.razor -o $@
|
51
|
+
# Remove BOM
|
52
|
+
tail -c +4 $@ > $@.nobom
|
53
|
+
mv $@.nobom $@
|
54
|
+
|
55
|
+
Gemfile.lock: Gemfile
|
56
|
+
bundle install
|
57
|
+
|
58
|
+
LICENSE: ../LICENSE
|
59
|
+
cp $< $@
|
60
|
+
|
61
|
+
release: .compared
|
62
|
+
bundle exec rake build release:guard_clean release:rubygem_push
|
63
|
+
.PHONY: release
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
7
|
+
|
8
|
+
require "rspec/core/rake_task"
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.ruby_opts = %w[-r./spec/coverage -w]
|
11
|
+
t.rspec_opts = %w[--color]
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative 'spec/capture_warnings'
|
15
|
+
include CaptureWarnings
|
16
|
+
namespace :spec do
|
17
|
+
task :warnings do
|
18
|
+
report_warnings do
|
19
|
+
Rake::Task['spec'].invoke
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
task default: ['spec:warnings']
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"../lib"))
|
3
|
+
require 'gherkin3/parser'
|
4
|
+
require 'gherkin3/token_scanner'
|
5
|
+
require 'gherkin3/token_matcher'
|
6
|
+
require 'gherkin3/ast_builder'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
parser = Gherkin3::Parser.new
|
10
|
+
parser.stop_at_first_error = false
|
11
|
+
files = ARGV.any? ? ARGV : (STDIN.tty? ? [] : [STDIN])
|
12
|
+
start_time = Time.now
|
13
|
+
files.each do |file|
|
14
|
+
scanner = Gherkin3::TokenScanner.new(file)
|
15
|
+
builder = Gherkin3::AstBuilder.new
|
16
|
+
begin
|
17
|
+
puts JSON.generate(parser.parse(scanner, builder, Gherkin3::TokenMatcher.new))
|
18
|
+
rescue Gherkin3::ParserError => e
|
19
|
+
STDERR.puts e.message
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end_time = Time.now
|
24
|
+
STDERR.puts (end_time - start_time)*1000 if ENV['GHERKIN_PERF']
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"../lib"))
|
3
|
+
require 'gherkin3/parser'
|
4
|
+
require 'gherkin3/token_scanner'
|
5
|
+
require 'gherkin3/token_formatter_builder'
|
6
|
+
require 'gherkin3/token_matcher'
|
7
|
+
|
8
|
+
parser = Gherkin3::Parser.new
|
9
|
+
files = ARGV + (STDIN.tty? ? [] : [STDIN])
|
10
|
+
files.each do |file|
|
11
|
+
scanner = Gherkin3::TokenScanner.new(file)
|
12
|
+
builder = Gherkin3::TokenFormatterBuilder.new
|
13
|
+
print parser.parse(scanner, builder, Gherkin3::TokenMatcher.new)
|
14
|
+
end
|
data/gherkin-ruby.razor
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
@using Berp;
|
2
|
+
@helper CallProduction(ProductionRule production)
|
3
|
+
{
|
4
|
+
switch(production.Type)
|
5
|
+
{
|
6
|
+
case ProductionRuleType.Start:
|
7
|
+
@: start_rule(context, :@production.RuleName);
|
8
|
+
break;
|
9
|
+
case ProductionRuleType.End:
|
10
|
+
@: end_rule(context, :@production.RuleName);
|
11
|
+
break;
|
12
|
+
case ProductionRuleType.Process:
|
13
|
+
@: build(context, token);
|
14
|
+
break;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
@helper HandleParserError(IEnumerable<string> expectedTokens, State state)
|
18
|
+
{<text>
|
19
|
+
state_comment = "State: @state.Id - @Raw(state.Comment)"
|
20
|
+
token.detach
|
21
|
+
expected_tokens = ["@Raw(string.Join("\", \"", expectedTokens))"]
|
22
|
+
error = token.eof? ? UnexpectedEOFException.new(token, expected_tokens, state_comment) : UnexpectedTokenException.new(token, expected_tokens, state_comment)
|
23
|
+
raise error if (stop_at_first_error)
|
24
|
+
add_error(context, error)
|
25
|
+
return @state.Id</text>}
|
26
|
+
@helper MatchToken(TokenType tokenType)
|
27
|
+
{<text>match_@(tokenType)(context, token)</text>}
|
28
|
+
# This file is generated. Do not edit! Edit gherkin-ruby.razor instead.
|
29
|
+
require_relative 'ast_builder'
|
30
|
+
require_relative 'token_matcher'
|
31
|
+
require_relative 'errors'
|
32
|
+
|
33
|
+
module Gherkin3
|
34
|
+
|
35
|
+
RULE_TYPE = [
|
36
|
+
:None,
|
37
|
+
@foreach(var rule in Model.RuleSet.Where(r => !r.TempRule))
|
38
|
+
{<text> :@rule.Name.Replace("#", "_"), # @rule.ToString(true)
|
39
|
+
</text>}
|
40
|
+
]
|
41
|
+
|
42
|
+
class ParserContext
|
43
|
+
attr_reader :token_scanner, :ast_builder, :token_matcher, :token_queue, :errors
|
44
|
+
|
45
|
+
def initialize(token_scanner, ast_builder, token_matcher, token_queue, errors)
|
46
|
+
@@token_scanner = token_scanner
|
47
|
+
@@ast_builder = ast_builder
|
48
|
+
@@token_matcher = token_matcher
|
49
|
+
@@token_queue = token_queue
|
50
|
+
@@errors = errors
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class @Model.ParserClassName
|
55
|
+
attr_accessor :stop_at_first_error
|
56
|
+
|
57
|
+
def parse(token_scanner, ast_builder=AstBuilder.new, token_matcher=TokenMatcher.new)
|
58
|
+
context = ParserContext.new(
|
59
|
+
token_scanner,
|
60
|
+
ast_builder,
|
61
|
+
token_matcher,
|
62
|
+
[],
|
63
|
+
[]
|
64
|
+
)
|
65
|
+
|
66
|
+
start_rule(context, :@Model.RuleSet.StartRule.Name);
|
67
|
+
state = 0
|
68
|
+
token = nil
|
69
|
+
begin
|
70
|
+
token = read_token(context)
|
71
|
+
state = match_token(state, token, context)
|
72
|
+
end until(token.eof?)
|
73
|
+
|
74
|
+
end_rule(context, :@Model.RuleSet.StartRule.Name)
|
75
|
+
|
76
|
+
raise CompositeParserException.new(context.errors) if context.errors.any?
|
77
|
+
|
78
|
+
get_result(context)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build(context, token)
|
82
|
+
handle_ast_error(context) do
|
83
|
+
context.ast_builder.build(token)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_error(context, error)
|
88
|
+
context.errors.push(error)
|
89
|
+
raise CompositeParserException, context.errors if context.errors.length > 10
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_rule(context, rule_type)
|
93
|
+
handle_ast_error(context) do
|
94
|
+
context.ast_builder.start_rule(rule_type)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def end_rule(context, rule_type)
|
99
|
+
handle_ast_error(context) do
|
100
|
+
context.ast_builder.end_rule(rule_type)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_result(context)
|
105
|
+
context.ast_builder.get_result
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_token(context)
|
109
|
+
context.token_queue.any? ? context.token_queue.shift : context.token_scanner.read
|
110
|
+
end
|
111
|
+
|
112
|
+
@foreach(var rule in Model.RuleSet.TokenRules)
|
113
|
+
{<text>
|
114
|
+
def match_@(rule.Name.Replace("#", ""))( context, token)
|
115
|
+
@if (rule.Name != "#EOF")
|
116
|
+
{
|
117
|
+
@:return false if token.eof?
|
118
|
+
}
|
119
|
+
return handle_external_error(context, false) do
|
120
|
+
context.token_matcher.match_@(rule.Name.Replace("#", ""))(token)
|
121
|
+
end
|
122
|
+
end</text>
|
123
|
+
}
|
124
|
+
|
125
|
+
def match_token(state, token, context)
|
126
|
+
case state
|
127
|
+
@foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
|
128
|
+
{
|
129
|
+
@:when @state.Id
|
130
|
+
@:match_token_at_@(state.Id)(token, context)
|
131
|
+
}
|
132
|
+
else
|
133
|
+
raise InvalidOperationException, "Unknown state: #{state}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
@foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
|
138
|
+
{<text>
|
139
|
+
# @Raw(state.Comment)
|
140
|
+
def match_token_at_@(state.Id)(token, context)
|
141
|
+
@foreach(var transition in state.Transitions)
|
142
|
+
{
|
143
|
+
@:if @MatchToken(transition.TokenType)
|
144
|
+
if (transition.LookAheadHint != null)
|
145
|
+
{
|
146
|
+
@:if lookahead_@(transition.LookAheadHint.Id)(context, token)
|
147
|
+
}
|
148
|
+
foreach(var production in transition.Productions)
|
149
|
+
{
|
150
|
+
@CallProduction(production)
|
151
|
+
}
|
152
|
+
@:return @transition.TargetState
|
153
|
+
if (transition.LookAheadHint != null)
|
154
|
+
{
|
155
|
+
@:end
|
156
|
+
}
|
157
|
+
@:end
|
158
|
+
}
|
159
|
+
@HandleParserError(state.Transitions.Select(t => "#" + t.TokenType.ToString()).Distinct(), state)
|
160
|
+
end</text>
|
161
|
+
}
|
162
|
+
|
163
|
+
@foreach(var lookAheadHint in Model.RuleSet.LookAheadHints)
|
164
|
+
{
|
165
|
+
<text>
|
166
|
+
def lookahead_@(lookAheadHint.Id)(context, currentToken)
|
167
|
+
currentToken.detach
|
168
|
+
token = nil
|
169
|
+
queue = []
|
170
|
+
match = false
|
171
|
+
loop do
|
172
|
+
token = read_token(context)
|
173
|
+
token.detach
|
174
|
+
queue.unshift(token)
|
175
|
+
|
176
|
+
if (false @foreach(var tokenType in lookAheadHint.ExpectedTokens) {<text>|| @MatchToken(tokenType)</text>})
|
177
|
+
match = true
|
178
|
+
break
|
179
|
+
end
|
180
|
+
|
181
|
+
break unless (false @foreach(var tokenType in lookAheadHint.Skip) {<text>|| @MatchToken(tokenType)</text>})
|
182
|
+
end
|
183
|
+
|
184
|
+
context.token_queue.concat(queue)
|
185
|
+
|
186
|
+
return match
|
187
|
+
end
|
188
|
+
</text>
|
189
|
+
}
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def handle_ast_error(context, &action)
|
194
|
+
handle_external_error(context, true, &action)
|
195
|
+
end
|
196
|
+
|
197
|
+
def handle_external_error(context, default_value, &action)
|
198
|
+
return action.call if stop_at_first_error
|
199
|
+
|
200
|
+
begin
|
201
|
+
return action.call
|
202
|
+
rescue CompositeParserException => e
|
203
|
+
e.errors.each { |error| add_error(context, error) }
|
204
|
+
rescue ParserException => e
|
205
|
+
add_error(context, e)
|
206
|
+
end
|
207
|
+
default_value
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
data/gherkin3.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'gherkin3'
|
4
|
+
s.version = '3.0.0.alpha.1'
|
5
|
+
s.authors = ["Gáspár Nagy", "Aslak Hellesøy", "Steve Tooke"]
|
6
|
+
s.description = 'Gherkin parser'
|
7
|
+
s.summary = "gherkin-#{s.version}"
|
8
|
+
s.email = 'cukes@googlegroups.com'
|
9
|
+
s.homepage = "https://github.com/cucumber/gherkin3"
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.license = "MIT"
|
12
|
+
s.required_ruby_version = ">= 1.9.3"
|
13
|
+
|
14
|
+
s.add_development_dependency 'bundler', '~> 1.7'
|
15
|
+
s.add_development_dependency 'rake', '~> 10.4'
|
16
|
+
s.add_development_dependency 'rspec', '~> 3.3'
|
17
|
+
|
18
|
+
# For coverage reports
|
19
|
+
s.add_development_dependency 'coveralls', '~> 0.8'
|
20
|
+
|
21
|
+
s.rubygems_version = ">= 1.6.1"
|
22
|
+
s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }
|
23
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
24
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
25
|
+
s.require_path = "lib"
|
26
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require_relative 'ast_node'
|
2
|
+
|
3
|
+
module Gherkin3
|
4
|
+
class AstBuilder
|
5
|
+
def initialize
|
6
|
+
@stack = [AstNode.new(:None)]
|
7
|
+
@comments = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def start_rule(rule_type)
|
11
|
+
@stack.push AstNode.new(rule_type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def end_rule(rule_type)
|
15
|
+
node = @stack.pop
|
16
|
+
current_node.add(node.rule_type, transform_node(node))
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(token)
|
20
|
+
if token.matched_type == :Comment
|
21
|
+
@comments.push({
|
22
|
+
type: 'Comment',
|
23
|
+
location: get_location(token),
|
24
|
+
text: token.matched_text
|
25
|
+
})
|
26
|
+
else
|
27
|
+
current_node.add(token.matched_type, token)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_result
|
32
|
+
current_node.get_single(:Feature)
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_node
|
36
|
+
@stack.last
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_location(token, column=nil)
|
40
|
+
# TODO: translated from JS... is it right?
|
41
|
+
(column.nil? || column.zero?) ? token.location : {line: token.location[:line], column: column}
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_tags(node)
|
45
|
+
tags = []
|
46
|
+
tags_node = node.get_single(:Tags)
|
47
|
+
return tags unless tags_node
|
48
|
+
|
49
|
+
tags_node.get_tokens(:TagLine).each do |token|
|
50
|
+
token.matched_items.each do |tag_item|
|
51
|
+
tags.push({
|
52
|
+
type: 'Tag',
|
53
|
+
location: get_location(token, tag_item.column),
|
54
|
+
name: tag_item.text
|
55
|
+
})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
tags
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_table_rows(node)
|
63
|
+
rows = node.get_tokens(:TableRow).map do |token|
|
64
|
+
{
|
65
|
+
type: 'TableRow',
|
66
|
+
location: get_location(token),
|
67
|
+
cells: get_cells(token)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
ensure_cell_count(rows);
|
71
|
+
rows
|
72
|
+
end
|
73
|
+
|
74
|
+
def ensure_cell_count(rows)
|
75
|
+
return if rows.empty?
|
76
|
+
cell_count = rows[0][:cells].length
|
77
|
+
rows.each do |row|
|
78
|
+
if (row[:cells].length != cell_count)
|
79
|
+
raise AstBuilderException.new("inconsistent cell count within the table", row[:location]);
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_cells(table_row_token)
|
85
|
+
table_row_token.matched_items.map do |cell_item|
|
86
|
+
{
|
87
|
+
type: 'TableCell',
|
88
|
+
location: get_location(table_row_token, cell_item.column),
|
89
|
+
value: cell_item.text
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_description(node)
|
95
|
+
node.get_single(:Description)
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_steps(node)
|
99
|
+
node.get_items(:Step)
|
100
|
+
end
|
101
|
+
|
102
|
+
def transform_node(node)
|
103
|
+
case node.rule_type
|
104
|
+
when :Step
|
105
|
+
step_line = node.get_token(:StepLine)
|
106
|
+
step_argument = node.get_single(:DataTable) || node.get_single(:DocString) || nil
|
107
|
+
|
108
|
+
reject_nils(
|
109
|
+
type: node.rule_type,
|
110
|
+
location: get_location(step_line),
|
111
|
+
keyword: step_line.matched_keyword,
|
112
|
+
text: step_line.matched_text,
|
113
|
+
argument: step_argument
|
114
|
+
)
|
115
|
+
when :DocString
|
116
|
+
separator_token = node.get_tokens(:DocStringSeparator)[0]
|
117
|
+
content_type = separator_token.matched_text
|
118
|
+
line_tokens = node.get_tokens(:Other)
|
119
|
+
content = line_tokens.map { |t| t.matched_text }.join("\n")
|
120
|
+
|
121
|
+
reject_nils(
|
122
|
+
type: node.rule_type,
|
123
|
+
location: get_location(separator_token),
|
124
|
+
contentType: content_type,
|
125
|
+
content: content
|
126
|
+
)
|
127
|
+
when :DataTable
|
128
|
+
rows = get_table_rows(node)
|
129
|
+
reject_nils(
|
130
|
+
type: node.rule_type,
|
131
|
+
location: rows[0][:location],
|
132
|
+
rows: rows,
|
133
|
+
)
|
134
|
+
when :Background
|
135
|
+
background_line = node.get_token(:BackgroundLine)
|
136
|
+
description = get_description(node)
|
137
|
+
steps = get_steps(node)
|
138
|
+
|
139
|
+
reject_nils(
|
140
|
+
type: node.rule_type,
|
141
|
+
location: get_location(background_line),
|
142
|
+
keyword: background_line.matched_keyword,
|
143
|
+
name: background_line.matched_text,
|
144
|
+
description: description,
|
145
|
+
steps: steps
|
146
|
+
)
|
147
|
+
when :Scenario_Definition
|
148
|
+
tags = get_tags(node)
|
149
|
+
scenario_node = node.get_single(:Scenario)
|
150
|
+
if(scenario_node)
|
151
|
+
scenario_line = scenario_node.get_token(:ScenarioLine)
|
152
|
+
description = get_description(scenario_node)
|
153
|
+
steps = get_steps(scenario_node)
|
154
|
+
|
155
|
+
reject_nils(
|
156
|
+
type: scenario_node.rule_type,
|
157
|
+
tags: tags,
|
158
|
+
location: get_location(scenario_line),
|
159
|
+
keyword: scenario_line.matched_keyword,
|
160
|
+
name: scenario_line.matched_text,
|
161
|
+
description: description,
|
162
|
+
steps: steps
|
163
|
+
)
|
164
|
+
else
|
165
|
+
scenario_outline_node = node.get_single(:ScenarioOutline)
|
166
|
+
raise 'Internal grammar error' unless scenario_outline_node
|
167
|
+
|
168
|
+
scenario_outline_line = scenario_outline_node.get_token(:ScenarioOutlineLine)
|
169
|
+
description = get_description(scenario_outline_node)
|
170
|
+
steps = get_steps(scenario_outline_node)
|
171
|
+
examples = scenario_outline_node.get_items(:Examples_Definition)
|
172
|
+
|
173
|
+
reject_nils(
|
174
|
+
type: scenario_outline_node.rule_type,
|
175
|
+
tags: tags,
|
176
|
+
location: get_location(scenario_outline_line),
|
177
|
+
keyword: scenario_outline_line.matched_keyword,
|
178
|
+
name: scenario_outline_line.matched_text,
|
179
|
+
description: description,
|
180
|
+
steps: steps,
|
181
|
+
examples: examples
|
182
|
+
)
|
183
|
+
end
|
184
|
+
when :Examples_Definition
|
185
|
+
tags = get_tags(node)
|
186
|
+
examples_node = node.get_single(:Examples)
|
187
|
+
examples_line = examples_node.get_token(:ExamplesLine)
|
188
|
+
description = get_description(examples_node)
|
189
|
+
rows = get_table_rows(examples_node)
|
190
|
+
|
191
|
+
reject_nils(
|
192
|
+
type: examples_node.rule_type,
|
193
|
+
tags: tags,
|
194
|
+
location: get_location(examples_line),
|
195
|
+
keyword: examples_line.matched_keyword,
|
196
|
+
name: examples_line.matched_text,
|
197
|
+
description: description,
|
198
|
+
tableHeader: rows.first,
|
199
|
+
tableBody: rows[1..-1]
|
200
|
+
)
|
201
|
+
when :Description
|
202
|
+
line_tokens = node.get_tokens(:Other)
|
203
|
+
# Trim trailing empty lines
|
204
|
+
last_non_empty = line_tokens.rindex { |token| !token.line.trimmed_line_text.empty? }
|
205
|
+
description = line_tokens[0..last_non_empty].map { |token| token.matched_text }.join("\n")
|
206
|
+
return description
|
207
|
+
when :Feature
|
208
|
+
header = node.get_single(:Feature_Header)
|
209
|
+
return unless header
|
210
|
+
tags = get_tags(header)
|
211
|
+
feature_line = header.get_token(:FeatureLine)
|
212
|
+
return unless feature_line
|
213
|
+
background = node.get_single(:Background)
|
214
|
+
scenario_definitions = node.get_items(:Scenario_Definition)
|
215
|
+
description = get_description(header)
|
216
|
+
language = feature_line.matched_gherkin_dialect
|
217
|
+
|
218
|
+
reject_nils(
|
219
|
+
type: node.rule_type,
|
220
|
+
tags: tags,
|
221
|
+
location: get_location(feature_line),
|
222
|
+
language: language,
|
223
|
+
keyword: feature_line.matched_keyword,
|
224
|
+
name: feature_line.matched_text,
|
225
|
+
description: description,
|
226
|
+
background: background,
|
227
|
+
scenarioDefinitions: scenario_definitions,
|
228
|
+
comments: @comments
|
229
|
+
)
|
230
|
+
else
|
231
|
+
return node
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def reject_nils(values)
|
236
|
+
values.reject { |k,v| v.nil? }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|