gherkin_language 0.0.2
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/.rubocop.yml +38 -0
- data/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/Guardfile +3 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +29 -0
- data/bin/gherkin_language +34 -0
- data/features/report.feature +58 -0
- data/features/sentences.feature +127 -0
- data/features/support/env.rb +5 -0
- data/features/tag.feature +53 -0
- data/gherkin_language.gemspec +14 -0
- data/lib/gherkin_language.rb +333 -0
- data/test/test_gherkin_language.rb +61 -0
- data/test/test_gherkin_language_tool.rb +40 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e36578ba3f6c90fdc22f8ddde30d96a0ac9c955
|
4
|
+
data.tar.gz: 96d5cfa9266ad79f3c287c7101b27669cf47e009
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 550d087efcbff10a081dda79bab4d26060c6c84df5777a1ae24112dbafbd61ca11775685bf43f7b1b8a84d8e23670a307d907467863be3fc9181ecad4b60804b
|
7
|
+
data.tar.gz: 951757d91180b2cdb0720ecb1e325aab15779e34e86a532b3222b63a30effe5a1ee7987c5070fccd510da2adb6b3321de3564498627532a85a31e84b4b237dda
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2015-07-02 21:10:32 +0200 using RuboCop version 0.31.0.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 5
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Max: 47
|
11
|
+
|
12
|
+
# Offense count: 2
|
13
|
+
# Configuration parameters: CountComments.
|
14
|
+
Metrics/ClassLength:
|
15
|
+
Max: 159
|
16
|
+
|
17
|
+
# Offense count: 2
|
18
|
+
Metrics/CyclomaticComplexity:
|
19
|
+
Max: 11
|
20
|
+
|
21
|
+
# Offense count: 17
|
22
|
+
# Configuration parameters: AllowURI, URISchemes.
|
23
|
+
Metrics/LineLength:
|
24
|
+
Max: 121
|
25
|
+
|
26
|
+
# Offense count: 8
|
27
|
+
# Configuration parameters: CountComments.
|
28
|
+
Metrics/MethodLength:
|
29
|
+
Max: 36
|
30
|
+
|
31
|
+
# Offense count: 1
|
32
|
+
# Configuration parameters: CountKeywordArgs.
|
33
|
+
Metrics/ParameterLists:
|
34
|
+
Max: 8
|
35
|
+
|
36
|
+
# Offense count: 2
|
37
|
+
Metrics/PerceivedComplexity:
|
38
|
+
Max: 12
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Funkwerk AG
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Check Language of Gherkin Files
|
2
|
+
===============================
|
3
|
+
|
4
|
+
[](https://travis-ci.org/funkwerk/gherkin_language)
|
5
|
+
|
6
|
+
This tool analyzes the language of gherkin files and report language errors.
|
7
|
+
Currently just English is supported.
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
run `gherkin_language` on a list of files
|
13
|
+
|
14
|
+
gherkin_language FEATURE_FILES
|
15
|
+
|
16
|
+
`gherkin_language` caches valid sentences. If all sentences are valid, it just needs some time to hash them.
|
17
|
+
|
18
|
+
To disable usage of cache, start it with `--no-cache`.
|
19
|
+
|
20
|
+
To just extract the sentences, start it with `--sentences`. This could be helpful for using these sentences in another tool. It should not be used for formatting issues. For formatting `gherkin_format --template ...` could be used.
|
21
|
+
|
22
|
+
To tag all words used, start it with `--tag`. This allows to build up a glossary based on the feature files provided.
|
23
|
+
|
24
|
+
|
25
|
+
Glossary
|
26
|
+
--------
|
27
|
+
|
28
|
+
It happens that there are words which are unknown to the dictionary.
|
29
|
+
Once this happens think about if could use another word, that is more common. If there is no such word, add it to the directory-located glossary.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
task default: :build
|
4
|
+
|
5
|
+
desc 'Builds the Gem.'
|
6
|
+
task build: :test do
|
7
|
+
sh 'gem build gherkin_language.gemspec'
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
end
|
13
|
+
|
14
|
+
task test: :rubocop
|
15
|
+
task test: :cucumber
|
16
|
+
|
17
|
+
desc 'Publishes the Gem'
|
18
|
+
task :push do
|
19
|
+
sh 'gem push gherkin_language-0.0.2.gem'
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Checks ruby style'
|
23
|
+
task :rubocop do
|
24
|
+
sh 'rubocop'
|
25
|
+
end
|
26
|
+
|
27
|
+
task :cucumber do
|
28
|
+
sh 'cucumber'
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gherkin_language'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
options = {}
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: gherkin_language [files]'
|
8
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |verbose|
|
9
|
+
options[:verbose] = verbose
|
10
|
+
end
|
11
|
+
opts.on('--sentences', 'extract relevant sentences') do |sentences|
|
12
|
+
options[:sentences] = sentences
|
13
|
+
end
|
14
|
+
opts.on('--tag', 'tag words') do |tag|
|
15
|
+
options[:tag] = tag
|
16
|
+
end
|
17
|
+
opts.on('--no-cache', 'do not use cache') do |no_cache|
|
18
|
+
options[:no_cache] = no_cache
|
19
|
+
end
|
20
|
+
end.parse!
|
21
|
+
|
22
|
+
language = GherkinLanguage.new(options.key? :no_cache)
|
23
|
+
|
24
|
+
if options.key? :sentences
|
25
|
+
ARGV.each { |file| puts language.extract_sentences language.parse file }
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
if options.key? :tag
|
29
|
+
puts language.tag ARGV
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
ARGV.each { |file| language.analyze file }
|
34
|
+
exit language.report
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: Report
|
2
|
+
As a Business Analyst
|
3
|
+
I want to get a report for language mistakes within my feature files
|
4
|
+
so that I am able to fix them
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given a file named "report.rb" with:
|
8
|
+
"""
|
9
|
+
$LOAD_PATH << '../../lib'
|
10
|
+
require 'gherkin_language'
|
11
|
+
|
12
|
+
no_cache = true
|
13
|
+
language = GherkinLanguage.new no_cache
|
14
|
+
language.analyze 'test.feature'
|
15
|
+
exit language.report
|
16
|
+
|
17
|
+
"""
|
18
|
+
|
19
|
+
Scenario: Broken Sentence
|
20
|
+
Given a file named "test.feature" with:
|
21
|
+
"""
|
22
|
+
Feature: Test
|
23
|
+
Scenario: Tag
|
24
|
+
Given an test
|
25
|
+
When execute
|
26
|
+
Then pass
|
27
|
+
"""
|
28
|
+
When I run `ruby report.rb`
|
29
|
+
Then it should fail with exactly:
|
30
|
+
"""
|
31
|
+
[misspelling] EN_A_VS_AN
|
32
|
+
Use 'a' instead of 'an' if the following word doesn't start with a vowel sound, e.g. 'a sentence', 'a university'
|
33
|
+
Context: Given an test when execute then pass
|
34
|
+
Replacements: a
|
35
|
+
References: test.feature
|
36
|
+
|
37
|
+
"""
|
38
|
+
|
39
|
+
Scenario: Unknown Words
|
40
|
+
Given a file named "test.feature" with:
|
41
|
+
"""
|
42
|
+
Feature: Test
|
43
|
+
Scenario: Tag
|
44
|
+
Given a test
|
45
|
+
When exicute
|
46
|
+
Then pass
|
47
|
+
"""
|
48
|
+
When I run `ruby report.rb`
|
49
|
+
Then it should fail with exactly:
|
50
|
+
"""
|
51
|
+
[misspelling] MORFOLOGIK_RULE_EN_US
|
52
|
+
Possible spelling mistake found
|
53
|
+
Context: Given a test when exicute then pass
|
54
|
+
Replacements: execute
|
55
|
+
References: test.feature
|
56
|
+
1 unknown words: exicute
|
57
|
+
|
58
|
+
"""
|
@@ -0,0 +1,127 @@
|
|
1
|
+
Feature: Sentences
|
2
|
+
As a Business Analyst
|
3
|
+
I want to extract all sentences out of my feature files
|
4
|
+
so that I could do further analysis on them
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given a file named "extract_sentences.rb" with:
|
8
|
+
"""
|
9
|
+
$LOAD_PATH << '../../lib'
|
10
|
+
require 'gherkin_language'
|
11
|
+
|
12
|
+
language = GherkinLanguage.new false
|
13
|
+
puts language.extract_sentences language.parse 'foo.feature'
|
14
|
+
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: Extract Sentences
|
18
|
+
Given a file named "foo.feature" with:
|
19
|
+
"""
|
20
|
+
Feature: Foo
|
21
|
+
Scenario: Bar
|
22
|
+
Given a foo
|
23
|
+
When I bar
|
24
|
+
Then I baz
|
25
|
+
"""
|
26
|
+
When I run `ruby extract_sentences.rb`
|
27
|
+
Then it should pass with:
|
28
|
+
"""
|
29
|
+
Foo
|
30
|
+
Bar
|
31
|
+
Given a foo when I bar then I baz
|
32
|
+
"""
|
33
|
+
|
34
|
+
Scenario: Extract Sentences with background
|
35
|
+
Given a file named "foo.feature" with:
|
36
|
+
"""
|
37
|
+
Feature: Foo
|
38
|
+
Background:
|
39
|
+
Given something
|
40
|
+
|
41
|
+
Scenario: Bar
|
42
|
+
Given a foo
|
43
|
+
When I bar
|
44
|
+
Then I baz
|
45
|
+
"""
|
46
|
+
When I run `ruby extract_sentences.rb`
|
47
|
+
Then it should pass with:
|
48
|
+
"""
|
49
|
+
Foo
|
50
|
+
Bar
|
51
|
+
Given something and a foo when I bar then I baz
|
52
|
+
"""
|
53
|
+
|
54
|
+
Scenario: Extract Sentences from outlines
|
55
|
+
Given a file named "foo.feature" with:
|
56
|
+
"""
|
57
|
+
Feature: Foo
|
58
|
+
Scenario Outline: Bar
|
59
|
+
Given a <foo>
|
60
|
+
When I <bar>
|
61
|
+
Then I <baz>
|
62
|
+
|
63
|
+
Examples: table
|
64
|
+
| foo | bar | baz |
|
65
|
+
| FOO | BAR | BAZ |
|
66
|
+
| oof | rab | zab |
|
67
|
+
"""
|
68
|
+
When I run `ruby extract_sentences.rb`
|
69
|
+
Then it should pass with:
|
70
|
+
"""
|
71
|
+
Foo
|
72
|
+
Bar
|
73
|
+
table
|
74
|
+
Given a FOO when I BAR then I BAZ
|
75
|
+
Given a oof when I rab then I zab
|
76
|
+
"""
|
77
|
+
|
78
|
+
Scenario: Extract Sentences considers feature description
|
79
|
+
Given a file named "foo.feature" with:
|
80
|
+
"""
|
81
|
+
Feature: Foo
|
82
|
+
As a user,
|
83
|
+
I want something
|
84
|
+
so that I have that
|
85
|
+
|
86
|
+
Scenario: Bar
|
87
|
+
Given a foo
|
88
|
+
When I bar
|
89
|
+
Then I baz
|
90
|
+
"""
|
91
|
+
When I run `ruby extract_sentences.rb`
|
92
|
+
Then it should pass with:
|
93
|
+
"""
|
94
|
+
Foo
|
95
|
+
As a user,
|
96
|
+
I want something
|
97
|
+
so that I have that
|
98
|
+
Bar
|
99
|
+
Given a foo when I bar then I baz
|
100
|
+
"""
|
101
|
+
|
102
|
+
Scenario: Extract Sentences considers scenario description
|
103
|
+
Given a file named "foo.feature" with:
|
104
|
+
"""
|
105
|
+
Feature: Foo
|
106
|
+
As a user,
|
107
|
+
I want something
|
108
|
+
so that I have that
|
109
|
+
|
110
|
+
Scenario: Bar
|
111
|
+
This is a sentence description
|
112
|
+
|
113
|
+
Given a foo
|
114
|
+
When I bar
|
115
|
+
Then I baz
|
116
|
+
"""
|
117
|
+
When I run `ruby extract_sentences.rb`
|
118
|
+
Then it should pass with:
|
119
|
+
"""
|
120
|
+
Foo
|
121
|
+
As a user,
|
122
|
+
I want something
|
123
|
+
so that I have that
|
124
|
+
Bar
|
125
|
+
This is a sentence description
|
126
|
+
Given a foo when I bar then I baz
|
127
|
+
"""
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Feature: Tag
|
2
|
+
As a Business Analyst
|
3
|
+
I want to tag all words from my features files
|
4
|
+
so that I can build up a glossary or check against forbidden words
|
5
|
+
|
6
|
+
Background: Prepare Testee
|
7
|
+
Given a file named "tag.rb" with:
|
8
|
+
"""
|
9
|
+
$LOAD_PATH << '../../lib'
|
10
|
+
require 'gherkin_language'
|
11
|
+
|
12
|
+
no_cache = true
|
13
|
+
language = GherkinLanguage.new no_cache
|
14
|
+
puts language.tag ['tag.feature']
|
15
|
+
|
16
|
+
"""
|
17
|
+
|
18
|
+
Scenario: Tag words
|
19
|
+
Given a file named "tag.feature" with:
|
20
|
+
"""
|
21
|
+
Feature: Test
|
22
|
+
Scenario: Tag
|
23
|
+
Given a test
|
24
|
+
When execute
|
25
|
+
Then pass
|
26
|
+
"""
|
27
|
+
When I run `ruby tag.rb`
|
28
|
+
Then it should pass with exactly:
|
29
|
+
"""
|
30
|
+
<S>
|
31
|
+
[[[/null]
|
32
|
+
"["/``]
|
33
|
+
Test[Test/NNP]
|
34
|
+
"["/'']
|
35
|
+
,[,/,,O]
|
36
|
+
"["/``,O]
|
37
|
+
Tag[tag/NN:UN,tag/VB,tag/VBP]
|
38
|
+
"["/'']
|
39
|
+
,[,/,,O]
|
40
|
+
"["/``,O]
|
41
|
+
Given[Given/NNP,B-VP]
|
42
|
+
a[a/DT,B-NP-singular]
|
43
|
+
test[test/JJ,test/NN,test/VB,test/VBP,E-NP-singular]
|
44
|
+
when[when/WRB,B-ADVP]
|
45
|
+
execute[execute/VB,execute/VBP,B-VP]
|
46
|
+
then[then/JJ,then/NN,then/RB,I-VP]
|
47
|
+
pass[pass/JJ,pass/NN,pass/VB,pass/VBP,I-VP]
|
48
|
+
"["/'',B-ADVP]
|
49
|
+
]
|
50
|
+
[</S>,O]
|
51
|
+
|
52
|
+
|
53
|
+
"""
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'gherkin_language'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.date = '2015-07-02'
|
5
|
+
s.summary = 'Gherkin Language'
|
6
|
+
s.description = 'Check language of Gherkin Files'
|
7
|
+
s.authors = ['Stefan Rohe']
|
8
|
+
s.homepage = 'http://github.com/funkwerk/gherkin_language/'
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.executables = s.files.grep(%r{^bin/}) { |file| File.basename(file) }
|
11
|
+
s.add_runtime_dependency 'gherkin', ['>= 2.12.2']
|
12
|
+
s.add_runtime_dependency 'term-ansicolor', ['>= 1.3.2']
|
13
|
+
s.add_development_dependency 'aruba', ['>= 0.6.2']
|
14
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'gherkin/formatter/json_formatter'
|
2
|
+
require 'gherkin/parser/parser'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'stringio'
|
5
|
+
require 'multi_json'
|
6
|
+
require 'term/ansicolor'
|
7
|
+
include Term::ANSIColor
|
8
|
+
require 'tmpdir'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'yaml'
|
11
|
+
require 'set'
|
12
|
+
require 'digest'
|
13
|
+
|
14
|
+
# gherkin utilities
|
15
|
+
class GherkinLanguage
|
16
|
+
# This service class provides access to language tool process.
|
17
|
+
class LanguageToolProcess
|
18
|
+
attr_accessor :errors, :unknown_words
|
19
|
+
|
20
|
+
VERSION = 'LanguageTool-3.0'
|
21
|
+
URL = "https://www.languagetool.org/download/#{VERSION}.zip"
|
22
|
+
|
23
|
+
# This value entity class represents a language error
|
24
|
+
class Error
|
25
|
+
attr_accessor :category, :context, :issuetype, :message, :replacements, :rule, :from_y, :to_y
|
26
|
+
|
27
|
+
def initialize(category, context, issuetype, message, replacements, rule, from_y, to_y)
|
28
|
+
@category = category
|
29
|
+
@context = context
|
30
|
+
@issuetype = issuetype
|
31
|
+
@message = message
|
32
|
+
@replacements = replacements
|
33
|
+
@rule = rule
|
34
|
+
@from_y = from_y
|
35
|
+
@to_y = to_y
|
36
|
+
end
|
37
|
+
|
38
|
+
def str(references)
|
39
|
+
(red("[#{@issuetype}] #{@rule}\n") +
|
40
|
+
" #{@message}\n Context: #{@context}\n Replacements: #{@replacements}\n References: #{references * ', '}\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
path = Dir.tmpdir
|
46
|
+
download path unless File.exist? "#{path}/#{VERSION}/languagetool-commandline.jar"
|
47
|
+
@path = path
|
48
|
+
@p = nil
|
49
|
+
@reference_line = 0
|
50
|
+
@errors = []
|
51
|
+
@unknown_words = []
|
52
|
+
use_user_glossary "#{path}/#{VERSION}" if File.exist? '.glossary'
|
53
|
+
end
|
54
|
+
|
55
|
+
def use_user_glossary(path)
|
56
|
+
resource_path = "#{path}/org/languagetool/resource/en"
|
57
|
+
system "cp #{resource_path}/added.txt #{resource_path}/added.copy && cp .glossary #{resource_path}/added.txt"
|
58
|
+
at_exit do
|
59
|
+
system "cp #{resource_path}/added.copy #{resource_path}/added.txt"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def download(path)
|
64
|
+
system "wget --quiet #{URL} -O /var/tmp/languagetool.zip"
|
65
|
+
FileUtils.mkdir_p path
|
66
|
+
system "unzip -qq -u /var/tmp/languagetool.zip -d #{path}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def start!
|
70
|
+
@errors = []
|
71
|
+
@unknown_words = []
|
72
|
+
@reference_line = 0
|
73
|
+
Dir.chdir("#{@path}/#{VERSION}/") do
|
74
|
+
@p = IO.popen('java -jar languagetool-commandline.jar --list-unknown --api --language en-US -', 'r+')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def tag(sentences)
|
79
|
+
output = ''
|
80
|
+
Dir.chdir("#{@path}/#{VERSION}/") do
|
81
|
+
p = IO.popen('java -jar languagetool-commandline.jar --taggeronly --api --language en-US -', 'r+')
|
82
|
+
sentences.each { |sentence| p.write sentence }
|
83
|
+
p.close_write
|
84
|
+
line = p.readline
|
85
|
+
loop do
|
86
|
+
break if line == "<!--\n"
|
87
|
+
output << line
|
88
|
+
line = p.readline
|
89
|
+
end
|
90
|
+
p.close
|
91
|
+
end
|
92
|
+
output.gsub!(' ', "\n")
|
93
|
+
output.gsub!(']', "]\n")
|
94
|
+
output.gsub!("\n\n", "\n")
|
95
|
+
output
|
96
|
+
end
|
97
|
+
|
98
|
+
def check_paragraph(paragraph)
|
99
|
+
start_line = @reference_line
|
100
|
+
send paragraph
|
101
|
+
end_line = @reference_line
|
102
|
+
send "\n\n"
|
103
|
+
Range.new(start_line, end_line)
|
104
|
+
end
|
105
|
+
|
106
|
+
def send(sentence)
|
107
|
+
@reference_line += sentence.count "\n"
|
108
|
+
@p.write sentence
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_errors(result)
|
112
|
+
doc = REXML::Document.new result
|
113
|
+
errors = []
|
114
|
+
doc.elements.each '//error' do |error|
|
115
|
+
errors.push Error.new(
|
116
|
+
error.attributes['category'],
|
117
|
+
error.attributes['context'],
|
118
|
+
error.attributes['locqualityissuetype'],
|
119
|
+
error.attributes['msg'],
|
120
|
+
error.attributes['replacements'],
|
121
|
+
error.attributes['ruleId'],
|
122
|
+
error.attributes['fromy'].to_i,
|
123
|
+
error.attributes['toy'].to_i)
|
124
|
+
end
|
125
|
+
errors
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse_unknown_words(result)
|
129
|
+
doc = REXML::Document.new result
|
130
|
+
errors = []
|
131
|
+
doc.elements.each '//unknown_words/word' do |error|
|
132
|
+
errors.push error.text
|
133
|
+
end
|
134
|
+
errors
|
135
|
+
end
|
136
|
+
|
137
|
+
def stop!
|
138
|
+
@p.close_write
|
139
|
+
errors = ''
|
140
|
+
line = @p.readline
|
141
|
+
loop do
|
142
|
+
break if line == "<!--\n"
|
143
|
+
errors << line
|
144
|
+
line = @p.readline
|
145
|
+
end
|
146
|
+
@errors = parse_errors errors
|
147
|
+
@unknown_words = parse_unknown_words errors
|
148
|
+
@p.close
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(no_cache = false)
|
153
|
+
path = "~/.gherkin_language/#{LanguageToolProcess::VERSION}/accepted_paragraphs.yml"
|
154
|
+
@settings_path = File.expand_path path
|
155
|
+
@accepted_paragraphs = {}
|
156
|
+
begin
|
157
|
+
@accepted_paragraphs = YAML.load_file @settings_path unless no_cache
|
158
|
+
rescue
|
159
|
+
puts 'could not read settings'
|
160
|
+
end
|
161
|
+
@references = {}
|
162
|
+
@line_to_reference = {}
|
163
|
+
end
|
164
|
+
|
165
|
+
def analyze(file)
|
166
|
+
sentences = extract_sentences parse file
|
167
|
+
sentences.select! { |sentence| !accepted? sentence }
|
168
|
+
return if sentences.empty?
|
169
|
+
sentences.each do |sentence|
|
170
|
+
stripped = sentence.strip
|
171
|
+
@references[stripped] = [] unless @references.include? stripped
|
172
|
+
@references[stripped].push file
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def accepted?(sentence)
|
177
|
+
return false if @accepted_paragraphs.nil?
|
178
|
+
key = :without_glossary
|
179
|
+
key = hash(File.read '.glossary') if File.exist? '.glossary'
|
180
|
+
|
181
|
+
return false unless @accepted_paragraphs.key? key
|
182
|
+
@accepted_paragraphs[key].include? hash sentence
|
183
|
+
end
|
184
|
+
|
185
|
+
def hash(value)
|
186
|
+
Digest::MD5.digest value.strip
|
187
|
+
end
|
188
|
+
|
189
|
+
def parse(file)
|
190
|
+
content = File.read file
|
191
|
+
to_json(content, file)
|
192
|
+
end
|
193
|
+
|
194
|
+
def extract_sentences(parsed)
|
195
|
+
feature_names = lambda do |input|
|
196
|
+
input.map { |feature| feature['name'] unless feature['name'] == '' }
|
197
|
+
end
|
198
|
+
|
199
|
+
descriptions = lambda do |input|
|
200
|
+
input.map { |feature| feature['description'] unless feature['description'] == '' }
|
201
|
+
end
|
202
|
+
|
203
|
+
sentences = feature_names.call(parsed) + descriptions.call(parsed) + scenario_names(parsed) + sentences(parsed)
|
204
|
+
sentences.select! { |sentence| sentence }
|
205
|
+
sentences.map { |sentence| sentence.gsub(/ «.+»/, '') }
|
206
|
+
end
|
207
|
+
|
208
|
+
def tag(files)
|
209
|
+
sentences = files.map { |file| extract_sentences parse file }
|
210
|
+
language = LanguageToolProcess.new
|
211
|
+
language.tag sentences
|
212
|
+
end
|
213
|
+
|
214
|
+
def report
|
215
|
+
return if @references.keys.empty?
|
216
|
+
language = LanguageToolProcess.new
|
217
|
+
language.start!
|
218
|
+
|
219
|
+
@references.keys.each do |sentence|
|
220
|
+
location = language.check_paragraph sentence
|
221
|
+
location.map { |line| @line_to_reference[line] = sentence }
|
222
|
+
end
|
223
|
+
language.stop!
|
224
|
+
errors = language.errors
|
225
|
+
unknown_words = language.unknown_words
|
226
|
+
|
227
|
+
used_refs = Set.new []
|
228
|
+
errors.each do |error|
|
229
|
+
used_refs.add @line_to_reference[error.from_y]
|
230
|
+
local_refs = @references[@line_to_reference[error.from_y]]
|
231
|
+
puts error.str local_refs
|
232
|
+
end
|
233
|
+
# TODO: list references for unknown words
|
234
|
+
puts red "#{unknown_words.count} unknown words: #{unknown_words * ', '}" unless unknown_words.empty?
|
235
|
+
return -1 unless unknown_words.empty?
|
236
|
+
|
237
|
+
@references.each do |sentence, _refs|
|
238
|
+
next if used_refs.include? sentence
|
239
|
+
key = :without_glossary
|
240
|
+
key = hash(File.read '.glossary') if File.exist? '.glossary'
|
241
|
+
|
242
|
+
@accepted_paragraphs[key] = Set.new [] unless @accepted_paragraphs.key? key
|
243
|
+
@accepted_paragraphs[key].add hash sentence
|
244
|
+
end
|
245
|
+
|
246
|
+
FileUtils.mkdir_p File.dirname @settings_path
|
247
|
+
File.open(@settings_path, 'w') do |settings_file|
|
248
|
+
settings_file.write @accepted_paragraphs.to_yaml
|
249
|
+
end
|
250
|
+
return -1 unless errors.empty?
|
251
|
+
0
|
252
|
+
end
|
253
|
+
|
254
|
+
def to_json(input, file = 'generated.feature')
|
255
|
+
io = StringIO.new
|
256
|
+
formatter = Gherkin::Formatter::JSONFormatter.new(io)
|
257
|
+
parser = Gherkin::Parser::Parser.new(formatter, true)
|
258
|
+
parser.parse(input, file, 0)
|
259
|
+
formatter.done
|
260
|
+
MultiJson.load io.string
|
261
|
+
end
|
262
|
+
|
263
|
+
def scenario_names(input)
|
264
|
+
# TODO: scenario outlines with example values inside?
|
265
|
+
scenarios = []
|
266
|
+
input.each do |features|
|
267
|
+
next unless features.key? 'elements'
|
268
|
+
elements = features['elements']
|
269
|
+
elements.each do |scenario|
|
270
|
+
scenarios.push scenario['name'] if scenario['type'] == 'scenario'
|
271
|
+
scenarios.push scenario['name'] if scenario['type'] == 'scenario_outline'
|
272
|
+
scenarios.push scenario['description'] unless scenario['description'].empty?
|
273
|
+
end
|
274
|
+
end
|
275
|
+
scenarios
|
276
|
+
end
|
277
|
+
|
278
|
+
def sentences(input)
|
279
|
+
sentences = []
|
280
|
+
background = []
|
281
|
+
input.each do |features|
|
282
|
+
next unless features.key? 'elements'
|
283
|
+
elements = features['elements']
|
284
|
+
elements.each do |scenario|
|
285
|
+
next unless scenario.key? 'steps'
|
286
|
+
terms = background.dup
|
287
|
+
if scenario['type'] == 'background'
|
288
|
+
scenario['steps'].each do |step|
|
289
|
+
new_terms = [step['keyword'], step['name']].join
|
290
|
+
new_terms = uncapitalize(new_terms) unless terms.empty?
|
291
|
+
background.push new_terms
|
292
|
+
end
|
293
|
+
next
|
294
|
+
end
|
295
|
+
|
296
|
+
scenario['steps'].each do |step|
|
297
|
+
keyword = step['keyword']
|
298
|
+
keyword = 'and ' unless background.empty? || keyword != 'Given '
|
299
|
+
new_terms = [keyword, step['name']].join
|
300
|
+
new_terms = uncapitalize(new_terms) unless terms.empty?
|
301
|
+
terms.push new_terms
|
302
|
+
end
|
303
|
+
sentence = terms.join ' '
|
304
|
+
if scenario.key? 'examples'
|
305
|
+
# TODO: support for multiple examples?
|
306
|
+
scenario['examples'].each do |example|
|
307
|
+
sentences.push example['name'] unless example['name'].empty?
|
308
|
+
sentences.push example['description'] unless example['description'].empty?
|
309
|
+
expand_outlines(sentence.strip, example).map { |expanded| sentences.push expanded }
|
310
|
+
end
|
311
|
+
else
|
312
|
+
sentences.push sentence.strip
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
sentences
|
317
|
+
end
|
318
|
+
|
319
|
+
def uncapitalize(term)
|
320
|
+
term[0, 1].downcase + term[1..-1]
|
321
|
+
end
|
322
|
+
|
323
|
+
def expand_outlines(sentence, example)
|
324
|
+
result = []
|
325
|
+
headers = example['rows'][0]['cells']
|
326
|
+
example['rows'].slice(1, example['rows'].length).each do |row|
|
327
|
+
modified_sentence = sentence.dup
|
328
|
+
headers.zip(row['cells']).map { |key, value| modified_sentence.gsub!("<#{key}>", value) }
|
329
|
+
result.push modified_sentence
|
330
|
+
end
|
331
|
+
result
|
332
|
+
end
|
333
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'gherkin_language'
|
3
|
+
|
4
|
+
# gherkin language test
|
5
|
+
class GherkinLanguageTest < Minitest::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@gherkin = GherkinLanguage.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_to_json
|
11
|
+
# setup
|
12
|
+
feature = %(Feature: Foo
|
13
|
+
Scenario: Bar
|
14
|
+
)
|
15
|
+
expected = MultiJson.load %(
|
16
|
+
[
|
17
|
+
{
|
18
|
+
"keyword": "Feature",
|
19
|
+
"name": "Foo",
|
20
|
+
"line": 1,
|
21
|
+
"description": "",
|
22
|
+
"id": "foo",
|
23
|
+
"uri": "generated.feature",
|
24
|
+
"elements":
|
25
|
+
[
|
26
|
+
{
|
27
|
+
"keyword": "Scenario", "name": "Bar",
|
28
|
+
"line": 2, "description": "",
|
29
|
+
"id": "foo;bar", "type": "scenario"
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
# exercise
|
37
|
+
actual = @gherkin.to_json feature
|
38
|
+
|
39
|
+
# verify
|
40
|
+
assert_equal expected, actual
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_sentences
|
44
|
+
# setup
|
45
|
+
feature = %(Feature: Foo
|
46
|
+
Scenario: Bar
|
47
|
+
Given a Foo
|
48
|
+
And another Foo
|
49
|
+
But no Bar
|
50
|
+
When a Bar
|
51
|
+
Then a Baz
|
52
|
+
)
|
53
|
+
expected = ['Given a Foo and another Foo but no Bar when a Bar then a Baz']
|
54
|
+
|
55
|
+
# exercise
|
56
|
+
actual = @gherkin.sentences @gherkin.to_json feature
|
57
|
+
|
58
|
+
# verify
|
59
|
+
assert_equal expected, actual
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'gherkin_language'
|
3
|
+
|
4
|
+
# checks language
|
5
|
+
class LanguageTest < Minitest::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@language = GherkinLanguage::LanguageToolProcess.new
|
8
|
+
@language.start!
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_check_valid_paragraph
|
12
|
+
# setup
|
13
|
+
feature = 'This is good English'
|
14
|
+
|
15
|
+
# exercise
|
16
|
+
@language.check_paragraph feature
|
17
|
+
@language.stop!
|
18
|
+
actual = @language.errors
|
19
|
+
unknown_words = @language.unknown_words
|
20
|
+
|
21
|
+
# verify
|
22
|
+
assert_empty actual
|
23
|
+
assert_empty unknown_words
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_check_invalid_paragraph
|
27
|
+
# setup
|
28
|
+
feature = 'This are no English good'
|
29
|
+
|
30
|
+
# exercise
|
31
|
+
@language.check_paragraph feature
|
32
|
+
@language.stop!
|
33
|
+
actual = @language.errors
|
34
|
+
unknown_words = @language.unknown_words
|
35
|
+
|
36
|
+
# verify
|
37
|
+
refute_empty actual
|
38
|
+
assert_empty unknown_words
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gherkin_language
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Rohe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gherkin
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.12.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.12.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: term-ansicolor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.3.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aruba
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.2
|
55
|
+
description: Check language of Gherkin Files
|
56
|
+
email:
|
57
|
+
executables:
|
58
|
+
- gherkin_language
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".rubocop.yml"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Guardfile
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/gherkin_language
|
70
|
+
- features/report.feature
|
71
|
+
- features/sentences.feature
|
72
|
+
- features/support/env.rb
|
73
|
+
- features/tag.feature
|
74
|
+
- gherkin_language.gemspec
|
75
|
+
- lib/gherkin_language.rb
|
76
|
+
- test/test_gherkin_language.rb
|
77
|
+
- test/test_gherkin_language_tool.rb
|
78
|
+
homepage: http://github.com/funkwerk/gherkin_language/
|
79
|
+
licenses: []
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.2.3
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Gherkin Language
|
101
|
+
test_files: []
|