quiet_quality 1.3.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/dogfood.yml +1 -1
- data/.github/workflows/linters.yml +1 -1
- data/.github/workflows/rspec.yml +1 -1
- data/.quiet_quality.yml +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +17 -0
- data/README.md +54 -8
- data/lib/quiet_quality/cli/arg_parser.rb +12 -0
- data/lib/quiet_quality/cli/entrypoint.rb +21 -1
- data/lib/quiet_quality/cli/message_formatter.rb +190 -0
- data/lib/quiet_quality/cli/presenter.rb +18 -2
- data/lib/quiet_quality/cli.rb +2 -2
- data/lib/quiet_quality/config/builder.rb +15 -1
- data/lib/quiet_quality/config/file_filter.rb +52 -0
- data/lib/quiet_quality/config/options.rb +5 -1
- data/lib/quiet_quality/config/parsed_options.rb +2 -0
- data/lib/quiet_quality/config/parser.rb +11 -1
- data/lib/quiet_quality/config/tool_options.rb +4 -7
- data/lib/quiet_quality/executors/execcer.rb +46 -0
- data/lib/quiet_quality/executors/serial_executor.rb +1 -1
- data/lib/quiet_quality/tools/base_runner.rb +4 -0
- data/lib/quiet_quality/tools/brakeman/runner.rb +4 -0
- data/lib/quiet_quality/tools/brakeman.rb +1 -1
- data/lib/quiet_quality/tools/haml_lint/runner.rb +4 -0
- data/lib/quiet_quality/tools/markdown_lint/runner.rb +9 -3
- data/lib/quiet_quality/tools/relevant_runner.rb +10 -1
- data/lib/quiet_quality/tools/rspec/runner.rb +4 -0
- data/lib/quiet_quality/tools/rubocop/runner.rb +4 -0
- data/lib/quiet_quality/tools/standardrb/runner.rb +4 -0
- data/lib/quiet_quality/tools/standardrb.rb +1 -1
- data/lib/quiet_quality/tools.rb +2 -2
- data/lib/quiet_quality/version.rb +1 -1
- data/lib/quiet_quality.rb +2 -2
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ff5a82876936d746842d76e055475ba19e78d0bfff7967b2f179a2262cb5d7e
|
4
|
+
data.tar.gz: 529ed1d29fdf3c3c6ba381e7ab71cbf006bc44c8d608858e8ec84d67c2efb267
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b840cfe8d90929c0fcc32fdc1370310924a46464006b39afa5dceff69b8c24254d701002e77f33a845baaa7fe5373243486b5d1422a3beea4fd45a50e65ec8b
|
7
|
+
data.tar.gz: e3176f406a5de5a027cae5a2edcbd897395a0191dad1a6cb402bf48fb005f9dce3783d8e6778f91e7d05c03b3f12f473ee23438b9235ed5eedd5e4d588a58526
|
data/.github/workflows/rspec.yml
CHANGED
data/.quiet_quality.yml
CHANGED
data/.standard.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Release 1.5.0
|
4
|
+
|
5
|
+
* Update to comply with current standardrb rules, and use checkout@v4
|
6
|
+
* Add a `-X/--exec` argument that allows you to let qq craft the command, but
|
7
|
+
then actually exec the command instead of running it and handling its output.
|
8
|
+
Especially useful for things like `rspec`, where the output it gives you about
|
9
|
+
failing tests is very useful, and qq is mostly only helpful for determining
|
10
|
+
what specs to run.
|
11
|
+
* Add a `--message-format` argument and `message_format` config file option,
|
12
|
+
which allow for a fairly complex configuration of the output format for
|
13
|
+
messages, so they can be displayed in various colorized/tabular formats.
|
14
|
+
|
15
|
+
## Release 1.4.0
|
16
|
+
|
17
|
+
* Support specifying `excludes` per-tool, so that certain files won't be passed
|
18
|
+
to those tools on the command-line (#107 resolves #106)
|
19
|
+
|
3
20
|
## Release 1.3.1
|
4
21
|
|
5
22
|
* Fix a bug around the logging of nil commands when runners are skipped (#104
|
data/README.md
CHANGED
@@ -151,19 +151,22 @@ The configuration file supports the following _global_ options (top-level keys):
|
|
151
151
|
* `colorize`: by default, `bin/qq` will include color codes in its output, to
|
152
152
|
make failing tools easier to spot, and messages easier to read. But you can
|
153
153
|
supply `colorize: false` to tell it not to do that if you don't want them.
|
154
|
+
* `message_format`: you can specify a format string with which to render the
|
155
|
+
messages, which interpolates values with various formatting flags. Details
|
156
|
+
given in the "Message Formatting" section below.
|
154
157
|
|
155
158
|
And then each tool can have an entry, within which `changed_files` and
|
156
159
|
`filter_messages` can be specified - the tool-specific settings override the
|
157
160
|
global ones.
|
158
161
|
|
159
|
-
The tools have
|
160
|
-
`file_filter`.
|
161
|
-
used to limit what file paths are passed to the tool. For
|
162
|
-
working in a rails engine `engines/foo/`, and you touch one
|
163
|
-
there, you would not want `qq` in the root of the repository
|
164
|
-
`rspec engines/foo/spec/foo/thing_spec.rb` - that probably won't work, as
|
165
|
-
engine will have its own test setup code and Gemfile. This setting is
|
166
|
-
intended to be used like this:
|
162
|
+
The tools have two additional settings that are not available at a global level:
|
163
|
+
`file_filter` and `excludes`. `file_filter` is a string that will be turned into
|
164
|
+
a _ruby regex_, and used to limit what file paths are passed to the tool. For
|
165
|
+
example, if you are working in a rails engine `engines/foo/`, and you touch one
|
166
|
+
of the rspec tests there, you would not want `qq` in the root of the repository
|
167
|
+
to run `rspec engines/foo/spec/foo/thing_spec.rb` - that probably won't work, as
|
168
|
+
your engine will have its own test setup code and Gemfile. This setting is
|
169
|
+
mostly intended to be used like this:
|
167
170
|
|
168
171
|
```yaml
|
169
172
|
rspec:
|
@@ -172,6 +175,49 @@ rspec:
|
|
172
175
|
file_filter: "^spec/"
|
173
176
|
```
|
174
177
|
|
178
|
+
`excludes` are more specific in meaning - this is an _array_ of regexes, and any
|
179
|
+
file that matches any of these regexes will _not_ be passed to the tool as an
|
180
|
+
explicit command line argument. This is generally because tools like rubocop
|
181
|
+
have internal systems for excluding files, but if you pass a filename on the
|
182
|
+
cli, those systems are ignored. That means that if you have changes to a
|
183
|
+
generated file like `db/schema.rb`, and that file doesn't meet your rubocop (or
|
184
|
+
standardrb) rules, you'll get _told_ unless you exclude it at the quiet-quality
|
185
|
+
level as well.
|
186
|
+
|
187
|
+
### Message Formatting
|
188
|
+
|
189
|
+
You can supply a message-format string on the cli or in your config file, which
|
190
|
+
will override the default formatting for message output on the CLI. These format
|
191
|
+
strings are intended to be a single line containing "substitution tokens", which
|
192
|
+
each look like `%[lr]?[bem]?color?(Size)(Source)`.
|
193
|
+
|
194
|
+
* The first (optional) flag can be an "l", and "r", or be left off (which is the
|
195
|
+
same as "l"). This flag indicates the 'justification' - left or right.
|
196
|
+
* The second (optional) flag can be a "b", an "e", or an "m", defaulting to "e";
|
197
|
+
these stand for "beginning", "ending", and "middle", and represent what part
|
198
|
+
of the string should be truncated if it needs to be shortened.
|
199
|
+
* The third (optional) part is a color name, and can be any of "yellow", "red",
|
200
|
+
"green", "blue", "cyan", or "none" (leaving it off is the same as specifing
|
201
|
+
"none"). This is the color to use for the token in the output - note that any
|
202
|
+
color supplied here is used regardless of the '--colorize' flag.
|
203
|
+
* The fourth part of the token is required, and is the _size_ of the token. If a
|
204
|
+
positive integer is supplied, then the token will take up that much space, and
|
205
|
+
will be padded on the appropriate side if necessary; if a negative integer is
|
206
|
+
supplied, then the token will not be padded out, but will still get truncated
|
207
|
+
if it is too long. The value '0' is special, and indicates that the token
|
208
|
+
should be neither padded nor truncated.
|
209
|
+
* The last part of the token is a string indicating the _source_ data to
|
210
|
+
represent, and must be one of these values: "tool", "loc", "level", "path",
|
211
|
+
"lines", "rule", "body". Each of these represents one piece of data out of the
|
212
|
+
message object that can be rendered into the message line.
|
213
|
+
|
214
|
+
Some example message formats:
|
215
|
+
|
216
|
+
```text
|
217
|
+
%lcyan8tool | %lmyellow30rule | %0loc
|
218
|
+
%le6tool [%mblue20rule] %b45loc %cyan-100body
|
219
|
+
```
|
220
|
+
|
175
221
|
### CLI Options
|
176
222
|
|
177
223
|
To specify which _tools_ to run (and if any are specified, the `default_tools`
|
@@ -67,6 +67,7 @@ module QuietQuality
|
|
67
67
|
setup_filter_messages_options(parser)
|
68
68
|
setup_colorization_options(parser)
|
69
69
|
setup_logging_options(parser)
|
70
|
+
setup_message_formatting_options(parser)
|
70
71
|
setup_verbosity_options(parser)
|
71
72
|
end
|
72
73
|
end
|
@@ -100,6 +101,11 @@ module QuietQuality
|
|
100
101
|
validate_value_from("executor", name, Executors::AVAILABLE)
|
101
102
|
set_global_option(:executor, name.to_sym)
|
102
103
|
end
|
104
|
+
|
105
|
+
parser.on("-X", "--exec TOOL", "Exec one tool instead of managing several") do |tool_name|
|
106
|
+
validate_value_from("tool", tool_name, Tools::AVAILABLE)
|
107
|
+
set_global_option(:exec_tool, tool_name.to_sym)
|
108
|
+
end
|
103
109
|
end
|
104
110
|
|
105
111
|
def setup_annotation_options(parser)
|
@@ -163,6 +169,12 @@ module QuietQuality
|
|
163
169
|
end
|
164
170
|
end
|
165
171
|
|
172
|
+
def setup_message_formatting_options(parser)
|
173
|
+
parser.on("-F", "--message-format FMT", "A format string with which to print messages") do |fmt|
|
174
|
+
set_global_option(:message_format, fmt)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
166
178
|
def setup_verbosity_options(parser)
|
167
179
|
parser.on("-v", "--verbose", "Log more verbosely - multiple times is more verbose") do
|
168
180
|
QuietQuality.logger.increase_level!
|
@@ -18,7 +18,7 @@ module QuietQuality
|
|
18
18
|
log_no_tools_text
|
19
19
|
else
|
20
20
|
log_options
|
21
|
-
|
21
|
+
execute!
|
22
22
|
log_results
|
23
23
|
annotate_messages
|
24
24
|
end
|
@@ -114,6 +114,26 @@ module QuietQuality
|
|
114
114
|
@_executed = executor
|
115
115
|
end
|
116
116
|
|
117
|
+
def exec_tool_options
|
118
|
+
@_exec_tool_options ||= options.tools
|
119
|
+
.detect { |topts| topts.tool_name == options.exec_tool.to_sym }
|
120
|
+
end
|
121
|
+
|
122
|
+
def execcer
|
123
|
+
@_execcer ||= QuietQuality::Executors::Execcer.new(
|
124
|
+
tool_options: exec_tool_options,
|
125
|
+
changed_files: changed_files
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
def execute!
|
130
|
+
if options.exec_tool
|
131
|
+
execcer.exec!
|
132
|
+
else
|
133
|
+
executed
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
117
137
|
def annotate_messages
|
118
138
|
return unless options.annotator
|
119
139
|
info("Annotating with #{options.annotator}")
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module QuietQuality
|
2
|
+
module Cli
|
3
|
+
class MessageFormatter
|
4
|
+
TOKEN_MATCHING_REGEX = %r{%[a-z]*-?\d+(?:tool|loc|level|path|lines|rule|body)}
|
5
|
+
|
6
|
+
def initialize(message_format:)
|
7
|
+
@message_format = message_format
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(message)
|
11
|
+
formatted_tokens = parsed_tokens.map { |pt| FormattedToken.new(parsed_token: pt, message: message) }
|
12
|
+
formatted_tokens.reduce(message_format) do |interpolating, ftok|
|
13
|
+
interpolating.gsub(ftok.token, ftok.formatted_token)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :message_format
|
20
|
+
|
21
|
+
def tokens
|
22
|
+
@_tokens ||= message_format.scan(TOKEN_MATCHING_REGEX)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parsed_tokens
|
26
|
+
@_parsed_tokens ||= tokens.map { |tok| ParsedToken.new(tok) }
|
27
|
+
end
|
28
|
+
|
29
|
+
class ParsedToken
|
30
|
+
TOKEN_PARSING_REGEX = %r{
|
31
|
+
% # start the interplation token
|
32
|
+
(?<just>[lr])? # specify the justification
|
33
|
+
(?<trunc>[bem])? # where to truncate from
|
34
|
+
(?<color>yellow|red|green|blue|cyan|none)? # what color
|
35
|
+
(?<size>-?\d+) # string size (may be negative)
|
36
|
+
(?<source>tool|loc|level|path|lines|rule|body) # data source name
|
37
|
+
}x
|
38
|
+
|
39
|
+
COLORS = {
|
40
|
+
"yellow" => :yellow,
|
41
|
+
"red" => :red,
|
42
|
+
"green" => :green,
|
43
|
+
"blue" => :light_blue,
|
44
|
+
"cyan" => :light_cyan,
|
45
|
+
"none" => nil
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
JUSTIFICATIONS = {"l" => :left, "r" => :right}.freeze
|
49
|
+
TRUNCATIONS = {"b" => :beginning, "m" => :middle, "e" => :ending}.freeze
|
50
|
+
|
51
|
+
def initialize(token)
|
52
|
+
@token = token
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :token
|
56
|
+
|
57
|
+
def justification
|
58
|
+
JUSTIFICATIONS.fetch(token_pieces[:just]&.downcase, :left)
|
59
|
+
end
|
60
|
+
|
61
|
+
def truncation
|
62
|
+
TRUNCATIONS.fetch(token_pieces[:trunc]&.downcase, :ending)
|
63
|
+
end
|
64
|
+
|
65
|
+
def color
|
66
|
+
COLORS.fetch(token_pieces[:color]&.downcase, nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
def size
|
70
|
+
raw_size.abs
|
71
|
+
end
|
72
|
+
|
73
|
+
def source
|
74
|
+
token_pieces[:source]
|
75
|
+
end
|
76
|
+
|
77
|
+
def allow_pad?
|
78
|
+
raw_size.positive?
|
79
|
+
end
|
80
|
+
|
81
|
+
def allow_truncate?
|
82
|
+
!raw_size.zero?
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def token_pieces
|
88
|
+
@_token_pieces ||= token.match(TOKEN_PARSING_REGEX)
|
89
|
+
end
|
90
|
+
|
91
|
+
def raw_size
|
92
|
+
@_raw_size ||= token_pieces[:size].to_i
|
93
|
+
end
|
94
|
+
end
|
95
|
+
private_constant :ParsedToken
|
96
|
+
|
97
|
+
class FormattedToken
|
98
|
+
def initialize(parsed_token:, message:)
|
99
|
+
@parsed_token = parsed_token
|
100
|
+
@message = message
|
101
|
+
end
|
102
|
+
|
103
|
+
def formatted_token
|
104
|
+
colorized(padded(truncated(base_string)))
|
105
|
+
end
|
106
|
+
|
107
|
+
def token
|
108
|
+
parsed_token.token
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
attr_reader :parsed_token, :message
|
114
|
+
|
115
|
+
def line_range
|
116
|
+
if message.start_line == message.stop_line
|
117
|
+
message.start_line.to_s
|
118
|
+
else
|
119
|
+
"#{message.start_line}-#{message.stop_line}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def base_string
|
124
|
+
case parsed_token.source
|
125
|
+
when "tool" then message.tool_name
|
126
|
+
when "loc" then location_string
|
127
|
+
when "level" then message.level
|
128
|
+
when "path" then message.path
|
129
|
+
when "lines" then line_range
|
130
|
+
when "rule" then message.rule
|
131
|
+
when "body" then flattened_body
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def location_string
|
136
|
+
"#{message.path}:#{line_range}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def flattened_body
|
140
|
+
message.body.gsub(/ *\n */, "\\n")
|
141
|
+
end
|
142
|
+
|
143
|
+
def truncated(s)
|
144
|
+
return s unless parsed_token.allow_truncate?
|
145
|
+
return s if s.length <= parsed_token.size
|
146
|
+
size = parsed_token.size
|
147
|
+
|
148
|
+
case parsed_token.truncation
|
149
|
+
when :beginning then truncate_beginning(s, size)
|
150
|
+
when :middle then truncate_middle(s, size)
|
151
|
+
when :ending then truncate_ending(s, size)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def truncate_beginning(s, size)
|
156
|
+
"…" + s.slice(1 - size, size - 1)
|
157
|
+
end
|
158
|
+
|
159
|
+
def truncate_middle(s, size)
|
160
|
+
front_len = (size / 2.0).floor
|
161
|
+
back_len = (size / 2.0).ceil - 1
|
162
|
+
s.slice(0, front_len) + "…" + s.slice(-back_len, back_len)
|
163
|
+
end
|
164
|
+
|
165
|
+
def truncate_ending(s, size)
|
166
|
+
s.slice(0, size - 1) + "…"
|
167
|
+
end
|
168
|
+
|
169
|
+
def padded(s)
|
170
|
+
return s unless parsed_token.allow_pad?
|
171
|
+
return s if s.length >= parsed_token.size
|
172
|
+
|
173
|
+
case parsed_token.justification
|
174
|
+
when :left then s.ljust(parsed_token.size)
|
175
|
+
when :right then s.rjust(parsed_token.size)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def colorized(s)
|
180
|
+
if parsed_token.color.nil?
|
181
|
+
s
|
182
|
+
else
|
183
|
+
Colorize.colorize(s, color: parsed_token.color)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
private_constant :FormattedToken
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -78,12 +78,28 @@ module QuietQuality
|
|
78
78
|
s.gsub(/ *\n */, "\\n").slice(0, length)
|
79
79
|
end
|
80
80
|
|
81
|
-
def
|
81
|
+
def locally_formatted_message(msg)
|
82
82
|
tool = colorize(:yellow, msg.tool_name)
|
83
83
|
line_range = line_range_for(msg)
|
84
84
|
rule_string = msg.rule ? " [#{colorize(:yellow, msg.rule)}]" : ""
|
85
85
|
truncated_body = reduce_text(msg.body, 120)
|
86
|
-
|
86
|
+
"#{tool} #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def loggable_message(msg)
|
90
|
+
if options.message_format
|
91
|
+
message_formatter.format(msg)
|
92
|
+
else
|
93
|
+
stream.puts locally_formatted_message(msg)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def log_message(msg)
|
98
|
+
stream.puts loggable_message(msg)
|
99
|
+
end
|
100
|
+
|
101
|
+
def message_formatter
|
102
|
+
@_message_formatter ||= MessageFormatter.new(message_format: options.message_format)
|
87
103
|
end
|
88
104
|
end
|
89
105
|
end
|
data/lib/quiet_quality/cli.rb
CHANGED
@@ -22,7 +22,7 @@ module QuietQuality
|
|
22
22
|
Options.new.tap { |opts| opts.tools = tools }
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def specified_tool_names
|
26
26
|
if cli.tools.any?
|
27
27
|
cli.tools
|
28
28
|
elsif config_file&.tools&.any?
|
@@ -32,6 +32,14 @@ module QuietQuality
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def exec_tool_name
|
36
|
+
cli.global_option(:exec_tool)
|
37
|
+
end
|
38
|
+
|
39
|
+
def tool_names
|
40
|
+
(specified_tool_names + [exec_tool_name]).compact.uniq
|
41
|
+
end
|
42
|
+
|
35
43
|
def config_finder
|
36
44
|
@_config_finder ||= Finder.new(from: ".")
|
37
45
|
end
|
@@ -84,6 +92,7 @@ module QuietQuality
|
|
84
92
|
def update_globals
|
85
93
|
update_annotator
|
86
94
|
update_executor
|
95
|
+
update_exec_tool
|
87
96
|
update_comparison_branch
|
88
97
|
update_logging
|
89
98
|
end
|
@@ -100,6 +109,10 @@ module QuietQuality
|
|
100
109
|
options.executor = Executors::AVAILABLE.fetch(executor_name)
|
101
110
|
end
|
102
111
|
|
112
|
+
def update_exec_tool
|
113
|
+
set_unless_nil(options, :exec_tool, apply.global_option(:exec_tool))
|
114
|
+
end
|
115
|
+
|
103
116
|
def update_comparison_branch
|
104
117
|
set_unless_nil(options, :comparison_branch, apply.global_option(:comparison_branch))
|
105
118
|
end
|
@@ -107,6 +120,7 @@ module QuietQuality
|
|
107
120
|
def update_logging
|
108
121
|
set_unless_nil(options, :logging, apply.global_option(:logging))
|
109
122
|
set_unless_nil(options, :colorize, apply.global_option(:colorize))
|
123
|
+
set_unless_nil(options, :message_format, apply.global_option(:message_format))
|
110
124
|
end
|
111
125
|
|
112
126
|
# ---- update the tool options (apply global forms first) -------
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module QuietQuality
|
2
|
+
module Config
|
3
|
+
class FileFilter
|
4
|
+
# * regex is a regex string
|
5
|
+
# * excludes is an array of regex strings OR a single regex string
|
6
|
+
def initialize(regex: nil, excludes: nil)
|
7
|
+
@regex_string = regex
|
8
|
+
@excludes_strings = excludes
|
9
|
+
end
|
10
|
+
|
11
|
+
def regex
|
12
|
+
return nil if @regex_string.nil?
|
13
|
+
@_regex ||= Regexp.new(@regex_string)
|
14
|
+
end
|
15
|
+
|
16
|
+
def excludes
|
17
|
+
return @_excludes if defined?(@_excludes)
|
18
|
+
|
19
|
+
@_excludes =
|
20
|
+
if @excludes_strings.nil?
|
21
|
+
nil
|
22
|
+
elsif @excludes_strings.is_a?(String)
|
23
|
+
[Regexp.new(@excludes_strings)]
|
24
|
+
else
|
25
|
+
@excludes_strings.map { |xs| Regexp.new(xs) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# The filter _overall_ matches if:
|
30
|
+
# (a) the regex either matches or is not supplied AND
|
31
|
+
# (b) either none of the excludes match or none are supplied
|
32
|
+
def match?(s)
|
33
|
+
regex_match?(s) && !excludes_match?(s)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# The regex is an allow-match - if it's not supplied, treat everything as matching.
|
39
|
+
def regex_match?(s)
|
40
|
+
return true if regex.nil?
|
41
|
+
regex.match?(s)
|
42
|
+
end
|
43
|
+
|
44
|
+
# The excludes are a list of deny-matches - if they're not supplied, treat _nothing_
|
45
|
+
# as matching.
|
46
|
+
def excludes_match?(s)
|
47
|
+
return false if excludes.nil? || excludes.empty?
|
48
|
+
excludes.any? { |exclude| exclude.match?(s) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -7,12 +7,14 @@ module QuietQuality
|
|
7
7
|
@annotator = nil
|
8
8
|
@executor = Executors::ConcurrentExecutor
|
9
9
|
@tools = nil
|
10
|
+
@exec_tool = nil
|
10
11
|
@comparison_branch = nil
|
11
12
|
@colorize = true
|
12
13
|
@logging = :normal
|
14
|
+
@message_format = nil
|
13
15
|
end
|
14
16
|
|
15
|
-
attr_accessor :tools, :comparison_branch, :annotator, :executor
|
17
|
+
attr_accessor :tools, :comparison_branch, :annotator, :executor, :exec_tool, :message_format
|
16
18
|
attr_reader :logging
|
17
19
|
attr_writer :colorize
|
18
20
|
|
@@ -37,9 +39,11 @@ module QuietQuality
|
|
37
39
|
{
|
38
40
|
annotator: annotator,
|
39
41
|
executor: executor.name,
|
42
|
+
exec_tool: exec_tool,
|
40
43
|
comparison_branch: comparison_branch,
|
41
44
|
colorize: colorize?,
|
42
45
|
logging: logging,
|
46
|
+
message_format: message_format,
|
43
47
|
tools: tool_hashes_by_name
|
44
48
|
}
|
45
49
|
end
|
@@ -48,6 +48,7 @@ module QuietQuality
|
|
48
48
|
read_global_option(opts, :unfiltered, :filter_messages, as: :reversed_boolean)
|
49
49
|
read_global_option(opts, :colorize, :colorize, as: :boolean)
|
50
50
|
read_global_option(opts, :logging, :logging, as: :symbol, validate_from: Options::LOGGING_LEVELS)
|
51
|
+
read_global_option(opts, :message_format, :message_format, as: :string)
|
51
52
|
end
|
52
53
|
|
53
54
|
def store_tool_options(opts)
|
@@ -64,7 +65,7 @@ module QuietQuality
|
|
64
65
|
read_tool_option(opts, tool_name, :unfiltered, :filter_messages, as: :reversed_boolean)
|
65
66
|
read_tool_option(opts, tool_name, :changed_files, :limit_targets, as: :boolean)
|
66
67
|
read_tool_option(opts, tool_name, :all_files, :limit_targets, as: :reversed_boolean)
|
67
|
-
|
68
|
+
read_file_filter(opts, tool_name)
|
68
69
|
end
|
69
70
|
|
70
71
|
def invalid!(message)
|
@@ -97,6 +98,15 @@ module QuietQuality
|
|
97
98
|
opts.set_tool_option(tool, into, coerced_value)
|
98
99
|
end
|
99
100
|
|
101
|
+
def read_file_filter(opts, tool)
|
102
|
+
parsed_regex = data.dig(tool.to_sym, :file_filter)
|
103
|
+
parsed_excludes = data.dig(tool.to_sym, :excludes)
|
104
|
+
if parsed_regex || parsed_excludes
|
105
|
+
filter = Config::FileFilter.new(regex: parsed_regex, excludes: parsed_excludes)
|
106
|
+
opts.set_tool_option(tool, :file_filter, filter)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
100
110
|
def validate_value(name, value, as:, from: nil)
|
101
111
|
case as
|
102
112
|
when :boolean then validate_boolean(name, value)
|
@@ -8,8 +8,9 @@ module QuietQuality
|
|
8
8
|
@file_filter = file_filter
|
9
9
|
end
|
10
10
|
|
11
|
+
attr_accessor :file_filter
|
11
12
|
attr_reader :tool_name
|
12
|
-
attr_writer :limit_targets, :filter_messages
|
13
|
+
attr_writer :limit_targets, :filter_messages
|
13
14
|
|
14
15
|
def limit_targets?
|
15
16
|
@limit_targets
|
@@ -31,17 +32,13 @@ module QuietQuality
|
|
31
32
|
tool_namespace::Parser
|
32
33
|
end
|
33
34
|
|
34
|
-
def file_filter
|
35
|
-
return nil if @file_filter.nil?
|
36
|
-
Regexp.new(@file_filter)
|
37
|
-
end
|
38
|
-
|
39
35
|
def to_h
|
40
36
|
{
|
41
37
|
tool_name: tool_name,
|
42
38
|
limit_targets: limit_targets?,
|
43
39
|
filter_messages: filter_messages?,
|
44
|
-
file_filter: file_filter&.to_s
|
40
|
+
file_filter: file_filter&.regex&.to_s,
|
41
|
+
excludes: file_filter&.excludes&.map(&:to_s)
|
45
42
|
}
|
46
43
|
end
|
47
44
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module QuietQuality
|
2
|
+
module Executors
|
3
|
+
class Execcer
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
def initialize(tool_options:, changed_files: nil)
|
7
|
+
@tool_options = tool_options
|
8
|
+
@changed_files = changed_files
|
9
|
+
end
|
10
|
+
|
11
|
+
def exec!
|
12
|
+
if runner.exec_command
|
13
|
+
Kernel.exec(*runner.exec_command)
|
14
|
+
else
|
15
|
+
info <<~LOG_MESSAGE
|
16
|
+
This runner does not believe it needs to execute at all.
|
17
|
+
This typically means that it was told to target changed-files, but no relevant
|
18
|
+
files were changed.
|
19
|
+
LOG_MESSAGE
|
20
|
+
Kernel.exit(0)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :tool_options, :changed_files
|
27
|
+
|
28
|
+
def limit_targets?
|
29
|
+
tool_options.limit_targets?
|
30
|
+
end
|
31
|
+
|
32
|
+
def runner
|
33
|
+
@_runner ||= tool_options.runner_class.new(
|
34
|
+
changed_files: limit_targets? ? changed_files : nil,
|
35
|
+
file_filter: tool_options.file_filter
|
36
|
+
).tap { |r| log_runner(r) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_runner(r)
|
40
|
+
command_string = r.exec_command ? "`#{r.exec_command.join(" ")}`" : "(skipped)"
|
41
|
+
info("Runner #{r.tool_name} exec_command: #{command_string}")
|
42
|
+
debug("Full exec_command for #{r.tool_name}", data: r.exec_command)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -23,6 +23,10 @@ module QuietQuality
|
|
23
23
|
fail(NoMethodError, "BaseRunner subclass must implement `command`")
|
24
24
|
end
|
25
25
|
|
26
|
+
def exec_command
|
27
|
+
fail(NoMethodError, "BaseRunner subclass must implement `exec_command`")
|
28
|
+
end
|
29
|
+
|
26
30
|
def success_status?(stat)
|
27
31
|
stat.success?
|
28
32
|
end
|
@@ -10,6 +10,10 @@ module QuietQuality
|
|
10
10
|
["brakeman", "-f", "json"]
|
11
11
|
end
|
12
12
|
|
13
|
+
def exec_command
|
14
|
+
["brakeman"]
|
15
|
+
end
|
16
|
+
|
13
17
|
# These are specified in constants at the top of brakeman.rb:
|
14
18
|
# https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman.rb#L6-L25
|
15
19
|
def failure_status?(stat)
|
@@ -10,15 +10,21 @@ module QuietQuality
|
|
10
10
|
"[]"
|
11
11
|
end
|
12
12
|
|
13
|
-
def command
|
13
|
+
def command(json: true)
|
14
14
|
return nil if skip_execution?
|
15
|
+
base_command = ["mdl"]
|
16
|
+
base_command << "--json" if json
|
15
17
|
if target_files.any?
|
16
|
-
|
18
|
+
base_command + target_files.sort
|
17
19
|
else
|
18
|
-
|
20
|
+
base_command + ["."]
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
24
|
+
def exec_command
|
25
|
+
command(json: false)
|
26
|
+
end
|
27
|
+
|
22
28
|
def relevant_path?(path)
|
23
29
|
path.end_with?(".md")
|
24
30
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative "
|
1
|
+
require_relative "base_runner"
|
2
2
|
|
3
3
|
module QuietQuality
|
4
4
|
module Tools
|
@@ -16,6 +16,11 @@ module QuietQuality
|
|
16
16
|
base_command + target_files.sort
|
17
17
|
end
|
18
18
|
|
19
|
+
def exec_command
|
20
|
+
return nil if skip_execution?
|
21
|
+
base_exec_command + target_files.sort
|
22
|
+
end
|
23
|
+
|
19
24
|
def relevant_path?(path)
|
20
25
|
fail(NoMethodError, "RelevantRunner subclass must implement `relevant_path?`")
|
21
26
|
end
|
@@ -24,6 +29,10 @@ module QuietQuality
|
|
24
29
|
fail(NoMethodError, "RelevantRunner subclass must implement either `command` or `base_command`")
|
25
30
|
end
|
26
31
|
|
32
|
+
def base_exec_command
|
33
|
+
fail(NoMethodError, "RelevantRunner subclass must implement either `exec_command` or `base_exec_command`")
|
34
|
+
end
|
35
|
+
|
27
36
|
def no_files_output
|
28
37
|
fail(NoMethodError, "RelevantRunner subclass must implement `no_files_output`")
|
29
38
|
end
|
data/lib/quiet_quality/tools.rb
CHANGED
@@ -8,8 +8,8 @@ module QuietQuality
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
require_relative "
|
12
|
-
require_relative "
|
11
|
+
require_relative "tools/base_runner"
|
12
|
+
require_relative "tools/relevant_runner"
|
13
13
|
|
14
14
|
glob = File.expand_path("../tools/*.rb", __FILE__)
|
15
15
|
Dir.glob(glob).sort.each { |f| require f }
|
data/lib/quiet_quality.rb
CHANGED
@@ -16,7 +16,7 @@ module QuietQuality
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
require_relative "
|
20
|
-
require_relative "
|
19
|
+
require_relative "quiet_quality/logger"
|
20
|
+
require_relative "quiet_quality/logging"
|
21
21
|
glob = File.expand_path("../quiet_quality/*.rb", __FILE__)
|
22
22
|
Dir.glob(glob).sort.each { |f| require f }
|
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.5.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-
|
11
|
+
date: 2023-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -184,6 +184,7 @@ files:
|
|
184
184
|
- ".quiet_quality.yml"
|
185
185
|
- ".rspec"
|
186
186
|
- ".rubocop.yml"
|
187
|
+
- ".standard.yml"
|
187
188
|
- CHANGELOG.md
|
188
189
|
- Gemfile
|
189
190
|
- LICENSE
|
@@ -199,10 +200,12 @@ files:
|
|
199
200
|
- lib/quiet_quality/cli.rb
|
200
201
|
- lib/quiet_quality/cli/arg_parser.rb
|
201
202
|
- lib/quiet_quality/cli/entrypoint.rb
|
203
|
+
- lib/quiet_quality/cli/message_formatter.rb
|
202
204
|
- lib/quiet_quality/cli/presenter.rb
|
203
205
|
- lib/quiet_quality/colorize.rb
|
204
206
|
- lib/quiet_quality/config.rb
|
205
207
|
- lib/quiet_quality/config/builder.rb
|
208
|
+
- lib/quiet_quality/config/file_filter.rb
|
206
209
|
- lib/quiet_quality/config/finder.rb
|
207
210
|
- lib/quiet_quality/config/options.rb
|
208
211
|
- lib/quiet_quality/config/parsed_options.rb
|
@@ -211,6 +214,7 @@ files:
|
|
211
214
|
- lib/quiet_quality/executors.rb
|
212
215
|
- lib/quiet_quality/executors/base_executor.rb
|
213
216
|
- lib/quiet_quality/executors/concurrent_executor.rb
|
217
|
+
- lib/quiet_quality/executors/execcer.rb
|
214
218
|
- lib/quiet_quality/executors/pipeline.rb
|
215
219
|
- lib/quiet_quality/executors/serial_executor.rb
|
216
220
|
- lib/quiet_quality/logger.rb
|
@@ -266,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
270
|
- !ruby/object:Gem::Version
|
267
271
|
version: '0'
|
268
272
|
requirements: []
|
269
|
-
rubygems_version: 3.
|
273
|
+
rubygems_version: 3.4.10
|
270
274
|
signing_key:
|
271
275
|
specification_version: 4
|
272
276
|
summary: A system for comparing quality tool outputs against the forward diffs
|