goodcheck 2.7.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c2de636a6040f67abd7f2a24da8cd3519d76490724c0897ec4da3e8594e9a88
4
- data.tar.gz: 690af43e3de713f7185fe3d080d8d99b647423e389b941981d18c2a84e049169
3
+ metadata.gz: fa64d7f536fc7b357fbae7aa685827211236015ff9203bc7291847705e4a8189
4
+ data.tar.gz: bf23ca5741b3e424d064d45b9e16ccc28fbf53d299d8a4966ce7a5566c277a0f
5
5
  SHA512:
6
- metadata.gz: 8e6d05a4a287d13d41fcb42af646d259aa50b1cb0c8b0c39d14aa09ce96fa97839331dbf192f1048737f56b5a253ef590187add746f82251c3acb8e45ceaaa7a
7
- data.tar.gz: aeec81a86feffac3321ad616c10f5f319a37dd6efce177a446e0a316c35db1fdc8de405d5c2afd5a7e450a9faafb359578ea92a1283411bfc988f78722c35eb5
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
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019 Sleeek Corporation
3
+ Copyright (c) 2019 Sider Corporation
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
- Check out the [documentation](docusaurus/docs/getstarted.md) for more details.
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
@@ -33,3 +33,8 @@ require "goodcheck/commands/init"
33
33
  require "goodcheck/commands/test"
34
34
  require "goodcheck/import_loader"
35
35
  require "goodcheck/commands/pattern"
36
+ require "goodcheck/unarchiver"
37
+
38
+ module Goodcheck
39
+ DEFAULT_CONFIG_FILE = "goodcheck.yml".freeze
40
+ end
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.exclude_paths.any? {|pattern| path.fnmatch?(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
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
- 1
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
- 1
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
- 1
49
+ EXIT_ERROR
45
50
  rescue Errno::ENOENT => exn
46
51
  stderr.puts "#{exn}"
47
- 1
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 EXIT_ERROR
32
- validate_rules or return EXIT_ERROR
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 id uniqueness..."
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(Rainbow(" Found #{count} #{duplication}.😞").red)
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
- if rule.triggers.any? {|trigger| !trigger.passes.empty? || !trigger.fails.empty?}
71
- stdout.puts "Testing rule #{Rainbow(rule.id).cyan}..."
68
+ stdout.puts "Testing rule #{Rainbow(rule.id).cyan}..."
72
69
 
73
- rule_ok = true
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't match.😱"
102
+ stdout.puts " #{index + 1}. #{Rainbow('fail').red} example didnt 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 " 🚨 The rule contains a `pattern` with `glob`, which is not supported by the test command."
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
- if rule_ok
119
- stdout.puts " OK!🎉"
120
- success_count += 1
121
- else
122
- failure_count += 1
123
- end
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
- rule_count = success_count + failure_count
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 "Tested #{rule}, #{success}, #{failure}"
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
- test_pass
147
+ failed_rule_ids.empty?
143
148
  end
144
149
 
145
150
  def rule_matches_example?(rule, trigger, example)
@@ -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 :import_target, string
191
- let :imports, array(import_target)
192
- let :exclude, array_or(string)
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(imports),
197
- exclude: optional(exclude)
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
- exclude_paths = Array(content[:exclude])
228
-
229
- Config.new(rules: rules, exclude_paths: exclude_paths)
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.load(content, filename: import)), symbolize_names: true)
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)
@@ -2,5 +2,7 @@ module Goodcheck
2
2
  module ExitStatus
3
3
  EXIT_SUCCESS = 0
4
4
  EXIT_ERROR = 1
5
+ EXIT_MATCH = 2
6
+ EXIT_TEST_FAILED = 3
5
7
  end
6
8
  end
@@ -31,9 +31,13 @@ module Goodcheck
31
31
  end
32
32
 
33
33
  def load(name, &block)
34
- uri = URI.parse(name)
34
+ uri = begin
35
+ URI.parse(name)
36
+ rescue URI::InvalidURIError
37
+ nil
38
+ end
35
39
 
36
- case uri.scheme
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 = Dir.glob(File.join(config_path.parent.to_path, path), File::FNM_DOTMATCH | File::FNM_EXTGLOB).sort
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
- yield File.read(file)
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
- content = http_get uri
91
- Goodcheck.logger.debug "Downloaded content: #{content[0, 1024].inspect}#{content.size > 1024 ? "..." : ""}"
92
- yield content
93
- write_cache uri, content
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
- yield path.read
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
- stdout.puts "#{Rainbow(issue.path).cyan}:#{start_line}:#{start_column}: #{message}"
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Goodcheck
2
- VERSION = "2.7.0".freeze
2
+ VERSION = "3.0.0".freeze
3
3
  end
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: 2.7.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Soutaro Matsumoto
7
+ - Sider Corporation
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-02 00:00:00.000000000 Z
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: minitest-reporters
56
+ name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.4'
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: '1.4'
68
+ version: '0.18'
69
69
  - !ruby/object:Gem::Dependency
70
- name: simplecov
70
+ name: marcel
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0.18'
76
- type: :development
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.18'
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: '4.0'
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: '4.0'
143
- description: Regexp based customizable linter
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
- - matsumoto@soutaro.com
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.com/sider/goodcheck
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.4.0
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.1.4
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: []