goodcheck 2.7.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/LICENSE +1 -1
- data/README.md +7 -1
- data/lib/goodcheck.rb +5 -0
- data/lib/goodcheck/cli.rb +4 -6
- data/lib/goodcheck/commands/check.rb +1 -3
- data/lib/goodcheck/commands/config_loading.rb +9 -4
- data/lib/goodcheck/commands/test.rb +33 -28
- data/lib/goodcheck/config.rb +68 -1
- data/lib/goodcheck/config_loader.rb +28 -15
- data/lib/goodcheck/exit_status.rb +2 -0
- data/lib/goodcheck/import_loader.rb +39 -11
- data/lib/goodcheck/reporters/json.rb +2 -1
- data/lib/goodcheck/reporters/text.rb +3 -1
- data/lib/goodcheck/rule.rb +3 -1
- data/lib/goodcheck/unarchiver.rb +40 -0
- data/lib/goodcheck/version.rb +1 -1
- metadata +31 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa64d7f536fc7b357fbae7aa685827211236015ff9203bc7291847705e4a8189
|
4
|
+
data.tar.gz: bf23ca5741b3e424d064d45b9e16ccc28fbf53d299d8a4966ce7a5566c277a0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d184c5e83feb1b656afdbb2aa71c724b33c584a674de2def35201bbd8a66275b5c2a45b466c477aee38689f53dc6e39a0ee4183a6ad61b3ba587485eeb7f3d89
|
7
|
+
data.tar.gz: 215b0532befa04bdbbdbb0ac24be805fde46ebe51bafda8d6193df1c3a183fbf0faad0495454b9dcfe6dec27015f817b5b52f99b4fa26936f57ac491fa923bf5
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,27 @@
|
|
2
2
|
|
3
3
|
## HEAD
|
4
4
|
|
5
|
+
## 3.0.0 (2021-06-14)
|
6
|
+
|
7
|
+
Breaking changes:
|
8
|
+
|
9
|
+
* Drop support of Ruby 2.4 [#169](https://github.com/sider/goodcheck/pull/169)
|
10
|
+
* `goodcheck test` exit with `3` on tests failed [#168](https://github.com/sider/goodcheck/pull/168)
|
11
|
+
(a potentially breaking change; the command previously exited with `1`)
|
12
|
+
|
13
|
+
Features:
|
14
|
+
|
15
|
+
* Import `.tar.gz` files [#190](https://github.com/sider/goodcheck/pull/190)
|
16
|
+
* Introduce `exclude_binary` option [#195](https://github.com/sider/goodcheck/pull/195)
|
17
|
+
* Add rule severity [#172](https://github.com/sider/goodcheck/pull/172)
|
18
|
+
|
19
|
+
Bugfixes and others:
|
20
|
+
|
21
|
+
* Update gemspec [#176](https://github.com/sider/goodcheck/pull/176)
|
22
|
+
* Update `psych` requirement allowing 4.0 [#183](https://github.com/sider/goodcheck/pull/183)
|
23
|
+
* Improve output error message on `RegexpError` [#188](https://github.com/sider/goodcheck/pull/188)
|
24
|
+
* Fix importing error with extended glob [#194](https://github.com/sider/goodcheck/pull/194)
|
25
|
+
|
5
26
|
## 2.7.0 (2020-12-02)
|
6
27
|
|
7
28
|
* Goodbye ActiveSupport [#155](https://github.com/sider/goodcheck/pull/155)
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -21,7 +21,13 @@ Goodcheck is provided as a Ruby gem. To install it, run:
|
|
21
21
|
$ gem install goodcheck
|
22
22
|
```
|
23
23
|
|
24
|
-
|
24
|
+
If you do not want to install it, you can run it via Docker instead:
|
25
|
+
|
26
|
+
```console
|
27
|
+
$ docker run -t --rm -v "$(pwd):/work" sider/goodcheck
|
28
|
+
```
|
29
|
+
|
30
|
+
Check out the [documentation](docusaurus/docs/getstarted.md) or [website](https://sider.github.io/goodcheck/) for more details.
|
25
31
|
|
26
32
|
## Development
|
27
33
|
|
data/lib/goodcheck.rb
CHANGED
data/lib/goodcheck/cli.rb
CHANGED
@@ -21,8 +21,6 @@ module Goodcheck
|
|
21
21
|
help: "Show help and quit"
|
22
22
|
}.freeze
|
23
23
|
|
24
|
-
DEFAULT_CONFIG_FILE = Pathname("goodcheck.yml").freeze
|
25
|
-
|
26
24
|
def run(args)
|
27
25
|
command = args.shift&.to_sym
|
28
26
|
|
@@ -58,7 +56,7 @@ module Goodcheck
|
|
58
56
|
end
|
59
57
|
|
60
58
|
def check(args)
|
61
|
-
config_path = DEFAULT_CONFIG_FILE
|
59
|
+
config_path = Pathname(DEFAULT_CONFIG_FILE)
|
62
60
|
targets = []
|
63
61
|
rules = []
|
64
62
|
formats = [:text, :json]
|
@@ -110,7 +108,7 @@ module Goodcheck
|
|
110
108
|
end
|
111
109
|
|
112
110
|
def test(args)
|
113
|
-
config_path = DEFAULT_CONFIG_FILE
|
111
|
+
config_path = Pathname(DEFAULT_CONFIG_FILE)
|
114
112
|
loglevel = ::Logger::ERROR
|
115
113
|
force_download = false
|
116
114
|
|
@@ -132,7 +130,7 @@ module Goodcheck
|
|
132
130
|
end
|
133
131
|
|
134
132
|
def init(args)
|
135
|
-
config_path = DEFAULT_CONFIG_FILE
|
133
|
+
config_path = Pathname(DEFAULT_CONFIG_FILE)
|
136
134
|
force = false
|
137
135
|
|
138
136
|
OptionParser.new("Usage: goodcheck init [options]") do |opts|
|
@@ -163,7 +161,7 @@ module Goodcheck
|
|
163
161
|
end
|
164
162
|
|
165
163
|
def pattern(args)
|
166
|
-
config_path = DEFAULT_CONFIG_FILE
|
164
|
+
config_path = Pathname(DEFAULT_CONFIG_FILE)
|
167
165
|
|
168
166
|
OptionParser.new do |opts|
|
169
167
|
opts.banner = "Usage: goodcheck pattern [options] ids..."
|
@@ -15,8 +15,6 @@ module Goodcheck
|
|
15
15
|
include HomePath
|
16
16
|
include ExitStatus
|
17
17
|
|
18
|
-
EXIT_MATCH = 2
|
19
|
-
|
20
18
|
def initialize(config_path:, rules:, targets:, reporter:, stderr:, home_path:, force_download:)
|
21
19
|
@config_path = config_path
|
22
20
|
@rules = rules
|
@@ -129,7 +127,7 @@ module Goodcheck
|
|
129
127
|
end
|
130
128
|
|
131
129
|
def excluded?(path)
|
132
|
-
config.
|
130
|
+
config.exclude_path?(path)
|
133
131
|
end
|
134
132
|
end
|
135
133
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
module Commands
|
3
3
|
module ConfigLoading
|
4
|
+
include ExitStatus
|
5
|
+
|
4
6
|
class ConfigFileNotFound < Error
|
5
7
|
attr_reader :path
|
6
8
|
|
@@ -31,20 +33,23 @@ module Goodcheck
|
|
31
33
|
yield
|
32
34
|
rescue ConfigFileNotFound => exn
|
33
35
|
stderr.puts "Configuration file not found: #{exn.path}"
|
34
|
-
|
36
|
+
EXIT_ERROR
|
37
|
+
rescue ConfigLoader::InvalidPattern => exn
|
38
|
+
stderr.puts exn.message
|
39
|
+
EXIT_ERROR
|
35
40
|
rescue Psych::Exception => exn
|
36
41
|
stderr.puts "Unexpected error happens while loading YAML file: #{exn.inspect}"
|
37
42
|
exn.backtrace.each do |trace_loc|
|
38
43
|
stderr.puts " #{trace_loc}"
|
39
44
|
end
|
40
|
-
|
45
|
+
EXIT_ERROR
|
41
46
|
rescue StrongJSON::Type::TypeError, StrongJSON::Type::UnexpectedAttributeError => exn
|
42
47
|
stderr.puts "Invalid config: #{exn.message}"
|
43
48
|
stderr.puts StrongJSON::ErrorReporter.new(path: exn.path).to_s
|
44
|
-
|
49
|
+
EXIT_ERROR
|
45
50
|
rescue Errno::ENOENT => exn
|
46
51
|
stderr.puts "#{exn}"
|
47
|
-
|
52
|
+
EXIT_ERROR
|
48
53
|
end
|
49
54
|
end
|
50
55
|
end
|
@@ -28,15 +28,15 @@ module Goodcheck
|
|
28
28
|
return EXIT_SUCCESS
|
29
29
|
end
|
30
30
|
|
31
|
-
validate_rule_uniqueness or return
|
32
|
-
validate_rules or return
|
31
|
+
validate_rule_uniqueness or return EXIT_TEST_FAILED
|
32
|
+
validate_rules or return EXIT_TEST_FAILED
|
33
33
|
|
34
34
|
EXIT_SUCCESS
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def validate_rule_uniqueness
|
39
|
-
stdout.puts "Validating rule
|
39
|
+
stdout.puts "Validating rule ID uniqueness..."
|
40
40
|
|
41
41
|
duplicated_ids = []
|
42
42
|
|
@@ -47,31 +47,29 @@ module Goodcheck
|
|
47
47
|
end
|
48
48
|
|
49
49
|
if duplicated_ids.empty?
|
50
|
-
stdout.puts " OK
|
50
|
+
stdout.puts Rainbow(" OK! 👍").green
|
51
51
|
true
|
52
52
|
else
|
53
53
|
count = duplicated_ids.size
|
54
54
|
duplication = count == 1 ? 'duplication' : 'duplications'
|
55
|
-
stdout.puts
|
55
|
+
stdout.puts " Found #{Rainbow(count).bold} #{duplication}. 😱"
|
56
56
|
duplicated_ids.each do |id|
|
57
|
-
stdout.puts " #{id}"
|
57
|
+
stdout.puts " - #{Rainbow(id).background(:red)}"
|
58
58
|
end
|
59
59
|
false
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
def validate_rules
|
64
|
-
test_pass = true
|
65
64
|
success_count = 0
|
66
|
-
failure_count = 0
|
67
65
|
failed_rule_ids = Set[]
|
68
66
|
|
69
67
|
config.rules.each do |rule|
|
70
|
-
|
71
|
-
stdout.puts "Testing rule #{Rainbow(rule.id).cyan}..."
|
68
|
+
stdout.puts "Testing rule #{Rainbow(rule.id).cyan}..."
|
72
69
|
|
73
|
-
|
70
|
+
rule_ok = true
|
74
71
|
|
72
|
+
if rule.triggers.any? {|trigger| !trigger.passes.empty? || !trigger.fails.empty?}
|
75
73
|
rule.triggers.each.with_index do |trigger, index|
|
76
74
|
if !trigger.passes.empty? || !trigger.fails.empty?
|
77
75
|
if trigger.by_pattern?
|
@@ -89,21 +87,19 @@ module Goodcheck
|
|
89
87
|
end
|
90
88
|
|
91
89
|
unless pass_errors.empty?
|
92
|
-
test_pass = false
|
93
90
|
rule_ok = false
|
94
91
|
|
95
92
|
pass_errors.each do |_, index|
|
96
|
-
stdout.puts " #{index + 1}. #{Rainbow('pass').green} example matched
|
93
|
+
stdout.puts " #{index + 1}. #{Rainbow('pass').green} example matched. 😱"
|
97
94
|
failed_rule_ids << rule.id
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
98
|
unless fail_errors.empty?
|
102
|
-
test_pass = false
|
103
99
|
rule_ok = false
|
104
100
|
|
105
101
|
fail_errors.each do |_, index|
|
106
|
-
stdout.puts " #{index + 1}. #{Rainbow('fail').red} example didn
|
102
|
+
stdout.puts " #{index + 1}. #{Rainbow('fail').red} example didn’t match. 😱"
|
107
103
|
failed_rule_ids << rule.id
|
108
104
|
end
|
109
105
|
end
|
@@ -111,16 +107,27 @@ module Goodcheck
|
|
111
107
|
end
|
112
108
|
|
113
109
|
if rule.triggers.any?(&:skips_fail_examples?)
|
114
|
-
stdout.puts "
|
110
|
+
stdout.puts " The rule contains a `pattern` with `glob`, which is not supported by the test command. 🚨"
|
115
111
|
stdout.puts " Skips testing `fail` examples."
|
116
112
|
end
|
113
|
+
end
|
117
114
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
115
|
+
if rule.severity && !config.severity_allowed?(rule.severity)
|
116
|
+
allowed_severities = config.allowed_severities.map { |s| %("#{s}") }.join(', ')
|
117
|
+
stdout.puts Rainbow(" \"#{rule.severity}\" severity isn’t allowed. Must be one of #{allowed_severities}. 😱").red
|
118
|
+
rule_ok = false
|
119
|
+
failed_rule_ids << rule.id
|
120
|
+
end
|
121
|
+
|
122
|
+
if !rule.severity && config.severity_required?
|
123
|
+
stdout.puts Rainbow(" Severity is required. 😱").red
|
124
|
+
rule_ok = false
|
125
|
+
failed_rule_ids << rule.id
|
126
|
+
end
|
127
|
+
|
128
|
+
if rule_ok
|
129
|
+
stdout.puts Rainbow(" OK! 👍").green
|
130
|
+
success_count += 1
|
124
131
|
end
|
125
132
|
end
|
126
133
|
|
@@ -132,14 +139,12 @@ module Goodcheck
|
|
132
139
|
end
|
133
140
|
end
|
134
141
|
|
135
|
-
|
136
|
-
rule = rule_count == 1 ? "1 rule" : "#{rule_count} rules"
|
137
|
-
success = Rainbow(success_count == 1 ? "1 success" : "#{success_count} successes").green
|
138
|
-
failure = Rainbow(failure_count == 1 ? "1 failure" : "#{failure_count} failures").red
|
142
|
+
total = success_count + failed_rule_ids.size
|
139
143
|
stdout.puts ""
|
140
|
-
stdout.puts "
|
144
|
+
stdout.puts "#{Rainbow(total).bold} #{total == 1 ? 'rule' : 'rules'} tested: " \
|
145
|
+
"#{Rainbow(success_count.to_s + ' successful').green.bold}, #{Rainbow(failed_rule_ids.size.to_s + ' failed').red.bold}"
|
141
146
|
|
142
|
-
|
147
|
+
failed_rule_ids.empty?
|
143
148
|
end
|
144
149
|
|
145
150
|
def rule_matches_example?(rule, trigger, example)
|
data/lib/goodcheck/config.rb
CHANGED
@@ -1,11 +1,43 @@
|
|
1
1
|
module Goodcheck
|
2
2
|
class Config
|
3
|
+
DEFAULT_EXCLUDE_BINARY = false
|
4
|
+
|
5
|
+
# https://www.iana.org/assignments/media-types/media-types.xhtml
|
6
|
+
BINARY_MIME_TYPES = %w[
|
7
|
+
audio
|
8
|
+
font
|
9
|
+
image
|
10
|
+
model
|
11
|
+
multipart
|
12
|
+
video
|
13
|
+
].to_set.freeze
|
14
|
+
BINARY_MIME_FULLTYPES = %w[
|
15
|
+
application/gzip
|
16
|
+
application/illustrator
|
17
|
+
application/pdf
|
18
|
+
application/zip
|
19
|
+
].to_set.freeze
|
20
|
+
|
3
21
|
attr_reader :rules
|
4
22
|
attr_reader :exclude_paths
|
23
|
+
attr_reader :exclude_binary
|
24
|
+
alias exclude_binary? exclude_binary
|
25
|
+
attr_reader :allowed_severities
|
26
|
+
attr_reader :severity_required
|
27
|
+
alias severity_required? severity_required
|
5
28
|
|
6
|
-
def initialize(rules:, exclude_paths:)
|
29
|
+
def initialize(rules:, exclude_paths:, exclude_binary: DEFAULT_EXCLUDE_BINARY, severity: nil)
|
7
30
|
@rules = rules
|
8
31
|
@exclude_paths = exclude_paths
|
32
|
+
@exclude_binary = exclude_binary || DEFAULT_EXCLUDE_BINARY
|
33
|
+
severity ||= {}
|
34
|
+
@allowed_severities = Set.new(severity.fetch(:allow, []))
|
35
|
+
@severity_required = severity.fetch(:required, false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def severity_allowed?(severity)
|
39
|
+
return true if allowed_severities.empty?
|
40
|
+
allowed_severities.include?(severity)
|
9
41
|
end
|
10
42
|
|
11
43
|
def each_rule(filter:, &block)
|
@@ -44,5 +76,40 @@ module Goodcheck
|
|
44
76
|
enum_for(:rules_for_path, path, rules_filter: rules_filter)
|
45
77
|
end
|
46
78
|
end
|
79
|
+
|
80
|
+
def exclude_path?(path)
|
81
|
+
excluded = exclude_paths.any? do |pattern|
|
82
|
+
path.fnmatch?(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
83
|
+
end
|
84
|
+
|
85
|
+
return true if excluded
|
86
|
+
return excluded unless exclude_binary?
|
87
|
+
return excluded unless path.file?
|
88
|
+
|
89
|
+
exclude_file_by_mime_type?(path)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def exclude_file_by_mime_type?(file)
|
95
|
+
# NOTE: Lazy load to save memory
|
96
|
+
require "marcel"
|
97
|
+
|
98
|
+
fulltype = Marcel::MimeType.for(file)
|
99
|
+
type, subtype = fulltype.split("/")
|
100
|
+
|
101
|
+
case
|
102
|
+
when subtype.end_with?("+xml") # e.g. "image/svg+xml"
|
103
|
+
false
|
104
|
+
when BINARY_MIME_TYPES.include?(type)
|
105
|
+
Goodcheck.logger.debug "Exclude file: #{file} (#{fulltype})"
|
106
|
+
true
|
107
|
+
when BINARY_MIME_FULLTYPES.include?(fulltype)
|
108
|
+
Goodcheck.logger.debug "Exclude file: #{file} (#{fulltype})"
|
109
|
+
true
|
110
|
+
else
|
111
|
+
false
|
112
|
+
end
|
113
|
+
end
|
47
114
|
end
|
48
115
|
end
|
@@ -103,7 +103,8 @@ module Goodcheck
|
|
103
103
|
justification: optional(array_or(string)),
|
104
104
|
glob: optional(glob),
|
105
105
|
pass: optional(array_or(string)),
|
106
|
-
fail: optional(array_or(string))
|
106
|
+
fail: optional(array_or(string)),
|
107
|
+
severity: optional(string)
|
107
108
|
)
|
108
109
|
|
109
110
|
let :negative_rule, object(
|
@@ -113,14 +114,16 @@ module Goodcheck
|
|
113
114
|
justification: optional(array_or(string)),
|
114
115
|
glob: optional(glob),
|
115
116
|
pass: optional(array_or(string)),
|
116
|
-
fail: optional(array_or(string))
|
117
|
+
fail: optional(array_or(string)),
|
118
|
+
severity: optional(string)
|
117
119
|
)
|
118
120
|
|
119
121
|
let :nopattern_rule, object(
|
120
122
|
id: string,
|
121
123
|
message: string,
|
122
124
|
justification: optional(array_or(string)),
|
123
|
-
glob: glob
|
125
|
+
glob: glob,
|
126
|
+
severity: optional(string)
|
124
127
|
)
|
125
128
|
|
126
129
|
let :positive_trigger, object(
|
@@ -163,7 +166,8 @@ module Goodcheck
|
|
163
166
|
id: string,
|
164
167
|
message: string,
|
165
168
|
justification: optional(array_or(string)),
|
166
|
-
trigger: array_or(trigger)
|
169
|
+
trigger: array_or(trigger),
|
170
|
+
severity: optional(string)
|
167
171
|
)
|
168
172
|
|
169
173
|
let :rule, enum(positive_rule,
|
@@ -187,14 +191,17 @@ module Goodcheck
|
|
187
191
|
|
188
192
|
let :rules, array(rule)
|
189
193
|
|
190
|
-
let :
|
191
|
-
|
192
|
-
|
194
|
+
let :severity, object(
|
195
|
+
allow: optional(array(string)),
|
196
|
+
required: boolean?
|
197
|
+
)
|
193
198
|
|
194
199
|
let :config, object(
|
195
200
|
rules: optional(rules),
|
196
|
-
import: optional(
|
197
|
-
exclude: optional(
|
201
|
+
import: optional(array(string)),
|
202
|
+
exclude: optional(array_or(string)),
|
203
|
+
exclude_binary: boolean?,
|
204
|
+
severity: optional(severity)
|
198
205
|
)
|
199
206
|
end
|
200
207
|
|
@@ -224,22 +231,27 @@ module Goodcheck
|
|
224
231
|
load_import rules, import
|
225
232
|
end
|
226
233
|
|
227
|
-
|
228
|
-
|
229
|
-
|
234
|
+
Config.new(
|
235
|
+
rules: rules,
|
236
|
+
exclude_paths: Array(content[:exclude]),
|
237
|
+
exclude_binary: content[:exclude_binary],
|
238
|
+
severity: content[:severity]
|
239
|
+
)
|
230
240
|
end
|
231
241
|
|
232
242
|
def load_rules(rules, array)
|
233
243
|
array.each do |hash|
|
234
244
|
rules << load_rule(hash)
|
245
|
+
rescue RegexpError => exn
|
246
|
+
raise InvalidPattern, "Invalid pattern of the `#{hash.fetch(:id)}` rule in `#{path}`: #{exn.message}"
|
235
247
|
end
|
236
248
|
end
|
237
249
|
|
238
250
|
def load_import(rules, import)
|
239
251
|
Goodcheck.logger.info "Importing rules from #{import}"
|
240
252
|
|
241
|
-
import_loader.load(import) do |content|
|
242
|
-
json = JSON.parse(JSON.dump(YAML.
|
253
|
+
import_loader.load(import) do |content, filename|
|
254
|
+
json = JSON.parse(JSON.dump(YAML.safe_load(content, filename: filename)), symbolize_names: true)
|
243
255
|
|
244
256
|
Schema.rules.coerce json
|
245
257
|
load_rules(rules, json)
|
@@ -253,8 +265,9 @@ module Goodcheck
|
|
253
265
|
triggers = retrieve_triggers(hash)
|
254
266
|
justifications = array(hash[:justification])
|
255
267
|
message = hash[:message].chomp
|
268
|
+
severity = hash[:severity]
|
256
269
|
|
257
|
-
Rule.new(id: id, message: message, justifications: justifications, triggers: triggers)
|
270
|
+
Rule.new(id: id, message: message, justifications: justifications, triggers: triggers, severity: severity)
|
258
271
|
end
|
259
272
|
|
260
273
|
def retrieve_triggers(hash)
|
@@ -31,9 +31,13 @@ module Goodcheck
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def load(name, &block)
|
34
|
-
uri =
|
34
|
+
uri = begin
|
35
|
+
URI.parse(name)
|
36
|
+
rescue URI::InvalidURIError
|
37
|
+
nil
|
38
|
+
end
|
35
39
|
|
36
|
-
case uri
|
40
|
+
case uri&.scheme
|
37
41
|
when nil
|
38
42
|
load_file name, &block
|
39
43
|
when "file"
|
@@ -45,14 +49,20 @@ module Goodcheck
|
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
|
-
def load_file(path)
|
49
|
-
files =
|
52
|
+
def load_file(path, &block)
|
53
|
+
files = Pathname.glob(File.join(config_path.parent.to_path, path), File::FNM_DOTMATCH | File::FNM_EXTGLOB).sort
|
50
54
|
if files.empty?
|
51
55
|
raise FileNotFound.new(path)
|
52
56
|
else
|
53
57
|
files.each do |file|
|
54
58
|
Goodcheck.logger.info "Reading file: #{file}"
|
55
|
-
|
59
|
+
if unarchiver.tar_gz?(file)
|
60
|
+
unarchiver.tar_gz(file.read) do |content, filename|
|
61
|
+
block.call(content, filename)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
block.call(file.read, file.to_path)
|
65
|
+
end
|
56
66
|
end
|
57
67
|
end
|
58
68
|
end
|
@@ -61,7 +71,7 @@ module Goodcheck
|
|
61
71
|
Digest::SHA2.hexdigest(uri.to_s)
|
62
72
|
end
|
63
73
|
|
64
|
-
def load_http(uri)
|
74
|
+
def load_http(uri, &block)
|
65
75
|
hash = cache_name(uri)
|
66
76
|
path = cache_path + hash
|
67
77
|
|
@@ -87,13 +97,19 @@ module Goodcheck
|
|
87
97
|
if download
|
88
98
|
path.rmtree if path.exist?
|
89
99
|
Goodcheck.logger.info "Downloading content..."
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
100
|
+
if unarchiver.tar_gz?(uri.path)
|
101
|
+
unarchiver.tar_gz(http_get(uri)) do |content, filename|
|
102
|
+
block.call(content, filename)
|
103
|
+
write_cache "#{uri}/#{filename}", content
|
104
|
+
end
|
105
|
+
else
|
106
|
+
content = http_get(uri)
|
107
|
+
block.call(content, uri.path)
|
108
|
+
write_cache uri, content
|
109
|
+
end
|
94
110
|
else
|
95
111
|
Goodcheck.logger.info "Reading content from cache..."
|
96
|
-
|
112
|
+
block.call(path.read, path.to_path)
|
97
113
|
end
|
98
114
|
end
|
99
115
|
|
@@ -117,5 +133,17 @@ module Goodcheck
|
|
117
133
|
raise "Error: HTTP GET #{uri.inspect} #{res.inspect}"
|
118
134
|
end
|
119
135
|
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def unarchiver
|
140
|
+
@unarchiver ||=
|
141
|
+
begin
|
142
|
+
filter = ->(filename) {
|
143
|
+
%w[.yml .yaml].include?(File.extname(filename).downcase) && File.basename(filename) != DEFAULT_CONFIG_FILE
|
144
|
+
}
|
145
|
+
Unarchiver.new(file_filter: filter)
|
146
|
+
end
|
147
|
+
end
|
120
148
|
end
|
121
149
|
end
|
@@ -26,7 +26,8 @@ module Goodcheck
|
|
26
26
|
end_column: location.end_column
|
27
27
|
},
|
28
28
|
message: issue.rule.message,
|
29
|
-
justifications: issue.rule.justifications
|
29
|
+
justifications: issue.rule.justifications,
|
30
|
+
severity: issue.rule.severity
|
30
31
|
}
|
31
32
|
end
|
32
33
|
stdout.puts ::JSON.dump(json)
|
@@ -37,7 +37,9 @@ module Goodcheck
|
|
37
37
|
else
|
38
38
|
line.bytesize - start_column
|
39
39
|
end
|
40
|
-
|
40
|
+
rule = Rainbow("(#{issue.rule.id})").darkgray
|
41
|
+
severity = issue.rule.severity ? Rainbow("[#{issue.rule.severity}]").magenta : ""
|
42
|
+
stdout.puts "#{Rainbow(issue.path).cyan}:#{start_line}:#{start_column}: #{message} #{rule} #{severity}".strip
|
41
43
|
stdout.puts line.chomp
|
42
44
|
stdout.puts (" " * start_column_index) + Rainbow("^" + "~" * (column_size - 1)).yellow
|
43
45
|
else
|
data/lib/goodcheck/rule.rb
CHANGED
@@ -4,12 +4,14 @@ module Goodcheck
|
|
4
4
|
attr_reader :triggers
|
5
5
|
attr_reader :message
|
6
6
|
attr_reader :justifications
|
7
|
+
attr_reader :severity
|
7
8
|
|
8
|
-
def initialize(id:, triggers:, message:, justifications:)
|
9
|
+
def initialize(id:, triggers:, message:, justifications:, severity: nil)
|
9
10
|
@id = id
|
10
11
|
@triggers = triggers
|
11
12
|
@message = message
|
12
13
|
@justifications = justifications
|
14
|
+
@severity = severity
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Goodcheck
|
2
|
+
class Unarchiver
|
3
|
+
attr_reader :file_filter
|
4
|
+
|
5
|
+
def initialize(file_filter: ->(_filename) { true })
|
6
|
+
@file_filter = file_filter
|
7
|
+
end
|
8
|
+
|
9
|
+
def tar_gz?(filename)
|
10
|
+
name = filename.to_s.downcase
|
11
|
+
ext = ".tar.gz"
|
12
|
+
name.end_with?(ext) && name != ext
|
13
|
+
end
|
14
|
+
|
15
|
+
def tar_gz(content)
|
16
|
+
require "rubygems/package"
|
17
|
+
|
18
|
+
Gem::Package::TarReader.new(StringIO.new(gz(content))) do |tar_reader|
|
19
|
+
tar_reader.each do |file|
|
20
|
+
if file.file? && file_filter.call(file.full_name)
|
21
|
+
yield file.read, file.full_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def gz(content)
|
30
|
+
require "zlib"
|
31
|
+
|
32
|
+
io = Zlib::GzipReader.new(StringIO.new(content))
|
33
|
+
begin
|
34
|
+
io.read
|
35
|
+
ensure
|
36
|
+
io.close
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/goodcheck/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goodcheck
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Sider Corporation
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,33 +53,39 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: simplecov
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0.18'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0.18'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: marcel
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0
|
76
|
-
|
75
|
+
version: '1.0'
|
76
|
+
- - "<"
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '2.0'
|
79
|
+
type: :runtime
|
77
80
|
prerelease: false
|
78
81
|
version_requirements: !ruby/object:Gem::Requirement
|
79
82
|
requirements:
|
80
83
|
- - ">="
|
81
84
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0
|
85
|
+
version: '1.0'
|
86
|
+
- - "<"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.0'
|
83
89
|
- !ruby/object:Gem::Dependency
|
84
90
|
name: strong_json
|
85
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,7 +135,7 @@ dependencies:
|
|
129
135
|
version: '3.1'
|
130
136
|
- - "<"
|
131
137
|
- !ruby/object:Gem::Version
|
132
|
-
version: '
|
138
|
+
version: '5.0'
|
133
139
|
type: :runtime
|
134
140
|
prerelease: false
|
135
141
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -139,10 +145,11 @@ dependencies:
|
|
139
145
|
version: '3.1'
|
140
146
|
- - "<"
|
141
147
|
- !ruby/object:Gem::Version
|
142
|
-
version: '
|
143
|
-
description:
|
148
|
+
version: '5.0'
|
149
|
+
description: Goodcheck is a regexp based linter that allows you to define custom rules
|
150
|
+
in a YAML file.
|
144
151
|
email:
|
145
|
-
-
|
152
|
+
- support@siderlabs.com
|
146
153
|
executables:
|
147
154
|
- goodcheck
|
148
155
|
extensions: []
|
@@ -177,11 +184,16 @@ files:
|
|
177
184
|
- lib/goodcheck/reporters/text.rb
|
178
185
|
- lib/goodcheck/rule.rb
|
179
186
|
- lib/goodcheck/trigger.rb
|
187
|
+
- lib/goodcheck/unarchiver.rb
|
180
188
|
- lib/goodcheck/version.rb
|
181
|
-
homepage: https://github.
|
189
|
+
homepage: https://sider.github.io/goodcheck/
|
182
190
|
licenses:
|
183
191
|
- MIT
|
184
|
-
metadata:
|
192
|
+
metadata:
|
193
|
+
homepage_uri: https://sider.github.io/goodcheck/
|
194
|
+
source_code_uri: https://github.com/sider/goodcheck
|
195
|
+
changelog_uri: https://github.com/sider/goodcheck/blob/master/CHANGELOG.md
|
196
|
+
bug_tracker_uri: https://github.com/sider/goodcheck/issues
|
185
197
|
post_install_message:
|
186
198
|
rdoc_options: []
|
187
199
|
require_paths:
|
@@ -190,15 +202,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
202
|
requirements:
|
191
203
|
- - ">="
|
192
204
|
- !ruby/object:Gem::Version
|
193
|
-
version: 2.
|
205
|
+
version: 2.5.0
|
194
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
207
|
requirements:
|
196
208
|
- - ">="
|
197
209
|
- !ruby/object:Gem::Version
|
198
210
|
version: '0'
|
199
211
|
requirements: []
|
200
|
-
rubygems_version: 3.
|
212
|
+
rubygems_version: 3.2.20
|
201
213
|
signing_key:
|
202
214
|
specification_version: 4
|
203
|
-
summary: Regexp based customizable linter
|
215
|
+
summary: Regexp based customizable linter.
|
204
216
|
test_files: []
|