quiet_quality 1.0.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dogfood.yml +1 -1
  3. data/.quiet_quality.ci.yml +6 -0
  4. data/.quiet_quality.yml +2 -1
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +59 -12
  7. data/lib/quiet_quality/cli/arg_parser.rb +20 -0
  8. data/lib/quiet_quality/cli/entrypoint.rb +53 -29
  9. data/lib/quiet_quality/cli/presenter.rb +77 -0
  10. data/lib/quiet_quality/config/builder.rb +7 -1
  11. data/lib/quiet_quality/config/logging.rb +23 -0
  12. data/lib/quiet_quality/config/options.rb +6 -0
  13. data/lib/quiet_quality/config/parsed_options.rb +6 -2
  14. data/lib/quiet_quality/config/parser.rb +21 -11
  15. data/lib/quiet_quality/config/tool_options.rb +8 -2
  16. data/lib/quiet_quality/executors/base_executor.rb +8 -0
  17. data/lib/quiet_quality/executors/pipeline.rb +4 -2
  18. data/lib/quiet_quality/logger.rb +17 -0
  19. data/lib/quiet_quality/tools/base_runner.rb +49 -0
  20. data/lib/quiet_quality/tools/brakeman/runner.rb +7 -22
  21. data/lib/quiet_quality/tools/brakeman.rb +0 -2
  22. data/lib/quiet_quality/tools/haml_lint/runner.rb +15 -47
  23. data/lib/quiet_quality/tools/haml_lint.rb +0 -2
  24. data/lib/quiet_quality/tools/markdown_lint/parser.rb +34 -0
  25. data/lib/quiet_quality/tools/markdown_lint/runner.rb +28 -0
  26. data/lib/quiet_quality/tools/markdown_lint.rb +9 -0
  27. data/lib/quiet_quality/tools/relevant_runner.rb +55 -0
  28. data/lib/quiet_quality/tools/rspec/runner.rb +9 -43
  29. data/lib/quiet_quality/tools/rspec.rb +0 -2
  30. data/lib/quiet_quality/tools/rubocop/runner.rb +9 -53
  31. data/lib/quiet_quality/tools/rubocop.rb +0 -2
  32. data/lib/quiet_quality/tools/standardrb/runner.rb +15 -3
  33. data/lib/quiet_quality/tools/standardrb.rb +0 -2
  34. data/lib/quiet_quality/tools.rb +6 -0
  35. data/lib/quiet_quality/version.rb +1 -1
  36. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7372d3c4b395adb2890c54270426bf92b9a2a9e2669ef27d5145eda776d1072
4
- data.tar.gz: 5877277009b82e9685e62f8f8a1f17aec823faaad93c3c8e40dcb7f2640d0e7c
3
+ metadata.gz: ae3f031f4d627e624ce2ae28a5cf58d8e9e0663822371089d54bda51cb1aefeb
4
+ data.tar.gz: c9c26fd4d3df19e70162b38ef78622cc569ea492656d9264c4a41a964931934a
5
5
  SHA512:
6
- metadata.gz: 4c860d24410fd6a03390e726fdc699113f3d0c21b722e12ccf564c666b5708690d8ed90a092448f3942496a47331e10b3d3bb7580ba2e0feeafbf74ea60bdb03
7
- data.tar.gz: 67b89da10e8fc43370d586d9282776c2f1f23a0fa4fcfbea5bfd596ab6520cca7c9319731fd960c63dc04e36ab7021b83e27497d52f2e10b8cdd9ef8a39ad394
6
+ metadata.gz: 6925f90d2f144b9bd59b4f0ca8d9a28e41cebf8570b30e3fa85cee6eefe2961aaaf246f733cd9b62de0d070afb7cca545c650ead15e45a6a39372d34c4240023
7
+ data.tar.gz: 3589c916dab556a467ab64c974ea91eda6a4f6f229ccda58aa51488b13b3a9a44217206fb3962697570cd176fb1bd282bee6231eccad5b50e1d948f0758eb8fb
@@ -27,4 +27,4 @@ jobs:
27
27
  run: bundle install --jobs 4 --retry 3
28
28
 
29
29
  - name: Run QuietQuality
30
- run: bundle exec bin/qq standardrb rubocop rspec --all-files --unfiltered --annotate github_stdout --comparison-branch origin/main
30
+ run: bundle exec bin/qq -C .quiet_quality.ci.yml
@@ -0,0 +1,6 @@
1
+ ---
2
+ default_tools: ["standardrb", "rubocop", "markdown_lint", "rspec"]
3
+ executor: concurrent
4
+ comparison_branch: origin/main
5
+ all_files: true
6
+ unfiltered: true
data/.quiet_quality.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
- default_tools: ["standardrb", "rubocop", "rspec"]
2
+ default_tools: ["standardrb", "rubocop", "markdown_lint", "rspec"]
3
3
  executor: concurrent
4
4
  comparison_branch: main
5
5
  changed_files: false
6
6
  filter_messages: false
7
+ logging: light
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ ## Release 1.2.0
4
+
5
+ * Support `--light`, `--quiet`, and `--logging LEVEL` arguments for less output
6
+ (#78, resolves #37)
7
+ * Support the [markdownlint](https://github.com/markdownlint/markdownlint) tool
8
+ (#79, resolves #58)
9
+ * Extract BaseRunner (#82) and RelevantRunner (#83) parent classes from the tool
10
+ runners, to allow new tools to be more easily implemented. (Resolves #81)
11
+ * Extract a Cli::Presenter from the Entrypoint, to simplify pending work on cli
12
+ presentation (#84, resolves #42)
13
+ * Update the docs a bit, and add a changelog (hi!)
14
+
15
+ ## Release 1.1.0
16
+
17
+ * Support a `file_filter` config entry per-tool (without a cli option), to limit
18
+ what file paths a runner might supply to its tool based on a regex
19
+ (#74, resolves #68)
20
+ * When what tools to execute is not specified (by cli or by config file), abort
21
+ `bin/qq` and explain, rather than assuming "all of them" (#79, resolves #58)
22
+ * Update the config parser to handle keys named to match the cli options
23
+ alongside the ones that were (mistakenly) named differently. This is a
24
+ backwards-compatible change; if we eventually deprecate and simplify some of
25
+ these option names, you'll have plenty of warning (#77, resolves #75)
26
+ * Support `--version/-V` flag (#73, resolves #69)
27
+
28
+ ## Release 1.0.3
29
+
30
+ * Fix the printed _output_ for the case where there were some warnings from a
31
+ tool, but all of them were filtered out (because they targetted lines that
32
+ were not changed, for example). This situation should tell you that nothing
33
+ is wrong with your PR, not that there is a problem (#71)
34
+
35
+ ## Release 1.0.2
36
+
37
+ * Fix the _exit status_ for the case where there were some warnings from a
38
+ tool, but all of them were filtered out (because they targetted lines that
39
+ were not changed, for example). This situation should produce a successful
40
+ result, and not fail a CI pipeline (#67)
41
+
42
+ ## Release 1.0.1
43
+
44
+ * Fix the calculation of `changed_files` for the executor - in the migration
45
+ to Entrypoint, the actual git call to get a ChangedFiles object to pass into
46
+ other service classes was lost, which had the result that the entire system
47
+ behaved (outside of tests) as if you were always running with `--all-files`
48
+ (#65).
49
+
50
+ ## Release 1.0.0
51
+
52
+ Initial functional public release.
data/README.md CHANGED
@@ -14,11 +14,13 @@ that too.
14
14
 
15
15
  So far, we have support for the following tools:
16
16
 
17
- * rubocop
18
- * standardrb
19
- * rspec
20
- * haml-lint
21
- * brakeman (though there's no way to run this against only changed files)
17
+ * [rubocop](https://github.com/rubocop/rubocop)
18
+ * [standardrb](https://github.com/standardrb/standard)
19
+ * [rspec](https://rspec.info/)
20
+ * [haml-lint](https://github.com/sds/haml-lint)
21
+ * [markdownlint](https://github.com/markdownlint/markdownlint)
22
+ * [brakeman](https://brakemanscanner.org/) (though there's no way to run this
23
+ against only changed files)
22
24
 
23
25
  Supporting more tools is relatively straightforward - they're implemented by
24
26
  wrapping cli invocations and parsing output files (which overall seem to be much
@@ -126,6 +128,9 @@ And then run `qq -C config/quiet_quality/linters_workflow.yml`
126
128
 
127
129
  The configuration file supports the following _global_ options (top-level keys):
128
130
 
131
+ * `default_tools`: Which tools should be run when you `qq` without specifying?
132
+ Valid values are: `rubocop`, `rspec`, `standardrb`, `haml_lint`, `brakeman`,
133
+ and `markdown_lint`.
129
134
  * `executor`: 'serial' or 'concurrent' (the latter is the default)
130
135
  * `annotator`: none set by default, and `github_stdout` is the only supported
131
136
  value so far.
@@ -139,17 +144,59 @@ The configuration file supports the following _global_ options (top-level keys):
139
144
  * `filter_messages`: defaults to false - should the resulting messages that do
140
145
  not refer to lines that were changed or added relative to the comparison
141
146
  branch be skipped? Also possible to set for each tool.
147
+ * `logging`: defaults to full messages printed. The `light` option
148
+ prints a aggregated result (e.g. "3 tools executed: 1 passed, 2 failed
149
+ (rubocop, standardrb)"). The `quiet` option will only return a status code,
150
+ printing nothing.
142
151
 
143
152
  And then each tool can have an entry, within which `changed_files` and
144
153
  `filter_messages` can be specified - the tool-specific settings override the
145
154
  global ones.
146
155
 
147
- ### CLI Options
156
+ The tools have one additional setting that is not available at a global level:
157
+ `file_filter`. This is a string that will be turned into a _ruby regex_, and
158
+ used to limit what file paths are passed to the tool. For example, if you are
159
+ working in a rails engine `engines/foo/`, and you touch one of the rspec tests
160
+ there, you would not want `qq` in the root of the repository to run
161
+ `rspec engines/foo/spec/foo/thing_spec.rb` - that probably won't work, as your
162
+ engine will have its own test setup code and Gemfile. This setting is mostly
163
+ intended to be used like this:
148
164
 
149
- The same options are all available on the CLI, plus some additional ones - run
150
- `qq --help` for a detailed list of the options, but the notable additions are:
165
+ ```yaml
166
+ rspec:
167
+ changed_files: true
168
+ filter_messages: false
169
+ file_filter: "^spec/"
170
+ ```
151
171
 
152
- * `--help/-H`: See a list of the options
153
- * `--no-config/-N`: Do _not_ load a config file, even if present.
154
- * `--config/-C` load the supplied config file (instead of the detected one, if
155
- found)
172
+ ### CLI Options
173
+
174
+ To specify which _tools_ to run (and if any are specified, the `default_tools`
175
+ from the configuration file will be ignored), you supply them as positional
176
+ arguments: `qq rubocop rspec --all-files -L` will run the `rubocop` and `rspec`
177
+ tools, for example.
178
+
179
+ Run `qq --help` for a detailed list of the CLI options, they largely agree with
180
+ those in the configuration file, but there are some differences. There's no way
181
+ to specify a `file_filter` for a tool on the command-line, and there are some
182
+ additional options available focused on managing the interactions with
183
+ configuration files.
184
+
185
+ ```text
186
+ Usage: qq [TOOLS] [GLOBAL_OPTIONS] [TOOL_OPTIONS]
187
+ -h, --help Prints this help
188
+ -V, --version Print the current version of the gem
189
+ -C, --config PATH Load a config file from this path
190
+ -N, --no-config Do not load a config file, even if present
191
+ -E, --executor EXECUTOR Which executor to use
192
+ -A, --annotate ANNOTATOR Annotate with this annotator
193
+ -G, --annotate-github-stdout Annotate with GitHub Workflow commands
194
+ -a, --all-files [tool] Use the tool(s) on all files
195
+ -c, --changed-files [tool] Use the tool(s) only on changed files
196
+ -B, --comparison-branch BRANCH Specify the branch to compare against
197
+ -f, --filter-messages [tool] Filter messages from tool(s) based on changed lines
198
+ -u, --unfiltered [tool] Don't filter messages from tool(s)
199
+ -l, --light Print aggregated results only
200
+ -q, --quiet Don't print results, only return a status code
201
+ -L, --logging LEVEL Specify logging mode that results will be returned in. Valid options: light, quiet
202
+ ```
@@ -65,6 +65,7 @@ module QuietQuality
65
65
  setup_annotation_options(parser)
66
66
  setup_file_target_options(parser)
67
67
  setup_filter_messages_options(parser)
68
+ setup_logging_options(parser)
68
69
  end
69
70
  end
70
71
 
@@ -76,6 +77,10 @@ module QuietQuality
76
77
  parser.on("-h", "--help", "Prints this help") do
77
78
  @parsed_options.helping = true
78
79
  end
80
+
81
+ parser.on("-V", "--version", "Print the current version of the gem") do
82
+ @parsed_options.printing_version = true
83
+ end
79
84
  end
80
85
 
81
86
  def setup_config_options(parser)
@@ -130,6 +135,21 @@ module QuietQuality
130
135
  read_tool_or_global_option(:filter_messages, tool, false)
131
136
  end
132
137
  end
138
+
139
+ def setup_logging_options(parser)
140
+ parser.on("-l", "--light", "Print aggregated results only") do
141
+ set_global_option(:logging, Config::Logging::LIGHT)
142
+ end
143
+
144
+ parser.on("-q", "--quiet", "Don't print results, only return a status code") do
145
+ set_global_option(:logging, Config::Logging::QUIET)
146
+ end
147
+
148
+ parser.on("-L", "--logging LEVEL", "Specify logging mode that results will be returned in. Valid options: light, quiet") do |level|
149
+ validate_value_from("logging level", level, Config::Logging::LEVELS)
150
+ set_global_option(:logging, level.to_sym)
151
+ end
152
+ end
133
153
  end
134
154
  end
135
155
  end
@@ -10,10 +10,13 @@ module QuietQuality
10
10
  def execute
11
11
  if helping?
12
12
  log_help_text
13
+ elsif printing_version?
14
+ log_version_text
15
+ elsif no_tools?
16
+ log_no_tools_text
13
17
  else
14
18
  executed
15
- log_outcomes
16
- log_messages
19
+ log_results
17
20
  annotate_messages
18
21
  end
19
22
 
@@ -21,13 +24,32 @@ module QuietQuality
21
24
  end
22
25
 
23
26
  def successful?
24
- helping? || !executed.any_failure?
27
+ return true if helping? || printing_version?
28
+ return false if no_tools?
29
+ !executed.any_failure?
25
30
  end
26
31
 
27
32
  private
28
33
 
29
34
  attr_reader :argv, :output_stream, :error_stream
30
35
 
36
+ def logger
37
+ @_logger ||= QuietQuality::Logger.new(stream: error_stream, logging: options.logging)
38
+ end
39
+
40
+ def presenter
41
+ @_presenter ||= Presenter.new(
42
+ logger: logger,
43
+ logging: options.logging,
44
+ outcomes: executor.outcomes,
45
+ messages: executor.messages
46
+ )
47
+ end
48
+
49
+ def log_results
50
+ presenter.log_results
51
+ end
52
+
31
53
  def arg_parser
32
54
  @_arg_parser ||= ArgParser.new(argv.dup)
33
55
  end
@@ -40,8 +62,35 @@ module QuietQuality
40
62
  parsed_options.helping?
41
63
  end
42
64
 
65
+ def quiet_logging?
66
+ options.logging.quiet?
67
+ end
68
+
69
+ def light_logging?
70
+ options.logging.light?
71
+ end
72
+
73
+ def printing_version?
74
+ parsed_options.printing_version?
75
+ end
76
+
77
+ def no_tools?
78
+ options.tools.empty?
79
+ end
80
+
43
81
  def log_help_text
44
- error_stream.puts(arg_parser.help_text)
82
+ logger.puts(arg_parser.help_text)
83
+ end
84
+
85
+ def log_version_text
86
+ logger.puts(QuietQuality::VERSION)
87
+ end
88
+
89
+ def log_no_tools_text
90
+ logger.puts(<<~TEXT)
91
+ You must specify one or more tools to run, either on the command-line or in the
92
+ default_tools key in a configuration file.
93
+ TEXT
45
94
  end
46
95
 
47
96
  def options
@@ -70,31 +119,6 @@ module QuietQuality
70
119
  @_executed = executor
71
120
  end
72
121
 
73
- def log_outcomes
74
- executed.outcomes.each do |outcome|
75
- result = outcome.success? ? "Passed" : "Failed"
76
- error_stream.puts "--- #{result}: #{outcome.tool}"
77
- end
78
- end
79
-
80
- def log_message(msg)
81
- line_range =
82
- if msg.start_line == msg.stop_line
83
- msg.start_line.to_s
84
- else
85
- "#{msg.start_line}-#{msg.stop_line}"
86
- end
87
- rule_string = msg.rule ? " [#{msg.rule}]" : ""
88
- truncated_body = msg.body.gsub(/ *\n */, "\\n").slice(0, 120)
89
- error_stream.puts " #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
90
- end
91
-
92
- def log_messages
93
- return unless executed.messages.any?
94
- error_stream.puts "\n\n#{executed.messages.count} messages:"
95
- executed.messages.each { |msg| log_message(msg) }
96
- end
97
-
98
122
  def annotate_messages
99
123
  return unless options.annotator
100
124
  annotator = options.annotator.new(output_stream: output_stream)
@@ -0,0 +1,77 @@
1
+ module QuietQuality
2
+ module Cli
3
+ class Presenter
4
+ def initialize(logger:, logging:, outcomes:, messages:)
5
+ @logger = logger
6
+ @logging = logging
7
+ @outcomes = outcomes
8
+ @messages = messages
9
+ end
10
+
11
+ def log_results
12
+ return if logging.quiet?
13
+
14
+ if logging.light?
15
+ log_light_outcomes
16
+ else
17
+ log_outcomes
18
+ log_messages
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :logger, :logging, :outcomes, :messages
25
+
26
+ def failed_outcomes
27
+ @_failed_outcomes ||= outcomes.select(&:failure?)
28
+ end
29
+
30
+ def successful_outcomes
31
+ @_successful_outcomes ||= outcomes.select(&:success?)
32
+ end
33
+
34
+ def log_light_outcomes
35
+ line = "%d tools executed: %d passed, %d failed" % [
36
+ outcomes.count,
37
+ successful_outcomes.count,
38
+ failed_outcomes.count
39
+ ]
40
+ line += " (#{failed_outcomes.map(&:tool).join(", ")})" if failed_outcomes.any?
41
+ logger.puts line
42
+ end
43
+
44
+ def log_outcomes
45
+ outcomes.each do |outcome|
46
+ result = outcome.success? ? "Passed" : "Failed"
47
+ logger.puts "--- #{result}: #{outcome.tool}"
48
+ end
49
+ end
50
+
51
+ def log_messages
52
+ return unless messages.any?
53
+ logger.puts "\n\n#{messages.count} messages:"
54
+ messages.each { |msg| log_message(msg) }
55
+ end
56
+
57
+ def line_range_for(msg)
58
+ if msg.start_line == msg.stop_line
59
+ msg.start_line.to_s
60
+ else
61
+ "#{msg.start_line}-#{msg.stop_line}"
62
+ end
63
+ end
64
+
65
+ def reduce_text(s, length)
66
+ s.gsub(/ *\n */, "\\n").slice(0, length)
67
+ end
68
+
69
+ def log_message(msg)
70
+ line_range = line_range_for(msg)
71
+ rule_string = msg.rule ? " [#{msg.rule}]" : ""
72
+ truncated_body = reduce_text(msg.body, 120)
73
+ logger.puts " #{msg.path}:#{line_range}#{rule_string} #{truncated_body}"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -28,7 +28,7 @@ module QuietQuality
28
28
  elsif config_file&.tools&.any?
29
29
  config_file.tools
30
30
  else
31
- Tools::AVAILABLE.keys
31
+ []
32
32
  end
33
33
  end
34
34
 
@@ -85,6 +85,7 @@ module QuietQuality
85
85
  update_annotator
86
86
  update_executor
87
87
  update_comparison_branch
88
+ update_logging
88
89
  end
89
90
 
90
91
  def update_annotator
@@ -103,12 +104,17 @@ module QuietQuality
103
104
  set_unless_nil(options, :comparison_branch, apply.global_option(:comparison_branch))
104
105
  end
105
106
 
107
+ def update_logging
108
+ set_unless_nil(options, :logging, apply.global_option(:logging))
109
+ end
110
+
106
111
  # ---- update the tool options (apply global forms first) -------
107
112
 
108
113
  def update_tools
109
114
  options.tools.each do |tool_options|
110
115
  update_tool_option(tool_options, :limit_targets)
111
116
  update_tool_option(tool_options, :filter_messages)
117
+ update_tool_option(tool_options, :file_filter)
112
118
  end
113
119
  end
114
120
 
@@ -0,0 +1,23 @@
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
@@ -6,9 +6,15 @@ module QuietQuality
6
6
  @executor = Executors::ConcurrentExecutor
7
7
  @tools = nil
8
8
  @comparison_branch = nil
9
+ @logging = Logging.new
9
10
  end
10
11
 
11
12
  attr_accessor :tools, :comparison_branch, :annotator, :executor
13
+ attr_reader :logging
14
+
15
+ def logging=(level)
16
+ @logging.level = level
17
+ end
12
18
  end
13
19
  end
14
20
  end
@@ -5,16 +5,20 @@ module QuietQuality
5
5
  @tools = []
6
6
  @tool_options = {}
7
7
  @global_options = {}
8
- @helping = false
8
+ @helping = @printing_version = false
9
9
  end
10
10
 
11
11
  attr_accessor :tools
12
- attr_writer :helping
12
+ attr_writer :helping, :printing_version
13
13
 
14
14
  def helping?
15
15
  @helping
16
16
  end
17
17
 
18
+ def printing_version?
19
+ @printing_version
20
+ end
21
+
18
22
  def set_global_option(name, value)
19
23
  @global_options[name.to_sym] = value
20
24
  end
@@ -38,11 +38,15 @@ module QuietQuality
38
38
  end
39
39
 
40
40
  def store_global_options(opts)
41
- read_global_option(opts, :executor, as: :symbol, validate_from: Executors::AVAILABLE)
42
- read_global_option(opts, :annotator, as: :symbol, validate_from: Annotators::ANNOTATOR_TYPES)
43
- read_global_option(opts, :comparison_branch, as: :string)
44
- read_global_option(opts, :changed_files, as: :boolean)
45
- read_global_option(opts, :filter_messages, as: :boolean)
41
+ read_global_option(opts, :executor, :executor, as: :symbol, validate_from: Executors::AVAILABLE)
42
+ read_global_option(opts, :annotator, :annotator, as: :symbol, validate_from: Annotators::ANNOTATOR_TYPES)
43
+ read_global_option(opts, :annotate, :annotator, as: :symbol, validate_from: Annotators::ANNOTATOR_TYPES)
44
+ read_global_option(opts, :comparison_branch, :comparison_branch, as: :string)
45
+ read_global_option(opts, :changed_files, :changed_files, as: :boolean)
46
+ read_global_option(opts, :all_files, :changed_files, as: :reversed_boolean)
47
+ read_global_option(opts, :filter_messages, :filter_messages, as: :boolean)
48
+ read_global_option(opts, :unfiltered, :filter_messages, as: :reversed_boolean)
49
+ read_global_option(opts, :logging, :logging, as: :symbol, validate_from: Logging::LEVELS)
46
50
  end
47
51
 
48
52
  def store_tool_options(opts)
@@ -54,8 +58,12 @@ module QuietQuality
54
58
  def store_tool_options_for(opts, tool_name)
55
59
  entries = data.fetch(tool_name, nil)
56
60
  return if entries.nil?
57
- read_tool_option(opts, tool_name, :filter_messages, as: :boolean)
58
- read_tool_option(opts, tool_name, :changed_files, as: :boolean)
61
+
62
+ read_tool_option(opts, tool_name, :filter_messages, :filter_messages, as: :boolean)
63
+ read_tool_option(opts, tool_name, :unfiltered, :filter_messages, as: :reversed_boolean)
64
+ read_tool_option(opts, tool_name, :changed_files, :changed_files, as: :boolean)
65
+ read_tool_option(opts, tool_name, :all_files, :changed_files, as: :reversed_boolean)
66
+ read_tool_option(opts, tool_name, :file_filter, :file_filter, as: :string)
59
67
  end
60
68
 
61
69
  def invalid!(message)
@@ -70,27 +78,28 @@ module QuietQuality
70
78
  [true, false].include?(value)
71
79
  end
72
80
 
73
- def read_global_option(opts, name, as:, validate_from: nil)
81
+ def read_global_option(opts, name, into, as:, validate_from: nil)
74
82
  parsed_value = data.fetch(name.to_sym, nil)
75
83
  return if parsed_value.nil?
76
84
 
77
85
  validate_value(name, parsed_value, as: as, from: validate_from)
78
86
  coerced_value = coerce_value(parsed_value, as: as)
79
- opts.set_global_option(name, coerced_value)
87
+ opts.set_global_option(into, coerced_value)
80
88
  end
81
89
 
82
- def read_tool_option(opts, tool, name, as:)
90
+ def read_tool_option(opts, tool, name, into, as:)
83
91
  parsed_value = data.dig(tool.to_sym, name.to_sym)
84
92
  return if parsed_value.nil?
85
93
 
86
94
  validate_value("#{tool}.#{name}", parsed_value, as: as)
87
95
  coerced_value = coerce_value(parsed_value, as: as)
88
- opts.set_tool_option(tool, name, coerced_value)
96
+ opts.set_tool_option(tool, into, coerced_value)
89
97
  end
90
98
 
91
99
  def validate_value(name, value, as:, from: nil)
92
100
  case as
93
101
  when :boolean then validate_boolean(name, value)
102
+ when :reversed_boolean then validate_boolean(name, value)
94
103
  when :symbol then validate_symbol(name, value, from: from)
95
104
  when :string then validate_string(name, value)
96
105
  else
@@ -123,6 +132,7 @@ module QuietQuality
123
132
  def coerce_value(value, as:)
124
133
  case as
125
134
  when :boolean then !!value
135
+ when :reversed_boolean then !value
126
136
  when :string then value.to_s
127
137
  when :symbol then value.to_sym
128
138
  else
@@ -1,14 +1,15 @@
1
1
  module QuietQuality
2
2
  module Config
3
3
  class ToolOptions
4
- def initialize(tool, limit_targets: true, filter_messages: true)
4
+ def initialize(tool, limit_targets: true, filter_messages: true, file_filter: nil)
5
5
  @tool_name = tool.to_sym
6
6
  @limit_targets = limit_targets
7
7
  @filter_messages = filter_messages
8
+ @file_filter = file_filter
8
9
  end
9
10
 
10
11
  attr_reader :tool_name
11
- attr_writer :limit_targets, :filter_messages
12
+ attr_writer :limit_targets, :filter_messages, :file_filter
12
13
 
13
14
  def limit_targets?
14
15
  @limit_targets
@@ -29,6 +30,11 @@ module QuietQuality
29
30
  def parser_class
30
31
  tool_namespace::Parser
31
32
  end
33
+
34
+ def file_filter
35
+ return nil if @file_filter.nil?
36
+ Regexp.new(@file_filter)
37
+ end
32
38
  end
33
39
  end
34
40
  end
@@ -22,6 +22,14 @@ module QuietQuality
22
22
  pipelines.any?(&:failure?)
23
23
  end
24
24
 
25
+ def successful_outcomes
26
+ @_successful_outcomes ||= outcomes.select(&:success?)
27
+ end
28
+
29
+ def failed_outcomes
30
+ @_failed_outcomes ||= outcomes.select(&:failure?)
31
+ end
32
+
25
33
  private
26
34
 
27
35
  attr_reader :tools, :changed_files
@@ -48,8 +48,10 @@ module QuietQuality
48
48
  end
49
49
 
50
50
  def runner
51
- @_runner ||= tool_options.runner_class
52
- .new(changed_files: limit_targets? ? changed_files : nil)
51
+ @_runner ||= tool_options.runner_class.new(
52
+ changed_files: limit_targets? ? changed_files : nil,
53
+ file_filter: tool_options.file_filter
54
+ )
53
55
  end
54
56
 
55
57
  def parser
@@ -0,0 +1,17 @@
1
+ module QuietQuality
2
+ class Logger
3
+ def initialize(stream:, logging:)
4
+ @stream = stream
5
+ @logging = logging
6
+ end
7
+
8
+ def puts(s)
9
+ return if logging.quiet?
10
+ stream.puts(s)
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :stream, :logging
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module QuietQuality
2
+ module Tools
3
+ class BaseRunner
4
+ # In general, we don't want to supply a huge number of arguments to a command-line tool.
5
+ MAX_FILES = 100
6
+
7
+ def initialize(changed_files: nil, file_filter: nil)
8
+ @changed_files = changed_files
9
+ @file_filter = file_filter
10
+ end
11
+
12
+ def invoke!
13
+ @_outcome ||= performed_outcome
14
+ end
15
+
16
+ def tool_name
17
+ fail(NoMethodError, "BaseRunner subclass must implement `tool_name`")
18
+ end
19
+
20
+ def command
21
+ fail(NoMethodError, "BaseRunner subclass must implement `command`")
22
+ end
23
+
24
+ def success_status?(stat)
25
+ stat.success?
26
+ end
27
+
28
+ # distinct from _error_ status - this is asking "does this status represent failures-found?"
29
+ def failure_status?(stat)
30
+ stat.exitstatus == 1
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :changed_files, :file_filter
36
+
37
+ def performed_outcome
38
+ out, err, stat = Open3.capture3(*command)
39
+ if success_status?(stat)
40
+ Outcome.new(tool: tool_name, output: out, logging: err)
41
+ elsif failure_status?(stat)
42
+ Outcome.new(tool: tool_name, output: out, logging: err, failure: true)
43
+ else
44
+ fail(ExecutionError, "Execution of #{tool_name} failed with #{stat.exitstatus}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,34 +1,19 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Brakeman
4
- class Runner
5
- # These are specified in constants at the top of brakeman.rb:
6
- # https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman.rb#L6-L25
7
- KNOWN_EXIT_STATUSES = [3, 4, 5, 6, 7, 8].to_set
8
-
9
- def initialize(changed_files: nil)
10
- @changed_files = changed_files
11
- end
12
-
13
- def invoke!
14
- @_outcome ||= performed_outcome
4
+ class Runner < BaseRunner
5
+ def tool_name
6
+ :brakeman
15
7
  end
16
8
 
17
- private
18
-
19
9
  def command
20
10
  ["brakeman", "-f", "json"]
21
11
  end
22
12
 
23
- def performed_outcome
24
- out, err, stat = Open3.capture3(*command)
25
- if stat.success?
26
- Outcome.new(tool: :brakeman, output: out, logging: err)
27
- elsif KNOWN_EXIT_STATUSES.include?(stat.exitstatus)
28
- Outcome.new(tool: :brakeman, output: out, logging: err, failure: true)
29
- else
30
- fail(ExecutionError, "Execution of brakeman failed with #{stat.exitstatus}")
31
- end
13
+ # These are specified in constants at the top of brakeman.rb:
14
+ # https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman.rb#L6-L25
15
+ def failure_status?(stat)
16
+ [3, 4, 5, 6, 7, 8].include?(stat.exitstatus)
32
17
  end
33
18
  end
34
19
  end
@@ -3,8 +3,6 @@ require_relative "./rubocop"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  module Brakeman
6
- ExecutionError = Class.new(Tools::Error)
7
- ParsingError = Class.new(Tools::Error)
8
6
  end
9
7
  end
10
8
  end
@@ -1,61 +1,29 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module HamlLint
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = %({"files": []})
7
-
8
- # haml-lint uses the `sysexits` gem, and exits with Sysexits::EX_DATAERR for the
9
- # failures case here in lib/haml_lint/cli.rb. That's mapped to status 65 - other
10
- # statuses have other failure meanings, which we don't want to interpret as "problems
11
- # encountered"
12
- FAILURE_STATUS = 65
13
-
14
- def initialize(changed_files: nil)
15
- @changed_files = changed_files
16
- end
17
-
18
- def invoke!
19
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
20
- end
21
-
22
- private
23
-
24
- attr_reader :changed_files
25
-
26
- def skip_execution?
27
- changed_files && relevant_files.empty?
28
- end
29
-
30
- def relevant_files
31
- return nil if changed_files.nil?
32
- changed_files.paths.select { |path| path.end_with?(".haml") }
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :haml_lint
33
7
  end
34
8
 
35
- def target_files
36
- return [] if changed_files.nil?
37
- return [] if relevant_files.length > MAX_FILES
38
- relevant_files
9
+ def no_files_output
10
+ %({"files": []})
39
11
  end
40
12
 
41
- def command
42
- return nil if skip_execution?
43
- ["haml-lint", "--reporter", "json"] + target_files.sort
13
+ def base_command
14
+ ["haml-lint", "--reporter", "json"]
44
15
  end
45
16
 
46
- def skipped_outcome
47
- Outcome.new(tool: :haml_lint, output: NO_FILES_OUTPUT)
17
+ def relevant_path?(path)
18
+ path.end_with?(".haml")
48
19
  end
49
20
 
50
- def performed_outcome
51
- out, err, stat = Open3.capture3(*command)
52
- if stat.success?
53
- Outcome.new(tool: :haml_lint, output: out, logging: err)
54
- elsif stat.exitstatus == FAILURE_STATUS
55
- Outcome.new(tool: :haml_lint, output: out, logging: err, failure: true)
56
- else
57
- fail(ExecutionError, "Execution of haml-lint failed with #{stat.exitstatus}")
58
- end
21
+ # haml-lint uses the `sysexits` gem, and exits with Sysexits::EX_DATAERR for the
22
+ # failures case here in lib/haml_lint/cli.rb. That's mapped to status 65 - other
23
+ # statuses have other failure meanings, which we don't want to interpret as "problems
24
+ # encountered"
25
+ def failure_status?(stat)
26
+ stat.exitstatus == 65
59
27
  end
60
28
  end
61
29
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module HamlLint
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -0,0 +1,34 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ class Parser
5
+ def initialize(text)
6
+ @text = text
7
+ end
8
+
9
+ def messages
10
+ return @_messages if defined?(@_messages)
11
+ messages = content.map { |entry| message_for_entry(entry) }
12
+ @_messages = Messages.new(messages)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :text
18
+
19
+ def content
20
+ @_content ||= JSON.parse(text, symbolize_names: true)
21
+ end
22
+
23
+ def message_for_entry(entry)
24
+ Message.new(
25
+ path: entry.fetch(:filename),
26
+ start_line: entry.fetch(:line),
27
+ rule: entry.fetch(:description),
28
+ body: entry.fetch(:docs)
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :markdown_lint
7
+ end
8
+
9
+ def no_files_output
10
+ "[]"
11
+ end
12
+
13
+ def command
14
+ return nil if skip_execution?
15
+ if target_files.any?
16
+ ["mdl", "--json"] + target_files.sort
17
+ else
18
+ ["mdl", "--json", "."]
19
+ end
20
+ end
21
+
22
+ def relevant_path?(path)
23
+ path.end_with?(".md")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ end
5
+ end
6
+ end
7
+
8
+ glob = File.expand_path("../markdown_lint/*.rb", __FILE__)
9
+ Dir.glob(glob).sort.each { |f| require f }
@@ -0,0 +1,55 @@
1
+ require_relative "./base_runner"
2
+
3
+ module QuietQuality
4
+ module Tools
5
+ class RelevantRunner < BaseRunner
6
+ # In general, we don't want to supply a huge number of arguments to a command-line tool.
7
+ # This will probably become configurable later.
8
+ MAX_FILES = 100
9
+
10
+ def invoke!
11
+ @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
12
+ end
13
+
14
+ def command
15
+ return nil if skip_execution?
16
+ base_command + target_files.sort
17
+ end
18
+
19
+ def relevant_path?(path)
20
+ fail(NoMethodError, "RelevantRunner subclass must implement `relevant_path?`")
21
+ end
22
+
23
+ def base_command
24
+ fail(NoMethodError, "RelevantRunner subclass must implement either `command` or `base_command`")
25
+ end
26
+
27
+ def no_files_output
28
+ fail(NoMethodError, "RelevantRunner subclass must implement `no_files_output`")
29
+ end
30
+
31
+ private
32
+
33
+ def skip_execution?
34
+ changed_files && relevant_files.empty?
35
+ end
36
+
37
+ def relevant_files
38
+ return nil if changed_files.nil?
39
+ changed_files.paths
40
+ .select { |path| relevant_path?(path) }
41
+ .select { |path| file_filter.nil? || file_filter.match?(path) }
42
+ end
43
+
44
+ def target_files
45
+ return [] if changed_files.nil?
46
+ return [] if relevant_files.length > MAX_FILES
47
+ relevant_files
48
+ end
49
+
50
+ def skipped_outcome
51
+ Outcome.new(tool: tool_name, output: no_files_output)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,55 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rspec
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = '{"examples": [], "summary": {"failure_count": 0}}'
7
-
8
- def initialize(changed_files: nil)
9
- @changed_files = changed_files
10
- end
11
-
12
- def invoke!
13
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
14
- end
15
-
16
- private
17
-
18
- attr_reader :changed_files
19
-
20
- def skip_execution?
21
- changed_files && relevant_files.empty?
22
- end
23
-
24
- def relevant_files
25
- return nil if changed_files.nil?
26
- changed_files.paths.select { |path| path.end_with?("_spec.rb") }
27
- end
28
-
29
- def target_files
30
- return [] if changed_files.nil?
31
- return [] if relevant_files.length > MAX_FILES
32
- relevant_files
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :rspec
33
7
  end
34
8
 
35
- def command
36
- return nil if skip_execution?
37
- ["rspec", "-f", "json"] + target_files.sort
9
+ def no_files_output
10
+ '{"examples": [], "summary": {"failure_count": 0}}'
38
11
  end
39
12
 
40
- def skipped_outcome
41
- Outcome.new(tool: :rspec, output: NO_FILES_OUTPUT)
13
+ def base_command
14
+ ["rspec", "-f", "json"]
42
15
  end
43
16
 
44
- def performed_outcome
45
- out, err, stat = Open3.capture3(*command)
46
- if stat.success?
47
- Outcome.new(tool: :rspec, output: out, logging: err)
48
- elsif stat.exitstatus == 1
49
- Outcome.new(tool: :rspec, output: out, logging: err, failure: true)
50
- else
51
- fail(ExecutionError, "Execution of rspec failed with #{stat.exitstatus}")
52
- end
17
+ def relevant_path?(path)
18
+ path.end_with?("_spec.rb")
53
19
  end
54
20
  end
55
21
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rspec
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -1,65 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rubocop
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = '{"files": [], "summary": {"offense_count": 0}}'
7
-
8
- def command_name
9
- "rubocop"
10
- end
11
-
12
- # Supplying changed_files: nil means "run against all files".
13
- def initialize(changed_files: nil)
14
- @changed_files = changed_files
15
- end
16
-
17
- def invoke!
18
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
19
- end
20
-
21
- private
22
-
23
- attr_reader :changed_files
24
-
25
- # If we were told that _no files changed_ (which is distinct from not being told that
26
- # any files changed - a [] instead of a nil), then we shouldn't run rubocop at all.
27
- def skip_execution?
28
- changed_files && relevant_files.empty?
29
- end
30
-
31
- # Note: if target_files goes over MAX_FILES, it's _empty_ instead - that means that
32
- # we run against the full repository instead of the specific files (rubocop's behavior
33
- # when no target files are specified)
34
- def command
35
- return nil if skip_execution?
36
- [command_name, "-f", "json"] + target_files.sort
37
- end
38
-
39
- def relevant_files
40
- return nil if changed_files.nil?
41
- changed_files.paths.select { |path| path.end_with?(".rb") }
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :rubocop
42
7
  end
43
8
 
44
- def target_files
45
- return [] if changed_files.nil?
46
- return [] if relevant_files.length > MAX_FILES
47
- relevant_files
9
+ def no_files_output
10
+ '{"files": [], "summary": {"offense_count": 0}}'
48
11
  end
49
12
 
50
- def skipped_outcome
51
- Outcome.new(tool: command_name.to_sym, output: NO_FILES_OUTPUT)
13
+ def base_command
14
+ ["rubocop", "-f", "json"]
52
15
  end
53
16
 
54
- def performed_outcome
55
- out, err, stat = Open3.capture3(*command)
56
- if stat.success?
57
- Outcome.new(tool: command_name.to_sym, output: out, logging: err)
58
- elsif stat.exitstatus == 1
59
- Outcome.new(tool: command_name.to_sym, output: out, logging: err, failure: true)
60
- else
61
- fail(ExecutionError, "Execution of #{command_name} failed with #{stat.exitstatus}")
62
- end
17
+ def relevant_path?(path)
18
+ path.end_with?(".rb")
63
19
  end
64
20
  end
65
21
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rubocop
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -1,9 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Standardrb
4
- class Runner < Rubocop::Runner
5
- def command_name
6
- "standardrb"
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :standardrb
7
+ end
8
+
9
+ def no_files_output
10
+ '{"files": [], "summary": {"offense_count": 0}}'
11
+ end
12
+
13
+ def base_command
14
+ ["standardrb", "-f", "json"]
15
+ end
16
+
17
+ def relevant_path?(path)
18
+ path.end_with?(".rb")
7
19
  end
8
20
  end
9
21
  end
@@ -3,8 +3,6 @@ require_relative "./rubocop"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  module Standardrb
6
- ExecutionError = Class.new(Tools::Error)
7
- ParsingError = Class.new(Tools::Error)
8
6
  end
9
7
  end
10
8
  end
@@ -3,9 +3,14 @@ require "open3"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  Error = Class.new(::QuietQuality::Error)
6
+ ExecutionError = Class.new(Error)
7
+ ParsingError = Class.new(Error)
6
8
  end
7
9
  end
8
10
 
11
+ require_relative "./tools/base_runner"
12
+ require_relative "./tools/relevant_runner"
13
+
9
14
  glob = File.expand_path("../tools/*.rb", __FILE__)
10
15
  Dir.glob(glob).sort.each { |f| require f }
11
16
 
@@ -15,6 +20,7 @@ module QuietQuality
15
20
  AVAILABLE = {
16
21
  brakeman: Brakeman,
17
22
  haml_lint: HamlLint,
23
+ markdown_lint: MarkdownLint,
18
24
  rspec: Rspec,
19
25
  rubocop: Rubocop,
20
26
  standardrb: Standardrb
@@ -1,3 +1,3 @@
1
1
  module QuietQuality
2
- VERSION = "1.0.3"
2
+ VERSION = "1.2.0"
3
3
  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.0.3
4
+ version: 1.2.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-05-24 00:00:00.000000000 Z
11
+ date: 2023-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git
@@ -166,9 +166,11 @@ files:
166
166
  - ".gitignore"
167
167
  - ".mdl_rules.rb"
168
168
  - ".mdlrc"
169
+ - ".quiet_quality.ci.yml"
169
170
  - ".quiet_quality.yml"
170
171
  - ".rspec"
171
172
  - ".rubocop.yml"
173
+ - CHANGELOG.md
172
174
  - Gemfile
173
175
  - LICENSE
174
176
  - README.md
@@ -183,9 +185,11 @@ files:
183
185
  - lib/quiet_quality/cli.rb
184
186
  - lib/quiet_quality/cli/arg_parser.rb
185
187
  - lib/quiet_quality/cli/entrypoint.rb
188
+ - lib/quiet_quality/cli/presenter.rb
186
189
  - lib/quiet_quality/config.rb
187
190
  - lib/quiet_quality/config/builder.rb
188
191
  - lib/quiet_quality/config/finder.rb
192
+ - lib/quiet_quality/config/logging.rb
189
193
  - lib/quiet_quality/config/options.rb
190
194
  - lib/quiet_quality/config/parsed_options.rb
191
195
  - lib/quiet_quality/config/parser.rb
@@ -195,17 +199,23 @@ files:
195
199
  - lib/quiet_quality/executors/concurrent_executor.rb
196
200
  - lib/quiet_quality/executors/pipeline.rb
197
201
  - lib/quiet_quality/executors/serial_executor.rb
202
+ - lib/quiet_quality/logger.rb
198
203
  - lib/quiet_quality/message.rb
199
204
  - lib/quiet_quality/message_filter.rb
200
205
  - lib/quiet_quality/messages.rb
201
206
  - lib/quiet_quality/tools.rb
207
+ - lib/quiet_quality/tools/base_runner.rb
202
208
  - lib/quiet_quality/tools/brakeman.rb
203
209
  - lib/quiet_quality/tools/brakeman/parser.rb
204
210
  - lib/quiet_quality/tools/brakeman/runner.rb
205
211
  - lib/quiet_quality/tools/haml_lint.rb
206
212
  - lib/quiet_quality/tools/haml_lint/parser.rb
207
213
  - lib/quiet_quality/tools/haml_lint/runner.rb
214
+ - lib/quiet_quality/tools/markdown_lint.rb
215
+ - lib/quiet_quality/tools/markdown_lint/parser.rb
216
+ - lib/quiet_quality/tools/markdown_lint/runner.rb
208
217
  - lib/quiet_quality/tools/outcome.rb
218
+ - lib/quiet_quality/tools/relevant_runner.rb
209
219
  - lib/quiet_quality/tools/rspec.rb
210
220
  - lib/quiet_quality/tools/rspec/parser.rb
211
221
  - lib/quiet_quality/tools/rspec/runner.rb