quiet_quality 1.2.1 → 1.3.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/.quiet_quality.ci.yml +1 -0
- data/.quiet_quality.yml +1 -0
- data/CHANGELOG.md +20 -0
- data/README.md +7 -1
- data/lib/quiet_quality/annotators/github_stdout.rb +3 -1
- data/lib/quiet_quality/cli/arg_parser.rb +22 -4
- data/lib/quiet_quality/cli/entrypoint.rb +13 -9
- data/lib/quiet_quality/cli/presenter.rb +26 -13
- data/lib/quiet_quality/colorize.rb +19 -0
- data/lib/quiet_quality/config/builder.rb +1 -0
- data/lib/quiet_quality/config/options.rb +39 -2
- data/lib/quiet_quality/config/parsed_options.rb +1 -0
- data/lib/quiet_quality/config/parser.rb +2 -1
- data/lib/quiet_quality/config/tool_options.rb +9 -0
- data/lib/quiet_quality/executors/pipeline.rb +26 -5
- data/lib/quiet_quality/logger.rb +54 -6
- data/lib/quiet_quality/logging.rb +21 -0
- data/lib/quiet_quality/message.rb +44 -9
- data/lib/quiet_quality/tools/base_runner.rb +9 -0
- data/lib/quiet_quality/tools/brakeman/parser.rb +8 -1
- data/lib/quiet_quality/tools/brakeman/runner.rb +1 -1
- data/lib/quiet_quality/tools/brakeman.rb +1 -0
- data/lib/quiet_quality/tools/haml_lint/parser.rb +2 -1
- data/lib/quiet_quality/tools/haml_lint/runner.rb +1 -1
- data/lib/quiet_quality/tools/haml_lint.rb +1 -0
- data/lib/quiet_quality/tools/markdown_lint/parser.rb +2 -1
- data/lib/quiet_quality/tools/markdown_lint/runner.rb +1 -1
- data/lib/quiet_quality/tools/markdown_lint.rb +1 -0
- data/lib/quiet_quality/tools/relevant_runner.rb +1 -0
- data/lib/quiet_quality/tools/rspec/parser.rb +26 -2
- data/lib/quiet_quality/tools/rspec/runner.rb +1 -1
- data/lib/quiet_quality/tools/rspec.rb +1 -0
- data/lib/quiet_quality/tools/rubocop/parser.rb +6 -1
- data/lib/quiet_quality/tools/rubocop/runner.rb +1 -1
- data/lib/quiet_quality/tools/rubocop.rb +1 -0
- data/lib/quiet_quality/tools/standardrb/parser.rb +3 -0
- data/lib/quiet_quality/tools/standardrb/runner.rb +1 -1
- data/lib/quiet_quality/tools/standardrb.rb +1 -0
- data/lib/quiet_quality/version.rb +1 -1
- data/lib/quiet_quality.rb +6 -0
- data/quiet_quality.gemspec +1 -1
- metadata +8 -7
- data/lib/quiet_quality/config/logging.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 921a945aaedb335246c6944ae8f460505357a73d20f4f1cf2ae3dba3e9fe9041
|
4
|
+
data.tar.gz: fab1f3330661738cc146e26590d489bb9fff0cc0c0318a5e9529fb9fd0802038
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84a082f31fb8a6ae290399a97fdb5918bd91c2eaa701ec7b3909fe27e2d66845ceae7db7bcf9a59dd6696c71bfe99b4cc189f1d529bea49f5f82ba849c8fb553
|
7
|
+
data.tar.gz: 222a7432405df57d2739c0799bc5844b1a916c76febf34c798891ee39e17ce79c17dedaec15928488a819261fdc35d3d746276109455cba7d490b2140c5d31c4
|
data/.quiet_quality.ci.yml
CHANGED
data/.quiet_quality.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Release 1.3.0
|
4
|
+
|
5
|
+
* Support (and enable by default) colorizing the console stderr output from
|
6
|
+
`bin/qq` - disable with the `--no-colorize` flag or the `colorize: false`
|
7
|
+
configuration entry. (#94, resolved #36)
|
8
|
+
* Introduce a Logging facility, and add the `--verbose/-v` flag - supply it
|
9
|
+
either once or twice to enable (colorized) logging in either `info` or `debug`
|
10
|
+
level, providing much more detail about what's going on during execution.
|
11
|
+
|
12
|
+
## Release 1.2.2
|
13
|
+
|
14
|
+
* Add some code to the Rspec::Parser that _cleans_ the json of certain text that
|
15
|
+
simplecov may write into the rspec json output. (#91, resolves #86)
|
16
|
+
* Include the name of the originating tool in the printed message, and the
|
17
|
+
annotation, when a warning is presented. (#90 resolves #72)
|
18
|
+
* Support `normal` as a logging level, and the `--normal` and `-n` cli
|
19
|
+
arguments. This is the default value, so this really only matters if your
|
20
|
+
config file sets another value and you want to override it from the cli.
|
21
|
+
(#91, resolves #86)
|
22
|
+
|
3
23
|
## Release 1.2.1
|
4
24
|
|
5
25
|
* Fix the handling of the various ways to specify whether tools should limit
|
data/README.md
CHANGED
@@ -148,6 +148,9 @@ The configuration file supports the following _global_ options (top-level keys):
|
|
148
148
|
prints a aggregated result (e.g. "3 tools executed: 1 passed, 2 failed
|
149
149
|
(rubocop, standardrb)"). The `quiet` option will only return a status code,
|
150
150
|
printing nothing.
|
151
|
+
* `colorize`: by default, `bin/qq` will include color codes in its output, to
|
152
|
+
make failing tools easier to spot, and messages easier to read. But you can
|
153
|
+
supply `colorize: false` to tell it not to do that if you don't want them.
|
151
154
|
|
152
155
|
And then each tool can have an entry, within which `changed_files` and
|
153
156
|
`filter_messages` can be specified - the tool-specific settings override the
|
@@ -196,7 +199,10 @@ Usage: qq [TOOLS] [GLOBAL_OPTIONS] [TOOL_OPTIONS]
|
|
196
199
|
-B, --comparison-branch BRANCH Specify the branch to compare against
|
197
200
|
-f, --filter-messages [tool] Filter messages from tool(s) based on changed lines
|
198
201
|
-u, --unfiltered [tool] Don't filter messages from tool(s)
|
202
|
+
--[no-]colorize Colorize the logging output
|
203
|
+
-n, --normal Print outcomes and messages
|
199
204
|
-l, --light Print aggregated results only
|
200
205
|
-q, --quiet Don't print results, only return a status code
|
201
|
-
-L, --logging LEVEL Specify logging mode
|
206
|
+
-L, --logging LEVEL Specify logging mode (from light/quiet/normal)
|
207
|
+
-v, --verbose Log more verbosely - multiple times is more verbose
|
202
208
|
```
|
@@ -18,10 +18,12 @@ module QuietQuality
|
|
18
18
|
# ::warning file={name},line={line},title={title}::{message}
|
19
19
|
# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message
|
20
20
|
def self.format(message)
|
21
|
+
title = message.tool_name.to_s
|
22
|
+
title += " #{message.rule}" if message.rule
|
21
23
|
attributes = {
|
22
24
|
file: message.path,
|
23
25
|
line: message.annotated_line || message.start_line,
|
24
|
-
title:
|
26
|
+
title: title
|
25
27
|
}.compact
|
26
28
|
|
27
29
|
attributes_string = attributes.map { |k, v| "#{k}=#{v}" }.join(",")
|
@@ -65,7 +65,9 @@ module QuietQuality
|
|
65
65
|
setup_annotation_options(parser)
|
66
66
|
setup_file_target_options(parser)
|
67
67
|
setup_filter_messages_options(parser)
|
68
|
+
setup_colorization_options(parser)
|
68
69
|
setup_logging_options(parser)
|
70
|
+
setup_verbosity_options(parser)
|
69
71
|
end
|
70
72
|
end
|
71
73
|
|
@@ -136,20 +138,36 @@ module QuietQuality
|
|
136
138
|
end
|
137
139
|
end
|
138
140
|
|
141
|
+
def setup_colorization_options(parser)
|
142
|
+
parser.on("--[no-]colorize", "Colorize the logging output") do |value|
|
143
|
+
set_global_option(:colorize, value)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
139
147
|
def setup_logging_options(parser)
|
148
|
+
parser.on("-n", "--normal", "Print outcomes and messages") do
|
149
|
+
set_global_option(:logging, :normal)
|
150
|
+
end
|
151
|
+
|
140
152
|
parser.on("-l", "--light", "Print aggregated results only") do
|
141
|
-
set_global_option(:logging,
|
153
|
+
set_global_option(:logging, :light)
|
142
154
|
end
|
143
155
|
|
144
156
|
parser.on("-q", "--quiet", "Don't print results, only return a status code") do
|
145
|
-
set_global_option(:logging,
|
157
|
+
set_global_option(:logging, :quiet)
|
146
158
|
end
|
147
159
|
|
148
|
-
parser.on("-L", "--logging LEVEL", "Specify logging mode
|
149
|
-
validate_value_from("logging level", level, Config::
|
160
|
+
parser.on("-L", "--logging LEVEL", "Specify logging mode (from normal/light/quiet)") do |level|
|
161
|
+
validate_value_from("logging level", level.to_sym, Config::Options::LOGGING_LEVELS)
|
150
162
|
set_global_option(:logging, level.to_sym)
|
151
163
|
end
|
152
164
|
end
|
165
|
+
|
166
|
+
def setup_verbosity_options(parser)
|
167
|
+
parser.on("-v", "--verbose", "Log more verbosely - multiple times is more verbose") do
|
168
|
+
QuietQuality.logger.increase_level!
|
169
|
+
end
|
170
|
+
end
|
153
171
|
end
|
154
172
|
end
|
155
173
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
module Cli
|
3
3
|
class Entrypoint
|
4
|
+
include Logging
|
5
|
+
|
4
6
|
def initialize(argv:, output_stream: $stdout, error_stream: $stderr)
|
5
7
|
@argv = argv
|
6
8
|
@output_stream = output_stream
|
@@ -15,6 +17,7 @@ module QuietQuality
|
|
15
17
|
elsif no_tools?
|
16
18
|
log_no_tools_text
|
17
19
|
else
|
20
|
+
log_options
|
18
21
|
executed
|
19
22
|
log_results
|
20
23
|
annotate_messages
|
@@ -33,19 +36,19 @@ module QuietQuality
|
|
33
36
|
|
34
37
|
attr_reader :argv, :output_stream, :error_stream
|
35
38
|
|
36
|
-
def logger
|
37
|
-
@_logger ||= QuietQuality::Logger.new(stream: error_stream, logging: options.logging)
|
38
|
-
end
|
39
|
-
|
40
39
|
def presenter
|
41
40
|
@_presenter ||= Presenter.new(
|
42
|
-
|
43
|
-
|
41
|
+
stream: error_stream,
|
42
|
+
options: options,
|
44
43
|
outcomes: executor.outcomes,
|
45
44
|
messages: executor.messages
|
46
45
|
)
|
47
46
|
end
|
48
47
|
|
48
|
+
def log_options
|
49
|
+
debug("Complete Options object:", data: options.to_h)
|
50
|
+
end
|
51
|
+
|
49
52
|
def log_results
|
50
53
|
presenter.log_results
|
51
54
|
end
|
@@ -71,15 +74,15 @@ module QuietQuality
|
|
71
74
|
end
|
72
75
|
|
73
76
|
def log_help_text
|
74
|
-
|
77
|
+
error_stream.puts(arg_parser.help_text)
|
75
78
|
end
|
76
79
|
|
77
80
|
def log_version_text
|
78
|
-
|
81
|
+
error_stream.puts(QuietQuality::VERSION)
|
79
82
|
end
|
80
83
|
|
81
84
|
def log_no_tools_text
|
82
|
-
|
85
|
+
error_stream.puts(<<~TEXT)
|
83
86
|
You must specify one or more tools to run, either on the command-line or in the
|
84
87
|
default_tools key in a configuration file.
|
85
88
|
TEXT
|
@@ -113,6 +116,7 @@ module QuietQuality
|
|
113
116
|
|
114
117
|
def annotate_messages
|
115
118
|
return unless options.annotator
|
119
|
+
info("Annotating with #{options.annotator}")
|
116
120
|
annotator = options.annotator.new(output_stream: output_stream)
|
117
121
|
annotator.annotate!(executed.messages)
|
118
122
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
module Cli
|
3
3
|
class Presenter
|
4
|
-
def initialize(
|
5
|
-
@
|
6
|
-
@
|
4
|
+
def initialize(stream:, options:, outcomes:, messages:)
|
5
|
+
@stream = stream
|
6
|
+
@options = options
|
7
7
|
@outcomes = outcomes
|
8
8
|
@messages = messages
|
9
9
|
end
|
10
10
|
|
11
11
|
def log_results
|
12
|
-
return if
|
12
|
+
return if options.quiet?
|
13
13
|
|
14
|
-
if
|
14
|
+
if options.light?
|
15
15
|
log_light_outcomes
|
16
16
|
else
|
17
17
|
log_outcomes
|
@@ -21,7 +21,7 @@ module QuietQuality
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :stream, :options, :outcomes, :messages
|
25
25
|
|
26
26
|
def failed_outcomes
|
27
27
|
@_failed_outcomes ||= outcomes.select(&:failure?)
|
@@ -31,26 +31,38 @@ module QuietQuality
|
|
31
31
|
@_successful_outcomes ||= outcomes.select(&:success?)
|
32
32
|
end
|
33
33
|
|
34
|
+
def colorize(color_name, s)
|
35
|
+
return s unless options.colorize?
|
36
|
+
Colorize.colorize(s, color: color_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def failed_tools_text
|
40
|
+
colorize(:red, " (#{failed_outcomes.map(&:tool).join(", ")})")
|
41
|
+
end
|
42
|
+
|
34
43
|
def log_light_outcomes
|
35
44
|
line = "%d tools executed: %d passed, %d failed" % [
|
36
45
|
outcomes.count,
|
37
46
|
successful_outcomes.count,
|
38
47
|
failed_outcomes.count
|
39
48
|
]
|
40
|
-
line +=
|
41
|
-
|
49
|
+
line += failed_tools_text if failed_outcomes.any?
|
50
|
+
stream.puts line
|
42
51
|
end
|
43
52
|
|
44
53
|
def log_outcomes
|
45
54
|
outcomes.each do |outcome|
|
46
|
-
|
47
|
-
|
55
|
+
if outcome.success?
|
56
|
+
stream.puts "--- " + colorize(:green, "Passed: #{outcome.tool}")
|
57
|
+
else
|
58
|
+
stream.puts "--- " + colorize(:red, "Failed: #{outcome.tool}")
|
59
|
+
end
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
63
|
def log_messages
|
52
64
|
return unless messages.any?
|
53
|
-
|
65
|
+
stream.puts "\n\n#{messages.count} messages:"
|
54
66
|
messages.each { |msg| log_message(msg) }
|
55
67
|
end
|
56
68
|
|
@@ -67,10 +79,11 @@ module QuietQuality
|
|
67
79
|
end
|
68
80
|
|
69
81
|
def log_message(msg)
|
82
|
+
tool = colorize(:yellow, msg.tool_name)
|
70
83
|
line_range = line_range_for(msg)
|
71
|
-
rule_string = msg.rule ? " [#{msg.rule}]" : ""
|
84
|
+
rule_string = msg.rule ? " [#{colorize(:yellow, msg.rule)}]" : ""
|
72
85
|
truncated_body = reduce_text(msg.body, 120)
|
73
|
-
|
86
|
+
stream.puts "#{tool} #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
|
74
87
|
end
|
75
88
|
end
|
76
89
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module QuietQuality
|
2
|
+
module Colorize
|
3
|
+
CODES = {
|
4
|
+
red: "\e[31m",
|
5
|
+
green: "\e[32m",
|
6
|
+
yellow: "\e[33m",
|
7
|
+
light_blue: "\e[94m",
|
8
|
+
light_cyan: "\e[96m"
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
RESET_CODE = "\e[0m"
|
12
|
+
|
13
|
+
def self.colorize(s, color:)
|
14
|
+
fail(ArgumentError, "Unrecognized color '#{color}'") unless CODES.include?(color.to_sym)
|
15
|
+
color_code = CODES.fetch(color.to_sym)
|
16
|
+
"#{color_code}#{s}#{RESET_CODE}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -106,6 +106,7 @@ module QuietQuality
|
|
106
106
|
|
107
107
|
def update_logging
|
108
108
|
set_unless_nil(options, :logging, apply.global_option(:logging))
|
109
|
+
set_unless_nil(options, :colorize, apply.global_option(:colorize))
|
109
110
|
end
|
110
111
|
|
111
112
|
# ---- update the tool options (apply global forms first) -------
|
@@ -1,19 +1,56 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
module Config
|
3
3
|
class Options
|
4
|
+
LOGGING_LEVELS = [:quiet, :light, :normal].freeze
|
5
|
+
|
4
6
|
def initialize
|
5
7
|
@annotator = nil
|
6
8
|
@executor = Executors::ConcurrentExecutor
|
7
9
|
@tools = nil
|
8
10
|
@comparison_branch = nil
|
9
|
-
@
|
11
|
+
@colorize = true
|
12
|
+
@logging = :normal
|
10
13
|
end
|
11
14
|
|
12
15
|
attr_accessor :tools, :comparison_branch, :annotator, :executor
|
13
16
|
attr_reader :logging
|
17
|
+
attr_writer :colorize
|
14
18
|
|
15
19
|
def logging=(level)
|
16
|
-
|
20
|
+
fail(ArgumentError, "Unrecognized logging level '#{level}'") unless LOGGING_LEVELS.include?(level.to_sym)
|
21
|
+
@logging = level.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def colorize?
|
25
|
+
!!@colorize
|
26
|
+
end
|
27
|
+
|
28
|
+
def quiet?
|
29
|
+
logging == :quiet
|
30
|
+
end
|
31
|
+
|
32
|
+
def light?
|
33
|
+
logging == :light
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
{
|
38
|
+
annotator: annotator,
|
39
|
+
executor: executor.name,
|
40
|
+
comparison_branch: comparison_branch,
|
41
|
+
colorize: colorize?,
|
42
|
+
logging: logging,
|
43
|
+
tools: tool_hashes_by_name
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def tool_hashes_by_name
|
50
|
+
return {} unless tools
|
51
|
+
tools
|
52
|
+
.map { |tool_option| [tool_option.tool_name, tool_option.to_h] }
|
53
|
+
.to_h
|
17
54
|
end
|
18
55
|
end
|
19
56
|
end
|
@@ -46,7 +46,8 @@ module QuietQuality
|
|
46
46
|
read_global_option(opts, :all_files, :limit_targets, as: :reversed_boolean)
|
47
47
|
read_global_option(opts, :filter_messages, :filter_messages, as: :boolean)
|
48
48
|
read_global_option(opts, :unfiltered, :filter_messages, as: :reversed_boolean)
|
49
|
-
read_global_option(opts, :
|
49
|
+
read_global_option(opts, :colorize, :colorize, as: :boolean)
|
50
|
+
read_global_option(opts, :logging, :logging, as: :symbol, validate_from: Options::LOGGING_LEVELS)
|
50
51
|
end
|
51
52
|
|
52
53
|
def store_tool_options(opts)
|
@@ -35,6 +35,15 @@ module QuietQuality
|
|
35
35
|
return nil if @file_filter.nil?
|
36
36
|
Regexp.new(@file_filter)
|
37
37
|
end
|
38
|
+
|
39
|
+
def to_h
|
40
|
+
{
|
41
|
+
tool_name: tool_name,
|
42
|
+
limit_targets: limit_targets?,
|
43
|
+
filter_messages: filter_messages?,
|
44
|
+
file_filter: file_filter&.to_s
|
45
|
+
}
|
46
|
+
end
|
38
47
|
end
|
39
48
|
end
|
40
49
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
module Executors
|
3
3
|
class Pipeline
|
4
|
+
include Logging
|
5
|
+
|
4
6
|
def initialize(tool_options:, changed_files: nil)
|
5
7
|
@tool_options = tool_options
|
6
8
|
@changed_files = changed_files
|
@@ -25,10 +27,7 @@ module QuietQuality
|
|
25
27
|
|
26
28
|
def messages
|
27
29
|
return @_messages if defined?(@_messages)
|
28
|
-
@_messages = parser.messages
|
29
|
-
@_messages = relevance_filter.filter(@_messages) if filter_messages? && changed_files
|
30
|
-
@_messages.each { |m| locator.update!(m) } if changed_files
|
31
|
-
@_messages
|
30
|
+
@_messages = relocated(filtered(parser.messages))
|
32
31
|
end
|
33
32
|
|
34
33
|
private
|
@@ -51,7 +50,12 @@ module QuietQuality
|
|
51
50
|
@_runner ||= tool_options.runner_class.new(
|
52
51
|
changed_files: limit_targets? ? changed_files : nil,
|
53
52
|
file_filter: tool_options.file_filter
|
54
|
-
)
|
53
|
+
).tap { |r| log_runner(r) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def log_runner(r)
|
57
|
+
info("Runner #{r.tool_name} command: `#{r.command.join(" ")}`")
|
58
|
+
debug("Full command for #{r.tool_name}", data: r.command)
|
55
59
|
end
|
56
60
|
|
57
61
|
def parser
|
@@ -65,6 +69,23 @@ module QuietQuality
|
|
65
69
|
def locator
|
66
70
|
@_locator ||= AnnotationLocator.new(changed_files: changed_files)
|
67
71
|
end
|
72
|
+
|
73
|
+
def filtered(messages_object)
|
74
|
+
return messages_object unless filter_messages? && changed_files
|
75
|
+
|
76
|
+
original_count = messages_object.count
|
77
|
+
relevance_filter.filter(messages_object).tap do |filtered|
|
78
|
+
info("Messages for #{tool_name} filtered from #{original_count} to #{filtered.count}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def relocated(messages_object)
|
83
|
+
if changed_files && !messages_object.empty?
|
84
|
+
messages_object.each { |m| locator.update!(m) }
|
85
|
+
info("Messages for #{tool_name} positioned into the diff for annotation purposes")
|
86
|
+
end
|
87
|
+
messages_object
|
88
|
+
end
|
68
89
|
end
|
69
90
|
end
|
70
91
|
end
|
data/lib/quiet_quality/logger.rb
CHANGED
@@ -1,17 +1,65 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
class Logger
|
3
|
-
|
3
|
+
LEVEL_UPS = {none: :warn, warn: :info, info: :debug}.freeze
|
4
|
+
LEVELS = {none: 0, warn: 1, info: 2, debug: 3}.freeze
|
5
|
+
COLORS = {warn: :yellow, info: :light_blue, debug: :light_cyan}.freeze
|
6
|
+
|
7
|
+
def initialize(level: :warn, stream: $stderr)
|
8
|
+
@level = level
|
4
9
|
@stream = stream
|
5
|
-
@logging = logging
|
6
10
|
end
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
attr_reader :level
|
13
|
+
|
14
|
+
def increase_level!
|
15
|
+
next_level = LEVEL_UPS.fetch(level, nil)
|
16
|
+
self.level = next_level if next_level
|
17
|
+
end
|
18
|
+
|
19
|
+
def show?(message_level)
|
20
|
+
LEVELS[message_level] <= LEVELS[level]
|
21
|
+
end
|
22
|
+
|
23
|
+
def level=(name)
|
24
|
+
fail(ArgumentError, "Unrecognized Logger level '#{name}'") unless LEVELS.include?(name.to_sym)
|
25
|
+
@level = name.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(message, data: nil)
|
29
|
+
log_message(message, data, :warn)
|
30
|
+
end
|
31
|
+
|
32
|
+
def info(message, data: nil)
|
33
|
+
log_message(message, data, :info)
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug(message, data: nil)
|
37
|
+
log_message(message, data, :debug)
|
11
38
|
end
|
12
39
|
|
13
40
|
private
|
14
41
|
|
15
|
-
attr_reader :stream
|
42
|
+
attr_reader :stream
|
43
|
+
|
44
|
+
def log_message(message, data, message_level)
|
45
|
+
return unless show?(message_level)
|
46
|
+
stream.puts formatted_message(message, data, message_level)
|
47
|
+
stream.flush
|
48
|
+
end
|
49
|
+
|
50
|
+
def formatted_message(message, data, message_level)
|
51
|
+
prefix = message_level.to_s.upcase.rjust(5)
|
52
|
+
if data
|
53
|
+
data_text = JSON.pretty_generate(data)
|
54
|
+
message = message + "\n" + data_text
|
55
|
+
end
|
56
|
+
prefixed_message = message.split("\n").map { |line| "[#{prefix}] #{line}" }.join("\n")
|
57
|
+
colorize(prefixed_message, message_level)
|
58
|
+
end
|
59
|
+
|
60
|
+
def colorize(s, message_level)
|
61
|
+
color = COLORS.fetch(message_level)
|
62
|
+
Colorize.colorize(s, color: color)
|
63
|
+
end
|
16
64
|
end
|
17
65
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module QuietQuality
|
2
|
+
module Logging
|
3
|
+
def warn(message, data: nil)
|
4
|
+
logger.warn(message, data: data)
|
5
|
+
end
|
6
|
+
|
7
|
+
def info(message, data: nil)
|
8
|
+
logger.info(message, data: data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug(message, data: nil)
|
12
|
+
logger.debug(message, data: data)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def logger
|
18
|
+
QuietQuality.logger
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
class Message
|
3
|
-
|
4
|
-
|
3
|
+
REQUIRED_ATTRS = %w[path body start_line tool_name].freeze
|
4
|
+
|
5
|
+
attr_writer :annotated_line
|
5
6
|
|
6
7
|
def self.load(hash)
|
7
8
|
new(**hash)
|
@@ -9,17 +10,51 @@ module QuietQuality
|
|
9
10
|
|
10
11
|
def initialize(**attrs)
|
11
12
|
@attrs = attrs.map { |k, v| [k.to_s, v] }.to_h
|
12
|
-
|
13
|
-
@body = @attrs.fetch("body")
|
14
|
-
@start_line = @attrs.fetch("start_line")
|
15
|
-
@stop_line = @attrs.fetch("stop_line", @start_line)
|
16
|
-
@annotated_line = @attrs.fetch("annotated_line", nil)
|
17
|
-
@level = @attrs.fetch("level", nil)
|
18
|
-
@rule = @attrs.fetch("rule", nil)
|
13
|
+
validate_attrs!
|
19
14
|
end
|
20
15
|
|
21
16
|
def to_h
|
22
17
|
@attrs.map { |k, v| [k.to_s, v] }.to_h
|
23
18
|
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
@_path ||= @attrs.fetch("path")
|
22
|
+
end
|
23
|
+
|
24
|
+
def body
|
25
|
+
@_body ||= @attrs.fetch("body")
|
26
|
+
end
|
27
|
+
|
28
|
+
def tool_name
|
29
|
+
@_tool_name ||= @attrs.fetch("tool_name")
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_line
|
33
|
+
@_start_line ||= @attrs.fetch("start_line")
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_line
|
37
|
+
@_stop_line ||= @attrs.fetch("stop_line", start_line)
|
38
|
+
end
|
39
|
+
|
40
|
+
def annotated_line
|
41
|
+
@annotated_line ||= @attrs.fetch("annotated_line", nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def level
|
45
|
+
@_level ||= @attrs.fetch("level", nil)
|
46
|
+
end
|
47
|
+
|
48
|
+
def rule
|
49
|
+
@_rule ||= @attrs.fetch("rule", nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_attrs!
|
55
|
+
REQUIRED_ATTRS.each do |attr|
|
56
|
+
raise ArgumentError, "Missing required attribute #{attr}" unless @attrs[attr]
|
57
|
+
end
|
58
|
+
end
|
24
59
|
end
|
25
60
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module QuietQuality
|
2
2
|
module Tools
|
3
3
|
class BaseRunner
|
4
|
+
include Logging
|
5
|
+
|
4
6
|
# In general, we don't want to supply a huge number of arguments to a command-line tool.
|
5
7
|
MAX_FILES = 100
|
6
8
|
|
@@ -36,6 +38,8 @@ module QuietQuality
|
|
36
38
|
|
37
39
|
def performed_outcome
|
38
40
|
out, err, stat = Open3.capture3(*command)
|
41
|
+
log_performance(err, stat)
|
42
|
+
|
39
43
|
if success_status?(stat)
|
40
44
|
Outcome.new(tool: tool_name, output: out, logging: err)
|
41
45
|
elsif failure_status?(stat)
|
@@ -44,6 +48,11 @@ module QuietQuality
|
|
44
48
|
fail(ExecutionError, "Execution of #{tool_name} failed with #{stat.exitstatus}")
|
45
49
|
end
|
46
50
|
end
|
51
|
+
|
52
|
+
def log_performance(err, stat)
|
53
|
+
info("Runner #{tool_name} exited with #{stat.exitstatus}")
|
54
|
+
debug("Runner logs from #{tool_name}:", data: err&.split("\n"))
|
55
|
+
end
|
47
56
|
end
|
48
57
|
end
|
49
58
|
end
|
@@ -37,7 +37,14 @@ module QuietQuality
|
|
37
37
|
line = warning.fetch(:line)
|
38
38
|
level = warning.fetch(:confidence, nil)
|
39
39
|
rule = warning.fetch(:warning_type)
|
40
|
-
Message.new(
|
40
|
+
Message.new(
|
41
|
+
path: path,
|
42
|
+
body: body,
|
43
|
+
start_line: line,
|
44
|
+
level: level,
|
45
|
+
rule: rule,
|
46
|
+
tool_name: TOOL_NAME
|
47
|
+
)
|
41
48
|
end
|
42
49
|
end
|
43
50
|
end
|
@@ -36,7 +36,8 @@ module QuietQuality
|
|
36
36
|
body: offense.fetch(:message),
|
37
37
|
start_line: offense.dig(:location, :line),
|
38
38
|
level: offense.fetch(:severity, nil),
|
39
|
-
rule: offense.fetch(:linter_name, nil)
|
39
|
+
rule: offense.fetch(:linter_name, nil),
|
40
|
+
tool_name: TOOL_NAME
|
40
41
|
)
|
41
42
|
end
|
42
43
|
end
|
@@ -16,8 +16,26 @@ module QuietQuality
|
|
16
16
|
|
17
17
|
attr_reader :text
|
18
18
|
|
19
|
+
# Many people use simplecov with rspec, and its default formatter
|
20
|
+
# writes text output into the stdout stream of rspec even when rspec is
|
21
|
+
# asked for json output. I have an issue open here, and I'll get a pair
|
22
|
+
# of PRs together if they indicate any willingness to accept such a
|
23
|
+
# change: https://github.com/simplecov-ruby/simplecov/issues/1060
|
24
|
+
#
|
25
|
+
# The only stdout writes are visible on these lines:
|
26
|
+
# https://github.com/simplecov-ruby/simplecov-html/blob/main/lib/simplecov-html.rb#L31
|
27
|
+
# https://github.com/simplecov-ruby/simplecov-html/blob/main/lib/simplecov-html.rb#L80
|
28
|
+
#
|
29
|
+
# There are _hundreds_ of rspec plugins, and any of them could write to
|
30
|
+
# stdout - we probably won't worry about any but the most common.
|
31
|
+
def cleaned_text
|
32
|
+
@_cleaned_text ||= text
|
33
|
+
.gsub(/Coverage report generated.*covered.$/, "")
|
34
|
+
.gsub(/Encoding problems with file.*$/, "")
|
35
|
+
end
|
36
|
+
|
19
37
|
def content
|
20
|
-
@_content ||= JSON.parse(
|
38
|
+
@_content ||= JSON.parse(cleaned_text, symbolize_names: true)
|
21
39
|
end
|
22
40
|
|
23
41
|
def examples
|
@@ -37,7 +55,13 @@ module QuietQuality
|
|
37
55
|
body = example.dig(:exception, :message) || example.fetch(:description)
|
38
56
|
line = example.fetch(:line_number)
|
39
57
|
rule = example.dig(:exception, :class) || "Failed Example"
|
40
|
-
Message.new(
|
58
|
+
Message.new(
|
59
|
+
path: path,
|
60
|
+
body: body,
|
61
|
+
start_line: line,
|
62
|
+
rule: rule,
|
63
|
+
tool_name: TOOL_NAME
|
64
|
+
)
|
41
65
|
end
|
42
66
|
end
|
43
67
|
end
|
@@ -37,9 +37,14 @@ module QuietQuality
|
|
37
37
|
start_line: offense.dig(:location, :start_line),
|
38
38
|
stop_line: offense.dig(:location, :last_line),
|
39
39
|
level: offense.fetch(:severity, nil),
|
40
|
-
rule: offense.fetch(:cop_name, nil)
|
40
|
+
rule: offense.fetch(:cop_name, nil),
|
41
|
+
tool_name: tool_name
|
41
42
|
)
|
42
43
|
end
|
44
|
+
|
45
|
+
def tool_name
|
46
|
+
TOOL_NAME
|
47
|
+
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
data/lib/quiet_quality.rb
CHANGED
@@ -10,7 +10,13 @@ require "set" # rubocop:disable Lint/RedundantRequireStatement
|
|
10
10
|
|
11
11
|
module QuietQuality
|
12
12
|
Error = Class.new(StandardError)
|
13
|
+
|
14
|
+
def self.logger
|
15
|
+
@_logger ||= QuietQuality::Logger.new
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
19
|
+
require_relative "./quiet_quality/logger"
|
20
|
+
require_relative "./quiet_quality/logging"
|
15
21
|
glob = File.expand_path("../quiet_quality/*.rb", __FILE__)
|
16
22
|
Dir.glob(glob).sort.each { |f| require f }
|
data/quiet_quality.gemspec
CHANGED
@@ -40,6 +40,6 @@ Gem::Specification.new do |spec|
|
|
40
40
|
spec.add_development_dependency "pry", "~> 0.14"
|
41
41
|
spec.add_development_dependency "standard", "~> 1.28"
|
42
42
|
spec.add_development_dependency "rubocop", "~> 1.50"
|
43
|
-
spec.add_development_dependency "debug"
|
43
|
+
spec.add_development_dependency "debug", "~> 1.7"
|
44
44
|
spec.add_development_dependency "mdl", "~> 0.12"
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quiet_quality
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Mueller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -126,16 +126,16 @@ dependencies:
|
|
126
126
|
name: debug
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '1.7'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - "
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '1.7'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: mdl
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,10 +186,10 @@ files:
|
|
186
186
|
- lib/quiet_quality/cli/arg_parser.rb
|
187
187
|
- lib/quiet_quality/cli/entrypoint.rb
|
188
188
|
- lib/quiet_quality/cli/presenter.rb
|
189
|
+
- lib/quiet_quality/colorize.rb
|
189
190
|
- lib/quiet_quality/config.rb
|
190
191
|
- lib/quiet_quality/config/builder.rb
|
191
192
|
- lib/quiet_quality/config/finder.rb
|
192
|
-
- lib/quiet_quality/config/logging.rb
|
193
193
|
- lib/quiet_quality/config/options.rb
|
194
194
|
- lib/quiet_quality/config/parsed_options.rb
|
195
195
|
- lib/quiet_quality/config/parser.rb
|
@@ -200,6 +200,7 @@ files:
|
|
200
200
|
- lib/quiet_quality/executors/pipeline.rb
|
201
201
|
- lib/quiet_quality/executors/serial_executor.rb
|
202
202
|
- lib/quiet_quality/logger.rb
|
203
|
+
- lib/quiet_quality/logging.rb
|
203
204
|
- lib/quiet_quality/message.rb
|
204
205
|
- lib/quiet_quality/message_filter.rb
|
205
206
|
- lib/quiet_quality/messages.rb
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module QuietQuality
|
2
|
-
module Config
|
3
|
-
class Logging
|
4
|
-
LIGHT = :light
|
5
|
-
QUIET = :quiet
|
6
|
-
LEVELS = [LIGHT, QUIET].freeze
|
7
|
-
|
8
|
-
attr_accessor :level
|
9
|
-
|
10
|
-
def initialize(level: nil)
|
11
|
-
@level = level
|
12
|
-
end
|
13
|
-
|
14
|
-
def light?
|
15
|
-
@level == LIGHT
|
16
|
-
end
|
17
|
-
|
18
|
-
def quiet?
|
19
|
-
@level == QUIET
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|