chutney 3.0.0.beta.2 → 3.0.0
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 +4 -4
- data/.rubocop.yml +2 -2
- data/README.md +1 -1
- data/chutney.gemspec +1 -1
- data/config/{chutney.yml → chutney_defaults.yml} +0 -0
- data/exe/chutney +5 -5
- data/lib/chutney.rb +8 -3
- data/lib/chutney/configuration.rb +3 -2
- data/lib/chutney/formatter.rb +4 -5
- data/lib/chutney/formatter/pie_formatter.rb +9 -10
- data/lib/chutney/formatter/rainbow_formatter.rb +8 -11
- data/lib/chutney/linter.rb +34 -34
- data/lib/chutney/linter/avoid_full_stop.rb +1 -3
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +3 -3
- data/lib/chutney/linter/avoid_scripting.rb +2 -2
- data/lib/chutney/linter/background_does_more_than_setup.rb +3 -4
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +1 -1
- data/lib/chutney/linter/bad_scenario_name.rb +2 -2
- data/lib/chutney/linter/file_name_differs_feature_name.rb +3 -3
- data/lib/chutney/linter/givens_after_background.rb +2 -2
- data/lib/chutney/linter/invalid_file_name.rb +1 -1
- data/lib/chutney/linter/invalid_step_flow.rb +2 -2
- data/lib/chutney/linter/missing_example_name.rb +2 -4
- data/lib/chutney/linter/missing_feature_description.rb +1 -1
- data/lib/chutney/linter/missing_feature_name.rb +2 -2
- data/lib/chutney/linter/missing_scenario_name.rb +1 -2
- data/lib/chutney/linter/missing_test_action.rb +1 -1
- data/lib/chutney/linter/missing_verification.rb +1 -1
- data/lib/chutney/linter/required_tags_starts_with.rb +5 -6
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +9 -9
- data/lib/chutney/linter/scenario_names_match.rb +2 -3
- data/lib/chutney/linter/tag_used_multiple_times.rb +1 -1
- data/lib/chutney/linter/too_clumsy.rb +1 -1
- data/lib/chutney/linter/too_long_step.rb +3 -3
- data/lib/chutney/linter/too_many_different_tags.rb +6 -6
- data/lib/chutney/linter/too_many_steps.rb +3 -3
- data/lib/chutney/linter/too_many_tags.rb +3 -3
- data/lib/chutney/linter/unique_scenario_names.rb +2 -2
- data/lib/chutney/linter/unknown_variable.rb +3 -3
- data/lib/chutney/linter/unused_variable.rb +3 -4
- data/lib/chutney/linter/use_background.rb +4 -4
- data/lib/chutney/linter/use_outline.rb +7 -7
- data/lib/chutney/version.rb +1 -1
- data/spec/chutney_spec.rb +9 -9
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a489eb526fae6a79ff386986463cf473aa4f5bc353448e99338781556f7fd194
|
4
|
+
data.tar.gz: 5cd7a3264aba055c8fe738147edfa661f57e2c0e258d5991b42adbfb2af45f05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fb8325c116764fadd900b0b98961eca7cf95f49ac171ef64e77e29f1806d6ff78bcca90ea9e2119320d33d81b13e67749fb3bf8e916df13e154871b54602b35
|
7
|
+
data.tar.gz: 8290dd430b9b55c900179717c3b3fcd83ea805e9142751dec8c69ae4c1e2233a3d5392d8951705a3fed0678b3a9fd768941889b5e9086465fa54ced8c43b1811
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
data/chutney.gemspec
CHANGED
@@ -61,7 +61,7 @@ Gem::Specification.new do |spec|
|
|
61
61
|
spec.add_development_dependency 'rake', '~> 13.0'
|
62
62
|
spec.add_development_dependency 'rerun', '~> 0.13'
|
63
63
|
spec.add_development_dependency 'rspec-expectations', '~> 3.0'
|
64
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
64
|
+
spec.add_development_dependency 'rubocop', '~> 0.90.0'
|
65
65
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
66
66
|
|
67
67
|
spec.required_ruby_version = '~> 2.6'
|
File without changes
|
data/exe/chutney
CHANGED
@@ -12,14 +12,14 @@ formatters = Set.new
|
|
12
12
|
|
13
13
|
OptionParser.new do |opts|
|
14
14
|
opts.banner = 'Usage: chutney [files]'
|
15
|
-
opts.on('-f',
|
16
|
-
'--format [formatter]',
|
15
|
+
opts.on('-f',
|
16
|
+
'--format [formatter]',
|
17
17
|
'One of JSONFormatter, PieFormatter or RainbowFormatter (default).') do |formatter|
|
18
18
|
raise 'No Such Formatter' unless %w[JSONFormatter PieFormatter RainbowFormatter].include? formatter
|
19
19
|
|
20
20
|
formatters << formatter
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
opts.on('-l',
|
24
24
|
'--linters',
|
25
25
|
'List the linter status by this configuration and exit') do
|
@@ -28,7 +28,7 @@ OptionParser.new do |opts|
|
|
28
28
|
max_name_length = chutney_config.keys.map(&:length).max + 1
|
29
29
|
chutney_config.each do |linter, value|
|
30
30
|
print pastel.cyan(linter.ljust(max_name_length))
|
31
|
-
|
31
|
+
|
32
32
|
if value['Enabled']
|
33
33
|
puts pastel.green('enabled')
|
34
34
|
else
|
@@ -42,7 +42,7 @@ end.parse!
|
|
42
42
|
formatters << 'RainbowFormatter' if formatters.empty?
|
43
43
|
|
44
44
|
files = ARGV.map { |pattern| Dir.glob(pattern) }.flatten
|
45
|
-
files = Dir.glob('features/**/*.feature') if ARGV.empty?
|
45
|
+
files = Dir.glob('features/**/*.feature') if ARGV.empty?
|
46
46
|
|
47
47
|
linter = Chutney::ChutneyLint.new(*files)
|
48
48
|
report = linter.analyse
|
data/lib/chutney.rb
CHANGED
@@ -36,6 +36,7 @@ require 'chutney/linter/unknown_variable'
|
|
36
36
|
require 'chutney/linter/unused_variable'
|
37
37
|
require 'chutney/linter/use_background'
|
38
38
|
require 'chutney/linter/use_outline'
|
39
|
+
require 'chutney/version'
|
39
40
|
|
40
41
|
require 'cuke_modeler'
|
41
42
|
require 'forwardable'
|
@@ -58,14 +59,18 @@ module Chutney
|
|
58
59
|
@files = files
|
59
60
|
@results = Hash.new { |h, k| h[k] = [] }
|
60
61
|
i18n_paths = Dir[File.expand_path(File.join(__dir__, 'config/locales')) + '/*.yml']
|
61
|
-
return if I18n.load_path.include?(i18n_paths)
|
62
62
|
|
63
|
-
|
63
|
+
i18n_paths.each do |path|
|
64
|
+
next if I18n.load_path.include?(path)
|
65
|
+
|
66
|
+
I18n.load_path << path
|
67
|
+
I18n.backend.reload!
|
68
|
+
end
|
64
69
|
end
|
65
70
|
|
66
71
|
def configuration
|
67
72
|
unless @config
|
68
|
-
default_file = [File.expand_path('..', __dir__), '**/config', '
|
73
|
+
default_file = [File.expand_path('..', __dir__), '**/config', 'chutney_defaults.yml']
|
69
74
|
config_file = Dir.glob(File.join(default_file)).first.freeze
|
70
75
|
@config = Configuration.new(config_file)
|
71
76
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'delegate'
|
3
4
|
|
4
|
-
module Chutney
|
5
|
+
module Chutney
|
5
6
|
# gherkin_lint configuration object
|
6
7
|
class Configuration < SimpleDelegator
|
7
8
|
def initialize(path)
|
@@ -23,7 +24,7 @@ module Chutney
|
|
23
24
|
config_files = ['chutney.yml', '.chutney.yml'].map do |fname|
|
24
25
|
Dir.glob(File.join(Dir.pwd, '**', fname))
|
25
26
|
end.flatten
|
26
|
-
|
27
|
+
|
27
28
|
config_file = config_files.first
|
28
29
|
merge_config(config_file) if !config_file.nil? && File.exist?(config_file)
|
29
30
|
end
|
data/lib/chutney/formatter.rb
CHANGED
@@ -4,18 +4,17 @@ module Chutney
|
|
4
4
|
# base class for all formatters
|
5
5
|
class Formatter
|
6
6
|
attr_accessor :results
|
7
|
-
|
7
|
+
|
8
8
|
def initialize
|
9
9
|
@results = {}
|
10
|
-
end
|
11
|
-
|
10
|
+
end
|
11
|
+
|
12
12
|
def files
|
13
13
|
results.map { |k, _v| k }
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def files_with_issues
|
17
17
|
results.filter { |_k, v| v.any? { |r| r[:issues].count.positive? } }
|
18
18
|
end
|
19
|
-
|
20
19
|
end
|
21
20
|
end
|
@@ -9,7 +9,7 @@ module Chutney
|
|
9
9
|
def initialize
|
10
10
|
super
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def format
|
14
14
|
data = top_offences.map do |offence|
|
15
15
|
{
|
@@ -21,14 +21,14 @@ module Chutney
|
|
21
21
|
end
|
22
22
|
print_report(data)
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def print_report(data)
|
26
26
|
return if data.empty?
|
27
27
|
|
28
28
|
print TTY::Pie.new(data: data, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
|
29
29
|
puts
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def top_offences
|
33
33
|
offence = Hash.new(0)
|
34
34
|
files_with_issues.each do |_file, linter|
|
@@ -38,9 +38,9 @@ module Chutney
|
|
38
38
|
end
|
39
39
|
offence.reject { |_k, v| v.zero? }.sort_by { |_linter, count| -count }
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def char_loop
|
43
|
-
@char_looper ||= Fiber.new do
|
43
|
+
@char_looper ||= Fiber.new do
|
44
44
|
chars = %w[• x + @ * / -]
|
45
45
|
current = 0
|
46
46
|
loop do
|
@@ -51,10 +51,10 @@ module Chutney
|
|
51
51
|
end
|
52
52
|
@char_looper.resume
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def colour_loop
|
56
|
-
@colour_looper ||= Fiber.new do
|
57
|
-
colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
|
56
|
+
@colour_looper ||= Fiber.new do
|
57
|
+
colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
|
58
58
|
current = 0
|
59
59
|
loop do
|
60
60
|
current = 0 if current >= colours.count
|
@@ -64,7 +64,7 @@ module Chutney
|
|
64
64
|
end
|
65
65
|
@colour_looper.resume
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def put_summary
|
69
69
|
pastel = Pastel.new
|
70
70
|
print "#{files.count} features inspected, "
|
@@ -74,6 +74,5 @@ module Chutney
|
|
74
74
|
puts pastel.red("#{files_with_issues.count} taste nasty")
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
78
77
|
end
|
79
78
|
end
|
@@ -5,38 +5,36 @@ require 'pastel'
|
|
5
5
|
module Chutney
|
6
6
|
# pretty formatter
|
7
7
|
class RainbowFormatter < Formatter
|
8
|
-
|
9
8
|
def initialize
|
10
9
|
super
|
11
|
-
|
10
|
+
|
12
11
|
@pastel = Pastel.new
|
13
12
|
end
|
14
|
-
|
15
|
-
def format
|
13
|
+
|
14
|
+
def format
|
16
15
|
files_with_issues.each do |file, linter|
|
17
16
|
put_file(file)
|
18
17
|
linter.filter { |l| !l[:issues].empty? }.each do |linter_with_issues|
|
19
|
-
|
20
18
|
put_linter(linter_with_issues)
|
21
19
|
linter_with_issues[:issues].each { |i| put_issue(file, i) }
|
22
20
|
end
|
23
21
|
end
|
24
22
|
put_summary
|
25
23
|
end
|
26
|
-
|
24
|
+
|
27
25
|
def put_file(file)
|
28
26
|
puts @pastel.cyan(file.to_s)
|
29
27
|
end
|
30
|
-
|
28
|
+
|
31
29
|
def put_linter(linter)
|
32
30
|
puts @pastel.red(" #{linter[:linter]}")
|
33
31
|
end
|
34
|
-
|
35
|
-
def put_issue(file, issue)
|
32
|
+
|
33
|
+
def put_issue(file, issue)
|
36
34
|
puts " #{issue[:message]}"
|
37
35
|
puts " #{@pastel.dim file.to_s}:#{@pastel.dim(issue.dig(:location, :line))}"
|
38
36
|
end
|
39
|
-
|
37
|
+
|
40
38
|
def put_summary
|
41
39
|
print "#{files.count} features inspected, "
|
42
40
|
if files_with_issues.count.zero?
|
@@ -45,6 +43,5 @@ module Chutney
|
|
45
43
|
puts @pastel.red("#{files_with_issues.count} taste nasty")
|
46
44
|
end
|
47
45
|
end
|
48
|
-
|
49
46
|
end
|
50
47
|
end
|
data/lib/chutney/linter.rb
CHANGED
@@ -7,7 +7,7 @@ module Chutney
|
|
7
7
|
class Linter
|
8
8
|
attr_accessor :issues
|
9
9
|
attr_reader :filename, :configuration
|
10
|
-
|
10
|
+
|
11
11
|
Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
|
12
12
|
|
13
13
|
def self.descendants
|
@@ -26,56 +26,56 @@ module Chutney
|
|
26
26
|
def lint
|
27
27
|
raise 'not implemented'
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def and_word?(word)
|
31
31
|
dialect_word(:and).include?(word)
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def background_word?(word)
|
35
35
|
dialect_word(:background).include?(word)
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def but_word?(word)
|
39
39
|
dialect_word(:but).include?(word)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def examples_word?(word)
|
43
43
|
dialect_word(:examples).include?(word)
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def feature_word?(word)
|
47
47
|
dialect_word(:feature).include?(word)
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def given_word?(word)
|
51
51
|
dialect_word(:given).include?(word)
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
def scenario_outline_word?(word)
|
55
55
|
dialect_word(:scenarioOutline).include?(word)
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def then_word?(word)
|
59
59
|
dialect_word(:then).include?(word)
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
def when_word?(word)
|
63
63
|
dialect_word(:when).include?(word)
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
def dialect_word(word)
|
67
67
|
CukeModeler::Parsing.dialects[dialect][word.to_s].map(&:strip)
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
def dialect
|
71
71
|
@content.feature&.parsing_data&.dig(:language) || 'en'
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def tags_for(element)
|
75
75
|
element.tags.map { |tag| tag.name[1..-1] }
|
76
76
|
end
|
77
|
-
|
78
|
-
def add_issue(message, feature = nil, scenario = nil, item = nil)
|
77
|
+
|
78
|
+
def add_issue(message, feature = nil, scenario = nil, item = nil)
|
79
79
|
issues << Lint.new(
|
80
80
|
message: message,
|
81
81
|
gherkin_type: type(feature, scenario, item),
|
@@ -85,17 +85,17 @@ module Chutney
|
|
85
85
|
step: item&.parsing_data&.dig(:name)
|
86
86
|
).to_h
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
def location(feature, scenario, step)
|
90
90
|
if step
|
91
91
|
step.parsing_data[:location]
|
92
92
|
elsif scenario
|
93
93
|
scenario.parsing_data.dig(:scenario, :location) || scenario.parsing_data.dig(:background, :location)
|
94
|
-
else
|
95
|
-
feature ? feature.parsing_data[:location] : 0
|
94
|
+
else
|
95
|
+
feature ? feature.parsing_data[:location] : { line: 0, column: 0 }
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
def type(_feature, scenario, step)
|
100
100
|
if step
|
101
101
|
:step
|
@@ -111,14 +111,14 @@ module Chutney
|
|
111
111
|
@content.feature
|
112
112
|
end
|
113
113
|
end
|
114
|
-
|
115
|
-
def elements
|
114
|
+
|
115
|
+
def elements
|
116
116
|
return [] unless feature
|
117
|
-
|
118
|
-
if block_given?
|
117
|
+
|
118
|
+
if block_given?
|
119
119
|
feature.children.each do |child|
|
120
120
|
next if off_switch?(child)
|
121
|
-
|
121
|
+
|
122
122
|
yield(feature, child)
|
123
123
|
end
|
124
124
|
else
|
@@ -136,38 +136,38 @@ module Chutney
|
|
136
136
|
off_switch ||= off_switch?(feature) unless element == feature
|
137
137
|
off_switch
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
def background
|
141
|
-
if block_given?
|
141
|
+
if block_given?
|
142
142
|
yield(feature, feature&.background)
|
143
143
|
else
|
144
144
|
feature&.background
|
145
145
|
end
|
146
146
|
end
|
147
|
-
|
147
|
+
|
148
148
|
def scenarios
|
149
149
|
if block_given?
|
150
150
|
feature&.tests&.each do |test|
|
151
151
|
yield(feature, test)
|
152
152
|
end
|
153
|
-
|
153
|
+
|
154
154
|
else
|
155
155
|
feature&.tests
|
156
156
|
end
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
def filled_scenarios
|
160
160
|
if block_given?
|
161
161
|
scenarios do |feature, scenario|
|
162
162
|
next if scenario.steps.empty?
|
163
|
-
|
163
|
+
|
164
164
|
yield(feature, scenario)
|
165
165
|
end
|
166
166
|
else
|
167
167
|
scenarios ? scenarios.filter { |s| !s.steps.empty? } : []
|
168
168
|
end
|
169
169
|
end
|
170
|
-
|
170
|
+
|
171
171
|
def steps
|
172
172
|
feature&.tests&.each do |t|
|
173
173
|
t.steps.each { |s| yield(feature, t, s) }
|
@@ -177,7 +177,7 @@ module Chutney
|
|
177
177
|
def self.linter_name
|
178
178
|
name.split('::').last
|
179
179
|
end
|
180
|
-
|
180
|
+
|
181
181
|
def linter_name
|
182
182
|
self.class.linter_name
|
183
183
|
end
|
@@ -187,10 +187,10 @@ module Chutney
|
|
187
187
|
value += render_step_argument(step.block) if step.block
|
188
188
|
value
|
189
189
|
end
|
190
|
-
|
190
|
+
|
191
191
|
def render_step_argument(argument)
|
192
192
|
return "\n#{argument.content}" if argument.is_a?(CukeModeler::DocString)
|
193
|
-
|
193
|
+
|
194
194
|
result = argument.rows.map do |row|
|
195
195
|
"|#{row.cells.map(&:value).join '|'}|"
|
196
196
|
end.join "\n"
|
@@ -2,12 +2,10 @@
|
|
2
2
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for avoiding periods
|
5
|
-
class AvoidFullStop < Linter
|
5
|
+
class AvoidFullStop < Linter
|
6
6
|
def lint
|
7
7
|
steps do |feature, child, step|
|
8
|
-
|
9
8
|
add_issue(I18n.t('linters.avoid_full_stop'), feature, child, step) if step.text.strip.end_with? '.'
|
10
|
-
|
11
9
|
end
|
12
10
|
end
|
13
11
|
end
|
@@ -4,13 +4,13 @@ module Chutney
|
|
4
4
|
# service class to lint for avoiding outline for single example
|
5
5
|
class AvoidOutlineForSingleExample < Linter
|
6
6
|
def lint
|
7
|
-
scenarios do |feature, scenario|
|
7
|
+
scenarios do |feature, scenario|
|
8
8
|
next unless scenario.is_a? CukeModeler::Outline
|
9
9
|
next unless scenario.examples
|
10
|
-
|
10
|
+
|
11
11
|
next if scenario.examples.length > 1
|
12
12
|
next if scenario.examples.first.rows.length > 2 # first row is the header
|
13
|
-
|
13
|
+
|
14
14
|
add_issue(I18n.t('linters.avoid_outline_for_single_example'), feature, scenario)
|
15
15
|
end
|
16
16
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for avoid scripting
|
5
|
-
class AvoidScripting < Linter
|
5
|
+
class AvoidScripting < Linter
|
6
6
|
def lint
|
7
7
|
scenarios do |feature, scenario|
|
8
8
|
when_steps = filter_when_steps(scenario.steps)
|
@@ -10,7 +10,7 @@ module Chutney
|
|
10
10
|
add_issue(I18n.t('linters.avoid_scripting', count: whens), feature, scenario, when_steps.last) if whens > 1
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def filter_when_steps(steps)
|
15
15
|
steps
|
16
16
|
.drop_while { |step| !when_word?(step.keyword) }
|
@@ -2,14 +2,13 @@
|
|
2
2
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for background that does more than setup
|
5
|
-
class BackgroundDoesMoreThanSetup < Linter
|
5
|
+
class BackgroundDoesMoreThanSetup < Linter
|
6
6
|
def lint
|
7
7
|
background do |feature, background|
|
8
|
-
|
9
|
-
invalid_steps = background&.steps&.select do |step|
|
8
|
+
invalid_steps = background&.steps&.select do |step|
|
10
9
|
when_word?(step.keyword) || then_word?(step.keyword)
|
11
10
|
end
|
12
|
-
|
11
|
+
|
13
12
|
next if invalid_steps.nil? || invalid_steps.empty?
|
14
13
|
|
15
14
|
add_issue(I18n.t('linters.background_does_more_than_setup'), feature, background, invalid_steps.first)
|
@@ -6,7 +6,7 @@ module Chutney
|
|
6
6
|
# service class for check that there are multiple scenarios once a background is used
|
7
7
|
class BackgroundRequiresMultipleScenarios < Linter
|
8
8
|
MESSAGE = 'Avoid using Background steps for just one scenario'
|
9
|
-
|
9
|
+
|
10
10
|
def lint
|
11
11
|
background do |feature, background|
|
12
12
|
next unless background
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Chutney
|
4
|
-
# service class to lint for bad scenario names
|
4
|
+
# service class to lint for bad scenario names
|
5
5
|
class BadScenarioName < Linter
|
6
6
|
def lint
|
7
7
|
scenarios do |feature, scenario|
|
8
8
|
next if scenario.name.empty?
|
9
|
-
|
9
|
+
|
10
10
|
bad = /\w*(test|verif|check)\w*/i
|
11
11
|
match = scenario.name.match(bad).to_a.first
|
12
12
|
add_issue(I18n.t('linters.bad_scenario_name', word: match), feature, scenario) if match
|
@@ -5,11 +5,11 @@ module Chutney
|
|
5
5
|
class FileNameDiffersFeatureName < Linter
|
6
6
|
def lint
|
7
7
|
return unless feature
|
8
|
-
|
8
|
+
|
9
9
|
expected_feature_name = title_case(filename)
|
10
10
|
return if ignore_whitespaces(feature.name).casecmp(ignore_whitespaces(expected_feature_name)) == 0
|
11
|
-
|
12
|
-
add_issue(I18n.t('linters.file_name_differs_feature_name', expected: expected_feature_name), feature)
|
11
|
+
|
12
|
+
add_issue(I18n.t('linters.file_name_differs_feature_name', expected: expected_feature_name), feature)
|
13
13
|
end
|
14
14
|
|
15
15
|
def title_case(value)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Chutney
|
4
|
-
# service class to lint for bad scenario names
|
4
|
+
# service class to lint for bad scenario names
|
5
5
|
class GivensAfterBackground < Linter
|
6
6
|
def lint
|
7
7
|
return unless background
|
8
|
-
|
8
|
+
|
9
9
|
filled_scenarios do |feature, scenario|
|
10
10
|
scenario.steps.each do |step|
|
11
11
|
add_issue(I18n.t('linters.givens_after_background'), feature, scenario, step) if given_word?(step.keyword)
|
@@ -7,7 +7,7 @@ module Chutney
|
|
7
7
|
filled_scenarios do |feature, scenario|
|
8
8
|
steps = scenario.steps.select { |step| !and_word?(step.keyword) && !but_word?(step.keyword) }
|
9
9
|
next if steps.empty?
|
10
|
-
|
10
|
+
|
11
11
|
last_step_is_an_action(feature, scenario, steps)
|
12
12
|
given_after_non_given(feature, scenario, steps)
|
13
13
|
verification_before_action(feature, scenario, steps)
|
@@ -33,7 +33,7 @@ module Chutney
|
|
33
33
|
def verification_before_action(feature, scenario, steps)
|
34
34
|
steps.each do |step|
|
35
35
|
break if when_word?(step.keyword)
|
36
|
-
|
36
|
+
|
37
37
|
add_issue(I18n.t('linters.invalid_step_flow.missing_action'), feature, scenario) if then_word?(step.keyword)
|
38
38
|
end
|
39
39
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for missing example names
|
5
5
|
class MissingExampleName < Linter
|
6
|
-
|
7
6
|
def lint
|
8
7
|
scenarios do |_feature, scenario|
|
9
8
|
next unless scenario.is_a? CukeModeler::Outline
|
@@ -11,17 +10,16 @@ module Chutney
|
|
11
10
|
scenario.examples.each do |example|
|
12
11
|
example_count = scenario.examples&.length || 0
|
13
12
|
next unless example_count > 1
|
14
|
-
|
13
|
+
|
15
14
|
check_example(scenario, example)
|
16
15
|
end
|
17
16
|
end
|
18
|
-
end
|
17
|
+
end
|
19
18
|
|
20
19
|
def check_example(scenario, example)
|
21
20
|
name = example.name.strip
|
22
21
|
duplicate_name_count = scenario.examples.filter { |e| e.name == name }.count
|
23
22
|
add_issue(I18n.t('linters.missing_example_name'), feature, scenario, example) if duplicate_name_count >= 2
|
24
23
|
end
|
25
|
-
|
26
24
|
end
|
27
25
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for missing feature names
|
5
|
-
class MissingFeatureName < Linter
|
5
|
+
class MissingFeatureName < Linter
|
6
6
|
def lint
|
7
7
|
return unless feature
|
8
|
-
|
8
|
+
|
9
9
|
add_issue(I18n.t('linters.missing_feature_name'), feature) if feature.name.empty?
|
10
10
|
end
|
11
11
|
end
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for missing scenario names
|
5
|
-
class MissingScenarioName < Linter
|
6
|
-
|
5
|
+
class MissingScenarioName < Linter
|
7
6
|
def lint
|
8
7
|
scenarios do |feature, scenario|
|
9
8
|
add_issue(I18n.t('linters.missing_scenario_name'), feature, scenario) if scenario.name.empty?
|
@@ -3,26 +3,25 @@
|
|
3
3
|
module Chutney
|
4
4
|
# service class to lint for tags used multiple times
|
5
5
|
class RequiredTagsStartsWith < Linter
|
6
|
-
|
7
6
|
def lint
|
8
7
|
return unless pattern
|
9
8
|
|
10
9
|
scenarios do |feature, scenario|
|
11
10
|
next if match_pattern? tags_for(feature)
|
12
11
|
next if match_pattern? tags_for(scenario)
|
13
|
-
|
12
|
+
|
14
13
|
add_issue(
|
15
|
-
I18n.t('linters.required_tags_starts_with',
|
16
|
-
allowed: pattern.join(', ')),
|
14
|
+
I18n.t('linters.required_tags_starts_with',
|
15
|
+
allowed: pattern.join(', ')),
|
17
16
|
feature, scenario
|
18
17
|
)
|
19
18
|
end
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
def pattern
|
23
22
|
configuration['Matcher'] || nil
|
24
23
|
end
|
25
|
-
|
24
|
+
|
26
25
|
def match_pattern?(target)
|
27
26
|
target.each do |t|
|
28
27
|
return true if t.start_with?(*pattern)
|
@@ -14,13 +14,13 @@ module Chutney
|
|
14
14
|
tags = scenario_tags
|
15
15
|
return if tags.nil? || tags.empty?
|
16
16
|
return unless feature.tests.length > 1
|
17
|
-
|
17
|
+
|
18
18
|
tags.each do |tag|
|
19
19
|
next if tag == 'skip'
|
20
20
|
|
21
21
|
add_issue(
|
22
|
-
I18n.t('linters.same_tag_for_all_scenarios.feature_level',
|
23
|
-
tag: tag),
|
22
|
+
I18n.t('linters.same_tag_for_all_scenarios.feature_level',
|
23
|
+
tag: tag),
|
24
24
|
feature
|
25
25
|
)
|
26
26
|
end
|
@@ -32,12 +32,12 @@ module Chutney
|
|
32
32
|
next if tags.nil? || tags.empty?
|
33
33
|
next unless scenario.is_a? CukeModeler::Outline
|
34
34
|
next unless scenario.examples.length > 1
|
35
|
-
|
35
|
+
|
36
36
|
tags.each do |tag|
|
37
37
|
next if tag == 'skip'
|
38
38
|
|
39
|
-
add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
|
40
|
-
tag: tag), feature, scenario)
|
39
|
+
add_issue(I18n.t('linters.same_tag_for_all_scenarios.example_level',
|
40
|
+
tag: tag), feature, scenario)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -55,13 +55,13 @@ module Chutney
|
|
55
55
|
def example_tags(scenario)
|
56
56
|
result = nil
|
57
57
|
return result unless scenario.is_a?(CukeModeler::Outline) && scenario.examples
|
58
|
-
|
58
|
+
|
59
59
|
scenario.examples.each do |example|
|
60
60
|
return nil unless example.tags
|
61
|
-
|
61
|
+
|
62
62
|
tags = tags_for(example)
|
63
63
|
result = tags if result.nil?
|
64
|
-
|
64
|
+
|
65
65
|
result &= tags
|
66
66
|
end
|
67
67
|
result
|
@@ -6,14 +6,13 @@ module Chutney
|
|
6
6
|
# service class to lint for tags used multiple times
|
7
7
|
class ScenarioNamesMatch < Linter
|
8
8
|
MESSAGE = 'Scenario Name does not match pattern'
|
9
|
-
|
10
9
|
|
11
10
|
def lint
|
12
11
|
scenarios do |feature, scenario|
|
13
12
|
next unless (scenario.name =~ /#{configuration['Matcher']}/).nil?
|
14
|
-
|
13
|
+
|
15
14
|
add_issue(
|
16
|
-
I18n.t('linters.scenario_names_match',
|
15
|
+
I18n.t('linters.scenario_names_match',
|
17
16
|
pattern: configuration['Matcher']), feature, scenario
|
18
17
|
)
|
19
18
|
end
|
@@ -8,7 +8,7 @@ module Chutney
|
|
8
8
|
total_tags = tags_for(feature) + tags_for(scenario)
|
9
9
|
double_used_tags = total_tags.find_all { |a| total_tags.count(a) > 1 }.uniq!
|
10
10
|
next if double_used_tags.nil?
|
11
|
-
|
11
|
+
|
12
12
|
add_issue(
|
13
13
|
I18n.t('linters.tag_used_multiple_times', tags: double_used_tags.join(',')), feature
|
14
14
|
)
|
@@ -6,14 +6,14 @@ module Chutney
|
|
6
6
|
def lint
|
7
7
|
steps do |feature, scenario, step|
|
8
8
|
next if step.text.length <= maxlength
|
9
|
-
|
9
|
+
|
10
10
|
add_issue(
|
11
|
-
I18n.t('linters.too_long_step', length: step.text.length, max: maxlength),
|
11
|
+
I18n.t('linters.too_long_step', length: step.text.length, max: maxlength),
|
12
12
|
feature, scenario
|
13
13
|
)
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def maxlength
|
18
18
|
configuration['MaxLength'] || '120'
|
19
19
|
end
|
@@ -6,20 +6,20 @@ module Chutney
|
|
6
6
|
def lint
|
7
7
|
tags = all_tags
|
8
8
|
return if tags.length <= maxcount
|
9
|
-
|
9
|
+
|
10
10
|
add_issue(
|
11
|
-
I18n.t('linters.too_many_different_tags', count: tags.length, max: maxcount),
|
11
|
+
I18n.t('linters.too_many_different_tags', count: tags.length, max: maxcount),
|
12
12
|
feature
|
13
13
|
)
|
14
14
|
end
|
15
|
-
|
16
|
-
def maxcount
|
15
|
+
|
16
|
+
def maxcount
|
17
17
|
configuration['MaxCount']&.to_i || 3
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def all_tags
|
21
21
|
return [] unless feature&.scenarios
|
22
|
-
|
22
|
+
|
23
23
|
tags_for(feature) + feature.scenarios.map { |scenario| tags_for(scenario) }.flatten
|
24
24
|
end
|
25
25
|
end
|
@@ -6,14 +6,14 @@ module Chutney
|
|
6
6
|
def lint
|
7
7
|
filled_scenarios do |feature, scenario|
|
8
8
|
next if scenario.steps.length <= maxcount
|
9
|
-
|
9
|
+
|
10
10
|
add_issue(
|
11
|
-
I18n.t('linters.too_many_steps', count: scenario.steps.length, max: maxcount),
|
11
|
+
I18n.t('linters.too_many_steps', count: scenario.steps.length, max: maxcount),
|
12
12
|
feature
|
13
13
|
)
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def maxcount
|
18
18
|
configuration['MaxCount']&.to_i || 10
|
19
19
|
end
|
@@ -7,14 +7,14 @@ module Chutney
|
|
7
7
|
scenarios do |feature, scenario|
|
8
8
|
tags = tags_for(feature) + tags_for(scenario)
|
9
9
|
next unless tags.length > maxcount
|
10
|
-
|
10
|
+
|
11
11
|
add_issue(
|
12
|
-
I18n.t('linters.too_many_tags', count: tags.length, max: maxcount),
|
12
|
+
I18n.t('linters.too_many_tags', count: tags.length, max: maxcount),
|
13
13
|
feature
|
14
14
|
)
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def maxcount
|
19
19
|
configuration['MaxCount']&.to_i || 3
|
20
20
|
end
|
@@ -14,13 +14,13 @@ module Chutney
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def issue(name, first_location, scenario)
|
19
19
|
add_issue(
|
20
20
|
I18n.t('linters.unique_scenario_names',
|
21
21
|
name: name,
|
22
22
|
line: first_location[:line],
|
23
|
-
column: first_location[:column]),
|
23
|
+
column: first_location[:column]),
|
24
24
|
feature, scenario
|
25
25
|
)
|
26
26
|
end
|
@@ -9,7 +9,7 @@ module Chutney
|
|
9
9
|
scenario.steps.each do |step|
|
10
10
|
step_vars(step).each do |used_var|
|
11
11
|
next if known_vars.include? used_var
|
12
|
-
|
12
|
+
|
13
13
|
add_issue(
|
14
14
|
I18n.t('linters.unknown_variable', variable: used_var), feature, scenario
|
15
15
|
)
|
@@ -21,7 +21,7 @@ module Chutney
|
|
21
21
|
def step_vars(step)
|
22
22
|
vars = gather_vars step.text
|
23
23
|
return vars unless step.block
|
24
|
-
|
24
|
+
|
25
25
|
vars + gather_vars_from_argument(step.block)
|
26
26
|
end
|
27
27
|
|
@@ -39,7 +39,7 @@ module Chutney
|
|
39
39
|
|
40
40
|
def known_variables(scenario)
|
41
41
|
return [] unless scenario.is_a? CukeModeler::Outline
|
42
|
-
|
42
|
+
|
43
43
|
scenario.examples.map { |ex| ex.rows.first.cells.map(&:value) }.flatten
|
44
44
|
end
|
45
45
|
end
|
@@ -6,9 +6,8 @@ module Chutney
|
|
6
6
|
def lint
|
7
7
|
scenarios do |feature, scenario|
|
8
8
|
next unless scenario.is_a? CukeModeler::Outline
|
9
|
-
|
9
|
+
|
10
10
|
scenario.examples.each do |example|
|
11
|
-
|
12
11
|
example.rows.first.cells.map(&:value).each do |variable|
|
13
12
|
next if used?(variable, scenario)
|
14
13
|
|
@@ -20,7 +19,7 @@ module Chutney
|
|
20
19
|
|
21
20
|
def used?(variable, scenario)
|
22
21
|
variable = "<#{variable}>"
|
23
|
-
|
22
|
+
|
24
23
|
scenario.steps.each do |step|
|
25
24
|
return true if step.text.include? variable
|
26
25
|
next unless step.block
|
@@ -36,7 +35,7 @@ module Chutney
|
|
36
35
|
|
37
36
|
def used_in_table?(variable, step)
|
38
37
|
return false unless step.block.is_a?(CukeModeler::Table)
|
39
|
-
|
38
|
+
|
40
39
|
step.block.rows.each do |row|
|
41
40
|
row.cells.each { |cell| return true if cell.value.include?(variable) }
|
42
41
|
end
|
@@ -16,11 +16,11 @@ module Chutney
|
|
16
16
|
|
17
17
|
def gather_givens
|
18
18
|
return unless feature.children
|
19
|
-
|
19
|
+
|
20
20
|
has_non_given_step = false
|
21
21
|
scenarios do |_feature, scenario|
|
22
22
|
next unless scenario.steps
|
23
|
-
|
23
|
+
|
24
24
|
has_non_given_step = true unless given_word?(scenario.steps.first.keyword)
|
25
25
|
end
|
26
26
|
return if has_non_given_step
|
@@ -33,7 +33,7 @@ module Chutney
|
|
33
33
|
def expanded_steps(&block)
|
34
34
|
scenarios do |_feature, scenario|
|
35
35
|
next unless scenario.steps
|
36
|
-
|
36
|
+
|
37
37
|
prototypes = [render_step(scenario.steps.first)]
|
38
38
|
prototypes = expand_examples(scenario.examples, prototypes) if scenario.is_a? CukeModeler::Outline
|
39
39
|
prototypes.each(&block)
|
@@ -52,7 +52,7 @@ module Chutney
|
|
52
52
|
headers = example.rows.first.cells.map(&:value)
|
53
53
|
example.rows.each_with_index do |row, idx|
|
54
54
|
next if idx.zero? # skip the header
|
55
|
-
|
55
|
+
|
56
56
|
modified_sentence = sentence.dup
|
57
57
|
headers.zip(row.cells.map(&:value)).map do |key, value|
|
58
58
|
modified_sentence.gsub!("<#{key}>", value)
|
@@ -11,18 +11,18 @@ module Chutney
|
|
11
11
|
scenarios.product(scenarios) do |lhs, rhs|
|
12
12
|
next if lhs == rhs
|
13
13
|
next if lhs[:reference][:line] > rhs[:reference][:line]
|
14
|
-
|
14
|
+
|
15
15
|
similarity = determine_similarity(lhs[:text], rhs[:text])
|
16
16
|
next unless similarity >= 0.95
|
17
|
-
|
17
|
+
|
18
18
|
similarity_pct = similarity.round(3) * 100
|
19
|
-
|
19
|
+
|
20
20
|
add_issue(lhs, rhs, similarity_pct)
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def add_issue(lhs, rhs, pct)
|
25
|
-
super(I18n.t('linters.use_outline',
|
25
|
+
super(I18n.t('linters.use_outline',
|
26
26
|
pct: pct,
|
27
27
|
lhs_name: lhs[:name],
|
28
28
|
lhs_line: lhs[:reference][:line],
|
@@ -39,11 +39,11 @@ module Chutney
|
|
39
39
|
def gather_scenarios(feature)
|
40
40
|
scenarios = []
|
41
41
|
return scenarios if feature.nil? || !feature.tests
|
42
|
-
|
42
|
+
|
43
43
|
scenarios do |_feature, scenario|
|
44
44
|
next unless scenario.steps
|
45
45
|
next if scenario.steps.empty?
|
46
|
-
|
46
|
+
|
47
47
|
scenarios.push generate_reference(feature, scenario)
|
48
48
|
end
|
49
49
|
scenarios
|
data/lib/chutney/version.rb
CHANGED
data/spec/chutney_spec.rb
CHANGED
@@ -8,25 +8,25 @@ describe Chutney::ChutneyLint do
|
|
8
8
|
it 'has a version number' do
|
9
9
|
expect(Chutney::VERSION).not_to be nil
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
describe '#initialize' do
|
13
13
|
it 'creates an instance' do
|
14
14
|
expect(subject).not_to be_nil
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
it 'has an empty list of files if none are given' do
|
18
18
|
expect(subject.files).to eq []
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
it 'has a list of files given on initialization' do
|
22
22
|
alt_subject = Chutney::ChutneyLint.new('a', 'b')
|
23
23
|
expect(alt_subject.files).to eq %w[a b]
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'initializes a results hash' do
|
27
27
|
expect(subject.results).to eq({})
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
it 'sets the load path for I18n' do
|
31
31
|
expect(I18n.load_path).not_to eq []
|
32
32
|
end
|
@@ -36,25 +36,25 @@ describe Chutney::ChutneyLint do
|
|
36
36
|
expect(subject.configuration).not_to be_nil
|
37
37
|
expect(subject.configuration).to respond_to :[]
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
it 'allows the configuration to be set explicitly' do
|
41
41
|
config = { 'BackgroundDoesMoreThanSetup' => { 'Enabled' => true } }
|
42
42
|
subject.configuration = config
|
43
43
|
expect(subject.configuration).to be config
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it 'controls the available linters' do
|
47
47
|
subject.configuration = {}
|
48
48
|
expect(subject.linters).to be_empty
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
it 'enables linters to be activated' do
|
52
52
|
config = { 'BackgroundDoesMoreThanSetup' => { 'Enabled' => true } }
|
53
53
|
subject.configuration = config
|
54
54
|
expect(subject.linters).to eq [Chutney::BackgroundDoesMoreThanSetup]
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
context 'linting' do
|
59
59
|
it 'aliases analyse and analyze' do
|
60
60
|
expect(subject).to respond_to :analyse
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chutney
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nigel Brookes-Thomas
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-09-
|
14
|
+
date: 2020-09-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: amatch
|
@@ -173,14 +173,14 @@ dependencies:
|
|
173
173
|
requirements:
|
174
174
|
- - "~>"
|
175
175
|
- !ruby/object:Gem::Version
|
176
|
-
version: 0.
|
176
|
+
version: 0.90.0
|
177
177
|
type: :development
|
178
178
|
prerelease: false
|
179
179
|
version_requirements: !ruby/object:Gem::Requirement
|
180
180
|
requirements:
|
181
181
|
- - "~>"
|
182
182
|
- !ruby/object:Gem::Version
|
183
|
-
version: 0.
|
183
|
+
version: 0.90.0
|
184
184
|
- !ruby/object:Gem::Dependency
|
185
185
|
name: rspec
|
186
186
|
requirement: !ruby/object:Gem::Requirement
|
@@ -217,7 +217,7 @@ files:
|
|
217
217
|
- README.md
|
218
218
|
- Rakefile
|
219
219
|
- chutney.gemspec
|
220
|
-
- config/
|
220
|
+
- config/chutney_defaults.yml
|
221
221
|
- config/cucumber.yml
|
222
222
|
- docs/.keep
|
223
223
|
- docs/_config.yml
|
@@ -297,9 +297,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
297
297
|
version: '2.6'
|
298
298
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
299
299
|
requirements:
|
300
|
-
- - "
|
300
|
+
- - ">="
|
301
301
|
- !ruby/object:Gem::Version
|
302
|
-
version:
|
302
|
+
version: '0'
|
303
303
|
requirements: []
|
304
304
|
rubygems_version: 3.1.2
|
305
305
|
signing_key:
|