gherkin3 3.0.0.alpha.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.
- 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
|