quiet_quality 1.3.1 → 1.5.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/.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
|