reviewer 0.1.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/main.yml +79 -11
- data/.github/workflows/release.yml +98 -0
- data/.gitignore +1 -1
- data/.inch.yml +3 -1
- data/.reek.yml +175 -0
- data/.reviewer.example.yml +7 -2
- data/.reviewer.yml +166 -40
- data/.rubocop.yml +34 -2
- data/CHANGELOG.md +42 -2
- data/Gemfile +39 -1
- data/Gemfile.lock +291 -70
- data/LICENSE.txt +20 -4
- data/README.md +310 -21
- data/RELEASING.md +190 -0
- data/Rakefile +117 -0
- data/dependency_decisions.yml +61 -0
- data/exe/fmt +1 -1
- data/exe/rvw +1 -1
- data/lib/reviewer/arguments/files.rb +47 -20
- data/lib/reviewer/arguments/keywords.rb +34 -41
- data/lib/reviewer/arguments/tags.rb +11 -11
- data/lib/reviewer/arguments.rb +100 -29
- data/lib/reviewer/batch/formatter.rb +87 -0
- data/lib/reviewer/batch.rb +32 -48
- data/lib/reviewer/capabilities.rb +81 -0
- data/lib/reviewer/command/string/env.rb +12 -6
- data/lib/reviewer/command/string/flags.rb +2 -4
- data/lib/reviewer/command/string.rb +47 -12
- data/lib/reviewer/command.rb +65 -10
- data/lib/reviewer/configuration/loader.rb +70 -0
- data/lib/reviewer/configuration.rb +6 -3
- data/lib/reviewer/context.rb +15 -0
- data/lib/reviewer/doctor/config_check.rb +46 -0
- data/lib/reviewer/doctor/environment_check.rb +58 -0
- data/lib/reviewer/doctor/formatter.rb +75 -0
- data/lib/reviewer/doctor/keyword_check.rb +85 -0
- data/lib/reviewer/doctor/opportunity_check.rb +88 -0
- data/lib/reviewer/doctor/report.rb +63 -0
- data/lib/reviewer/doctor/tool_inventory.rb +41 -0
- data/lib/reviewer/doctor.rb +28 -0
- data/lib/reviewer/history.rb +10 -17
- data/lib/reviewer/output/formatting.rb +40 -0
- data/lib/reviewer/output/printer.rb +70 -9
- data/lib/reviewer/output.rb +37 -78
- data/lib/reviewer/prompt.rb +38 -0
- data/lib/reviewer/report/formatter.rb +124 -0
- data/lib/reviewer/report.rb +100 -0
- data/lib/reviewer/runner/failed_files.rb +66 -0
- data/lib/reviewer/runner/formatter.rb +103 -0
- data/lib/reviewer/runner/guidance.rb +79 -0
- data/lib/reviewer/runner/result.rb +150 -0
- data/lib/reviewer/runner/strategies/captured.rb +98 -23
- data/lib/reviewer/runner/strategies/passthrough.rb +2 -11
- data/lib/reviewer/runner.rb +126 -40
- data/lib/reviewer/session/formatter.rb +87 -0
- data/lib/reviewer/session.rb +208 -0
- data/lib/reviewer/setup/catalog.rb +233 -0
- data/lib/reviewer/setup/detector.rb +61 -0
- data/lib/reviewer/setup/formatter.rb +94 -0
- data/lib/reviewer/setup/gemfile_lock.rb +55 -0
- data/lib/reviewer/setup/generator.rb +54 -0
- data/lib/reviewer/setup/tool_block.rb +112 -0
- data/lib/reviewer/setup.rb +41 -0
- data/lib/reviewer/shell/result.rb +14 -15
- data/lib/reviewer/shell/timer.rb +40 -35
- data/lib/reviewer/shell.rb +41 -12
- data/lib/reviewer/tool/conversions.rb +20 -0
- data/lib/reviewer/tool/file_resolver.rb +54 -0
- data/lib/reviewer/tool/settings.rb +88 -44
- data/lib/reviewer/tool/test_file_mapper.rb +73 -0
- data/lib/reviewer/tool/timing.rb +78 -0
- data/lib/reviewer/tool.rb +88 -69
- data/lib/reviewer/tools.rb +47 -33
- data/lib/reviewer/version.rb +1 -1
- data/lib/reviewer.rb +109 -50
- data/reviewer.gemspec +16 -19
- metadata +101 -142
- data/lib/reviewer/conversions.rb +0 -16
- data/lib/reviewer/guidance.rb +0 -77
- data/lib/reviewer/keywords/git/staged.rb +0 -64
- data/lib/reviewer/keywords/git.rb +0 -14
- data/lib/reviewer/keywords.rb +0 -9
- data/lib/reviewer/loader.rb +0 -59
- data/lib/reviewer/output/scrubber.rb +0 -48
- data/lib/reviewer/output/token.rb +0 -85
|
@@ -8,81 +8,125 @@ module Reviewer
|
|
|
8
8
|
|
|
9
9
|
alias key tool_key
|
|
10
10
|
|
|
11
|
-
# Creates an instance of settings for retrieving values from the configuration file
|
|
11
|
+
# Creates an instance of settings for retrieving values from the configuration file
|
|
12
12
|
# @param tool_key [Symbol] the unique identifier for the tool in the config file
|
|
13
|
-
# @param config
|
|
13
|
+
# @param config [Hash] the configuration values to examine for the settings
|
|
14
14
|
#
|
|
15
|
-
# @return [
|
|
16
|
-
def initialize(tool_key, config:
|
|
15
|
+
# @return [Settings]
|
|
16
|
+
def initialize(tool_key, config:)
|
|
17
17
|
@tool_key = tool_key.to_sym
|
|
18
|
-
@config = config
|
|
18
|
+
@config = config
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
# Returns a hash code for comparing settings instances
|
|
22
|
+
#
|
|
23
|
+
# @return [Integer] hash code based on configuration state
|
|
24
|
+
def hash = state.hash
|
|
24
25
|
|
|
26
|
+
# Compares two settings instances for equality based on their configuration
|
|
27
|
+
# @param other [Settings] the settings to compare against
|
|
28
|
+
# @return [Boolean] true if both have the same configuration
|
|
25
29
|
def eql?(other)
|
|
26
30
|
self.class == other.class &&
|
|
27
31
|
state == other.state
|
|
28
32
|
end
|
|
29
33
|
alias :== eql?
|
|
30
34
|
|
|
31
|
-
def
|
|
32
|
-
config.
|
|
35
|
+
def skip_in_batch?
|
|
36
|
+
if config.key?(:skip_in_batch)
|
|
37
|
+
config.fetch(:skip_in_batch) { false }
|
|
38
|
+
else
|
|
39
|
+
config.fetch(:disabled) { false }
|
|
40
|
+
end
|
|
33
41
|
end
|
|
34
42
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
end
|
|
43
|
+
def disabled? = skip_in_batch?
|
|
44
|
+
def enabled? = !skip_in_batch?
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
# The human-readable name of the tool
|
|
47
|
+
#
|
|
48
|
+
# @return [String] the configured name or capitalized tool key
|
|
49
|
+
def name = config.fetch(:name) { tool_key.to_s.capitalize }
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
# The human-readable description of what the tool does
|
|
52
|
+
#
|
|
53
|
+
# @return [String] the configured description or a default placeholder
|
|
54
|
+
def description = config.fetch(:description) { "(No description provided for '#{name}')" }
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
# The tags used to categorize and filter the tool
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<String>] configured tags or empty array
|
|
59
|
+
def tags = config.fetch(:tags) { [] }
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
# The collection of reference links for the tool (home, install, usage, etc.)
|
|
62
|
+
#
|
|
63
|
+
# @return [Hash] configured links or empty hash if none
|
|
64
|
+
def links = config.fetch(:links) { {} }
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
# The environment variables to set when running the tool
|
|
67
|
+
#
|
|
68
|
+
# @return [Hash] configured env vars or empty hash
|
|
69
|
+
def env = config.fetch(:env) { {} }
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
# The CLI flags to pass to the tool's review command
|
|
72
|
+
#
|
|
73
|
+
# @return [Hash] configured flags or empty hash
|
|
74
|
+
def flags = config.fetch(:flags) { {} }
|
|
75
|
+
|
|
76
|
+
# The CLI flag used to pass files to the tool (e.g., '--files')
|
|
77
|
+
#
|
|
78
|
+
# @return [String] the configured flag or empty string if files are passed directly
|
|
79
|
+
def files_flag = config.dig(:files, :flag) || ''
|
|
80
|
+
|
|
81
|
+
# The separator used to join multiple file paths in the command
|
|
82
|
+
#
|
|
83
|
+
# @return [String] the configured separator or a space by default
|
|
84
|
+
def files_separator = config.dig(:files, :separator) || ' '
|
|
85
|
+
|
|
86
|
+
# The glob pattern used to filter which files this tool should process
|
|
87
|
+
#
|
|
88
|
+
# @return [String, nil] the pattern (e.g., '*.rb') or nil if not configured
|
|
89
|
+
def files_pattern = config.dig(:files, :pattern)
|
|
90
|
+
|
|
91
|
+
# The test framework to use for mapping source files to test files
|
|
92
|
+
#
|
|
93
|
+
# @return [String, nil] the framework name ('minitest' or 'rspec') or nil if not configured
|
|
94
|
+
def map_to_tests = config.dig(:files, :map_to_tests)
|
|
95
|
+
|
|
96
|
+
def supports_files? = config.key?(:files)
|
|
97
|
+
|
|
98
|
+
# The regex pattern for extracting a summary detail from tool output
|
|
99
|
+
#
|
|
100
|
+
# @return [String, nil] the configured pattern or nil
|
|
101
|
+
def summary_pattern = config.dig(:summary, :pattern)
|
|
102
|
+
|
|
103
|
+
# The label template for displaying the extracted summary detail
|
|
104
|
+
#
|
|
105
|
+
# @return [String, nil] the configured label or nil
|
|
106
|
+
def summary_label = config.dig(:summary, :label)
|
|
107
|
+
|
|
108
|
+
# Returns the file-scoped command override for a given command type.
|
|
109
|
+
# When configured, this command replaces the standard command when files are passed.
|
|
110
|
+
#
|
|
111
|
+
# @param command_type [Symbol] the command type (:review, :format)
|
|
112
|
+
# @return [String, nil] the file-scoped command or nil if not configured
|
|
113
|
+
def files_command(command_type) = config.dig(:files, command_type)
|
|
62
114
|
|
|
63
115
|
# The collection of configured commands for the tool
|
|
64
116
|
#
|
|
65
117
|
# @return [Hash] all of the commands configured for the tool
|
|
66
|
-
def commands
|
|
67
|
-
config.fetch(:commands) { {} }
|
|
68
|
-
end
|
|
118
|
+
def commands = config.fetch(:commands) { {} }
|
|
69
119
|
|
|
70
120
|
# The largest exit status that can still be considered a success for the command
|
|
71
121
|
#
|
|
72
122
|
# @return [Integer] the configured `max_exit_status` for the tool or 0 if one isn't configured
|
|
73
|
-
def max_exit_status
|
|
74
|
-
commands.fetch(:max_exit_status, 0)
|
|
75
|
-
end
|
|
123
|
+
def max_exit_status = commands.fetch(:max_exit_status) { 0 }
|
|
76
124
|
|
|
77
125
|
protected
|
|
78
126
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def load_config
|
|
84
|
-
Reviewer.tools.to_h.fetch(key) { {} }
|
|
85
|
-
end
|
|
127
|
+
# Returns the configuration as a plain hash for comparison
|
|
128
|
+
# @return [Hash] the configuration state
|
|
129
|
+
def state = config.to_hash
|
|
86
130
|
end
|
|
87
131
|
end
|
|
88
132
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reviewer
|
|
4
|
+
class Tool
|
|
5
|
+
# Maps source files to their corresponding test files based on framework conventions.
|
|
6
|
+
class TestFileMapper
|
|
7
|
+
FRAMEWORKS = {
|
|
8
|
+
minitest: { dir: 'test', suffix: '_test.rb', source_dirs: %w[app lib] },
|
|
9
|
+
rspec: { dir: 'spec', suffix: '_spec.rb', source_dirs: %w[app lib] }
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
# Creates a mapper for the specified test framework
|
|
13
|
+
# @param framework [Symbol, String, nil] the test framework (:minitest or :rspec)
|
|
14
|
+
#
|
|
15
|
+
# @return [TestFileMapper] a mapper instance for the framework
|
|
16
|
+
def initialize(framework)
|
|
17
|
+
@framework = framework&.to_sym
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Maps source files to their corresponding test files
|
|
21
|
+
# @param files [Array<String>] source files to map
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<String>] mapped test files (only those that exist on disk)
|
|
24
|
+
def map(files)
|
|
25
|
+
return files unless supported?
|
|
26
|
+
|
|
27
|
+
files.map { |file| map_file(file) }.compact.uniq
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Checks if the framework is supported for mapping
|
|
31
|
+
#
|
|
32
|
+
# @return [Boolean] true if the framework is :minitest or :rspec
|
|
33
|
+
def supported?
|
|
34
|
+
@framework && FRAMEWORKS.key?(@framework)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def map_file(file)
|
|
40
|
+
return file if test_file?(file)
|
|
41
|
+
|
|
42
|
+
mapped = source_to_test(file)
|
|
43
|
+
mapped && File.exist?(mapped) ? mapped : nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_file?(file)
|
|
47
|
+
file.end_with?(config[:suffix])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def source_to_test(file)
|
|
51
|
+
return nil unless file.end_with?('.rb')
|
|
52
|
+
|
|
53
|
+
replace_source_dir_with_test_dir(file).sub(/\.rb$/, config[:suffix])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def replace_source_dir_with_test_dir(path)
|
|
57
|
+
config[:source_dirs].each do |dir|
|
|
58
|
+
return path.sub(%r{^#{dir}/}, "#{config[:dir]}/") if path.start_with?("#{dir}/")
|
|
59
|
+
end
|
|
60
|
+
prepend_test_dir(path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def prepend_test_dir(path)
|
|
64
|
+
dir = config[:dir]
|
|
65
|
+
path.start_with?(dir) ? path : "#{dir}/#{path}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def config
|
|
69
|
+
FRAMEWORKS[@framework]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module Reviewer
|
|
6
|
+
class Tool
|
|
7
|
+
# Manages timing persistence for a tool — recording, retrieving, and averaging
|
|
8
|
+
# execution times, plus tracking when the prepare command was last run.
|
|
9
|
+
class Timing
|
|
10
|
+
SIX_HOURS_IN_SECONDS = 60 * 60 * 6
|
|
11
|
+
|
|
12
|
+
# Creates a timing tracker for a specific tool
|
|
13
|
+
# @param history [History] the persistence store for timing data
|
|
14
|
+
# @param key [Symbol] the tool's unique key
|
|
15
|
+
#
|
|
16
|
+
# @return [Timing]
|
|
17
|
+
def initialize(history, key)
|
|
18
|
+
@history = history
|
|
19
|
+
@key = key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Specifies when the tool last had its `prepare` command run
|
|
23
|
+
#
|
|
24
|
+
# @return [Time, nil] timestamp of when the `prepare` command was last run
|
|
25
|
+
def last_prepared_at
|
|
26
|
+
date_string = @history.get(@key, :last_prepared_at).to_s
|
|
27
|
+
|
|
28
|
+
date_string.empty? ? nil : DateTime.parse(date_string).to_time
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Sets the timestamp for when the tool last ran its `prepare` command
|
|
32
|
+
# @param timestamp [DateTime, Time] the value to record
|
|
33
|
+
#
|
|
34
|
+
# @return [void]
|
|
35
|
+
def last_prepared_at=(timestamp)
|
|
36
|
+
@history.set(@key, :last_prepared_at, timestamp.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Calculates the average execution time for a command
|
|
40
|
+
# @param command [Command] the command to get timing for
|
|
41
|
+
#
|
|
42
|
+
# @return [Float] the average time in seconds or 0 if no history
|
|
43
|
+
def average_time(command)
|
|
44
|
+
times = get_timing(command)
|
|
45
|
+
|
|
46
|
+
times.any? ? times.sum / times.size : 0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Retrieves historical timing data for a command
|
|
50
|
+
# @param command [Command] the command to look up
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Float>] the last few recorded execution times
|
|
53
|
+
def get_timing(command)
|
|
54
|
+
@history.get(@key, command.raw_string) || []
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Records the execution time for a command to calculate running averages
|
|
58
|
+
# @param command [Command] the command that was run
|
|
59
|
+
# @param time [Float, nil] the execution time in seconds
|
|
60
|
+
#
|
|
61
|
+
# @return [void]
|
|
62
|
+
def record_timing(command, time)
|
|
63
|
+
return unless time
|
|
64
|
+
|
|
65
|
+
timing = get_timing(command).take(4) << time.round(2)
|
|
66
|
+
|
|
67
|
+
@history.set(@key, command.raw_string, timing)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Determines whether the `prepare` command was run recently enough
|
|
71
|
+
#
|
|
72
|
+
# @return [Boolean] true if the timestamp is nil or older than six hours
|
|
73
|
+
def stale?
|
|
74
|
+
!last_prepared_at || last_prepared_at < Time.now - SIX_HOURS_IN_SECONDS
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/reviewer/tool.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative 'tool/conversions'
|
|
4
|
+
require_relative 'tool/file_resolver'
|
|
5
5
|
require_relative 'tool/settings'
|
|
6
|
+
require_relative 'tool/test_file_mapper'
|
|
7
|
+
require_relative 'tool/timing'
|
|
6
8
|
|
|
7
9
|
module Reviewer
|
|
8
10
|
# Provides an instance of a specific tool for accessing its settings and run history
|
|
@@ -10,14 +12,9 @@ module Reviewer
|
|
|
10
12
|
extend Forwardable
|
|
11
13
|
include Comparable
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
# used by some tools to retrieve data, it only runs it occasionally in order to save time.
|
|
15
|
-
# This is the default window that it uses to determine if the tool's preparation step should be
|
|
16
|
-
# considered stale and needs to be rerun. Frequent enough that it shouldn't get stale, but
|
|
17
|
-
# infrequent enough that it's not cumbersome.
|
|
18
|
-
SIX_HOURS_IN_SECONDS = 60 * 60 * 6
|
|
15
|
+
SIX_HOURS_IN_SECONDS = Timing::SIX_HOURS_IN_SECONDS
|
|
19
16
|
|
|
20
|
-
attr_reader :settings
|
|
17
|
+
attr_reader :settings
|
|
21
18
|
|
|
22
19
|
def_delegators :@settings,
|
|
23
20
|
:key,
|
|
@@ -29,98 +26,86 @@ module Reviewer
|
|
|
29
26
|
:links,
|
|
30
27
|
:enabled?,
|
|
31
28
|
:disabled?,
|
|
32
|
-
:
|
|
29
|
+
:skip_in_batch?,
|
|
30
|
+
:max_exit_status,
|
|
31
|
+
:supports_files?
|
|
32
|
+
|
|
33
|
+
# Returns the tool's key as a symbol
|
|
34
|
+
# @return [Symbol] the tool's unique identifier
|
|
35
|
+
def to_sym = key
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
# Returns the tool's name as a string
|
|
38
|
+
# @return [String] the tool's display name
|
|
39
|
+
def to_s = name
|
|
36
40
|
|
|
37
41
|
# Create an instance of a tool
|
|
38
42
|
# @param tool_key [Symbol] the key to the tool from the configuration file
|
|
43
|
+
# @param config [Hash] the tool's configuration hash
|
|
44
|
+
# @param history [History] the history store for timing and state persistence
|
|
39
45
|
#
|
|
40
46
|
# @return [Tool] an instance of tool for accessing settings information and facts about the tool
|
|
41
|
-
def initialize(tool_key)
|
|
42
|
-
@settings = Settings.new(tool_key)
|
|
47
|
+
def initialize(tool_key, config:, history:)
|
|
48
|
+
@settings = Settings.new(tool_key, config: config)
|
|
49
|
+
@history = history
|
|
50
|
+
@timing = Timing.new(history, key)
|
|
43
51
|
end
|
|
44
52
|
|
|
45
|
-
# For determining if the tool should run
|
|
46
|
-
# the tool has a preparation command
|
|
53
|
+
# For determining if the tool should run its preparation command. It will only be run if
|
|
54
|
+
# the tool has a preparation command and it hasn't been run in the last 6 hours
|
|
47
55
|
#
|
|
48
56
|
# @return [Boolean] true if the tool has a configured `prepare` command that hasn't been run in
|
|
49
57
|
# the last 6 hours
|
|
50
|
-
def prepare?
|
|
51
|
-
preparable? && stale?
|
|
52
|
-
end
|
|
58
|
+
def prepare? = preparable? && stale?
|
|
53
59
|
|
|
54
60
|
# Determines whether a tool has a specific command type configured
|
|
55
61
|
# @param command_type [Symbol] one of the available command types defined in Command::TYPES
|
|
56
62
|
#
|
|
57
63
|
# @return [Boolean] true if the command type is configured and not blank
|
|
58
64
|
def command?(command_type)
|
|
59
|
-
commands.key?(command_type) &&
|
|
65
|
+
commands.key?(command_type) && commands[command_type]
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
# Determines if the tool can run a `install` command
|
|
63
69
|
#
|
|
64
70
|
# @return [Boolean] true if there is a non-blank `install` command configured
|
|
65
|
-
def installable?
|
|
66
|
-
|
|
71
|
+
def installable? = command?(:install)
|
|
72
|
+
|
|
73
|
+
# Returns the install command string for this tool
|
|
74
|
+
#
|
|
75
|
+
# @return [String, nil] the install command or nil if not configured
|
|
76
|
+
def install_command
|
|
77
|
+
commands[:install]
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
# Determines if the tool can run a `prepare` command
|
|
70
81
|
#
|
|
71
82
|
# @return [Boolean] true if there is a non-blank `prepare` command configured
|
|
72
|
-
def preparable?
|
|
73
|
-
command?(:prepare)
|
|
74
|
-
end
|
|
83
|
+
def preparable? = command?(:prepare)
|
|
75
84
|
|
|
76
85
|
# Determines if the tool can run a `review` command
|
|
77
86
|
#
|
|
78
87
|
# @return [Boolean] true if there is a non-blank `review` command configured
|
|
79
|
-
def reviewable?
|
|
80
|
-
command?(:review)
|
|
81
|
-
end
|
|
88
|
+
def reviewable? = command?(:review)
|
|
82
89
|
|
|
83
90
|
# Determines if the tool can run a `format` command
|
|
84
91
|
#
|
|
85
92
|
# @return [Boolean] true if there is a non-blank `format` command configured
|
|
86
|
-
def formattable?
|
|
87
|
-
command?(:format)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Specifies when the tool last had it's `prepare` command run
|
|
91
|
-
#
|
|
92
|
-
# @return [Time] timestamp of when the `prepare` command was last run
|
|
93
|
-
def last_prepared_at
|
|
94
|
-
date_string = Reviewer.history.get(key, :last_prepared_at)
|
|
95
|
-
|
|
96
|
-
date_string == '' || date_string.nil? ? nil : DateTime.parse(date_string).to_time
|
|
97
|
-
end
|
|
93
|
+
def formattable? = command?(:format)
|
|
98
94
|
|
|
99
|
-
#
|
|
100
|
-
# @param last_prepared_at [DateTime] the value to record for when the `prepare` command last ran
|
|
95
|
+
# Whether this tool matches any of the given tags and is eligible for batch runs
|
|
101
96
|
#
|
|
102
|
-
# @
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
# @param tag_list [Array<String, Symbol>] tags to match against
|
|
98
|
+
# @return [Boolean] true if the tool is batch-eligible and shares at least one tag
|
|
99
|
+
def matches_tags?(tag_list)
|
|
100
|
+
!skip_in_batch? && tag_list.intersect?(tags)
|
|
105
101
|
end
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def get_timing(command)
|
|
114
|
-
Reviewer.history.get(key, command.raw_string) || []
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def record_timing(command, time)
|
|
118
|
-
return if time.nil?
|
|
119
|
-
|
|
120
|
-
timing = get_timing(command).take(4) << time.round(2)
|
|
121
|
-
|
|
122
|
-
Reviewer.history.set(key, command.raw_string, timing)
|
|
123
|
-
end
|
|
103
|
+
def_delegators :@timing,
|
|
104
|
+
:last_prepared_at,
|
|
105
|
+
:last_prepared_at=,
|
|
106
|
+
:average_time,
|
|
107
|
+
:get_timing,
|
|
108
|
+
:record_timing
|
|
124
109
|
|
|
125
110
|
# Determines whether the `prepare` command was run recently enough
|
|
126
111
|
#
|
|
@@ -129,22 +114,18 @@ module Reviewer
|
|
|
129
114
|
def stale?
|
|
130
115
|
return false unless preparable?
|
|
131
116
|
|
|
132
|
-
|
|
117
|
+
@timing.stale?
|
|
133
118
|
end
|
|
134
119
|
|
|
135
120
|
# Convenience method for determining if a tool has a configured install link
|
|
136
121
|
#
|
|
137
122
|
# @return [Boolean] true if there is an `install` key under links and the value isn't blank
|
|
138
|
-
def install_link?
|
|
139
|
-
links.key?(:install) && !links[:install].nil?
|
|
140
|
-
end
|
|
123
|
+
def install_link? = links.key?(:install) && !!links[:install]
|
|
141
124
|
|
|
142
125
|
# Returns the text for the install link if available
|
|
143
126
|
#
|
|
144
127
|
# @return [String, nil] the link if it exists, nil otherwise
|
|
145
|
-
def install_link
|
|
146
|
-
install_link? ? links.fetch(:install) : nil
|
|
147
|
-
end
|
|
128
|
+
def install_link = install_link? ? links.fetch(:install) : nil
|
|
148
129
|
|
|
149
130
|
# Determines if two tools are equal
|
|
150
131
|
# @param other [Tool] the tool to compare to the current instance
|
|
@@ -154,5 +135,43 @@ module Reviewer
|
|
|
154
135
|
settings == other.settings
|
|
155
136
|
end
|
|
156
137
|
alias :== eql?
|
|
138
|
+
|
|
139
|
+
# Records the pass/fail status and failed files from a result into history
|
|
140
|
+
# @param result [Runner::Result] the result of running this tool
|
|
141
|
+
#
|
|
142
|
+
# @return [void]
|
|
143
|
+
def record_run(result)
|
|
144
|
+
status = result.success? ? :passed : :failed
|
|
145
|
+
@history.set(key, :last_status, status)
|
|
146
|
+
|
|
147
|
+
if result.success?
|
|
148
|
+
@history.set(key, :last_failed_files, nil)
|
|
149
|
+
else
|
|
150
|
+
files = Runner::FailedFiles.new(result.stdout, result.stderr).to_a
|
|
151
|
+
@history.set(key, :last_failed_files, files) if files.any?
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Resolves which files this tool should process
|
|
156
|
+
# @param files [Array<String>] the input files to resolve
|
|
157
|
+
#
|
|
158
|
+
# @return [Array<String>] files after mapping and filtering
|
|
159
|
+
def resolve_files(files)
|
|
160
|
+
file_resolver.resolve(files)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Determines if this tool should be skipped because files were requested but none match
|
|
164
|
+
# @param files [Array<String>] the requested files
|
|
165
|
+
#
|
|
166
|
+
# @return [Boolean] true if files were requested but none match after resolution
|
|
167
|
+
def skip_files?(files)
|
|
168
|
+
file_resolver.skip?(files)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def file_resolver
|
|
174
|
+
@file_resolver ||= FileResolver.new(settings)
|
|
175
|
+
end
|
|
157
176
|
end
|
|
158
177
|
end
|
data/lib/reviewer/tools.rb
CHANGED
|
@@ -7,37 +7,39 @@ module Reviewer
|
|
|
7
7
|
include Enumerable
|
|
8
8
|
|
|
9
9
|
# Provides an instance to work with for knowing which tools to run in a given context.
|
|
10
|
-
# @param tags
|
|
11
|
-
# @param tool_names
|
|
10
|
+
# @param tags [Array] the tags to use to filter tools for a run
|
|
11
|
+
# @param tool_names [Array<String>] the explicitly provided tool names to filter tools for a run
|
|
12
|
+
# @param arguments [Arguments] the parsed CLI arguments
|
|
13
|
+
# @param history [History] the history store for status persistence
|
|
14
|
+
# @param config_file [Pathname, nil] path to the .reviewer.yml configuration file
|
|
12
15
|
#
|
|
13
16
|
# @return [Reviewer::Tools] collection of tools based on the current run context
|
|
14
|
-
def initialize(tags: nil, tool_names: nil)
|
|
15
|
-
@tags
|
|
16
|
-
@tool_names
|
|
17
|
+
def initialize(tags: nil, tool_names: nil, arguments: nil, history: nil, config_file: nil)
|
|
18
|
+
@tags = tags
|
|
19
|
+
@tool_names = tool_names
|
|
20
|
+
@arguments = arguments
|
|
21
|
+
@history = history
|
|
22
|
+
@config_file = config_file
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
# The current state of all available configured tools regardless of whether they are disabled
|
|
20
26
|
#
|
|
21
27
|
# @return [Hash] hash representing all of the configured tools
|
|
22
|
-
def to_h
|
|
23
|
-
configured
|
|
24
|
-
end
|
|
28
|
+
def to_h = configured
|
|
25
29
|
alias inspect to_h
|
|
26
30
|
|
|
27
31
|
# Provides a collection of all configured tools instantiated as Tool instances
|
|
28
32
|
#
|
|
29
33
|
# @return [Array<Tool>] the full collection of all Tool instances
|
|
30
34
|
def all
|
|
31
|
-
configured.
|
|
35
|
+
configured.map { |tool_name, config| Tool.new(tool_name, config: config, history: @history) }
|
|
32
36
|
end
|
|
33
37
|
alias to_a all
|
|
34
38
|
|
|
35
|
-
# Provides a collection of all
|
|
39
|
+
# Provides a collection of all tools that run in the default batch
|
|
36
40
|
#
|
|
37
|
-
# @return [Array<Tool>] the full collection of
|
|
38
|
-
def enabled
|
|
39
|
-
@enabled ||= all.keep_if(&:enabled?)
|
|
40
|
-
end
|
|
41
|
+
# @return [Array<Tool>] the full collection of batch-included Tool instances
|
|
42
|
+
def enabled = @enabled ||= all.reject(&:skip_in_batch?)
|
|
41
43
|
|
|
42
44
|
# Provides a collection of all explicitly-specified-via-command-line tools as Tool instances
|
|
43
45
|
#
|
|
@@ -54,13 +56,22 @@ module Reviewer
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
# Uses the full context of a run to provide the filtered subset of tools to use. It takes into
|
|
57
|
-
# consideration: tagged tools, explicitly-specified tools,
|
|
58
|
-
# any other relevant details that should influence whether a specific
|
|
59
|
-
# of the current batch being executed.
|
|
59
|
+
# consideration: tagged tools, explicitly-specified tools, failed tools, configuration
|
|
60
|
+
# (enabled/disabled), and any other relevant details that should influence whether a specific
|
|
61
|
+
# tool should be run as part of the current batch being executed.
|
|
60
62
|
#
|
|
61
63
|
# @return [Array<Tool>] the full collection of should-be-used-for-this-run tools
|
|
62
64
|
def current
|
|
63
|
-
|
|
65
|
+
return enabled unless subset? || failed_keyword?
|
|
66
|
+
|
|
67
|
+
(specified + tagged + failed).uniq
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns tools that failed in the previous run based on history
|
|
71
|
+
#
|
|
72
|
+
# @return [Array<Tool>] tools with :last_status of :failed in history
|
|
73
|
+
def failed_from_history
|
|
74
|
+
all.select { |tool| @history.get(tool.key, :last_status) == :failed }
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
private
|
|
@@ -70,28 +81,31 @@ module Reviewer
|
|
|
70
81
|
# only a subset of relevant tools.
|
|
71
82
|
#
|
|
72
83
|
# @return [Boolean] true if any tool names or tags are provided via the command line
|
|
73
|
-
def subset?
|
|
74
|
-
tool_names.any? || tags.any?
|
|
75
|
-
end
|
|
84
|
+
def subset? = tool_names.any? || tags.any?
|
|
76
85
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
end
|
|
86
|
+
def failed
|
|
87
|
+
return [] unless failed_keyword?
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
Array(@tags || Reviewer.arguments.tags)
|
|
89
|
+
failed_from_history
|
|
83
90
|
end
|
|
84
91
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
def failed_keyword? = @arguments&.keywords&.failed? || false
|
|
93
|
+
|
|
94
|
+
def configured = @configured ||= Configuration::Loader.configuration(file: @config_file)
|
|
95
|
+
def tags = Array(@tags || matching_tags)
|
|
96
|
+
def tool_names = Array(@tool_names || matching_tool_names)
|
|
97
|
+
def tagged?(tool) = tool.matches_tags?(tags)
|
|
98
|
+
def named?(tool) = tool_names.map(&:to_s).include?(tool.key.to_s)
|
|
88
99
|
|
|
89
|
-
def
|
|
90
|
-
|
|
100
|
+
def matching_tool_names
|
|
101
|
+
provided = @arguments&.keywords&.provided || []
|
|
102
|
+
provided & configured.keys.map(&:to_s)
|
|
91
103
|
end
|
|
92
104
|
|
|
93
|
-
def
|
|
94
|
-
|
|
105
|
+
def matching_tags
|
|
106
|
+
provided = @arguments&.keywords&.provided || []
|
|
107
|
+
all_tags = enabled.flat_map(&:tags).uniq
|
|
108
|
+
(provided & all_tags) + Array(@arguments&.tags&.raw)
|
|
95
109
|
end
|
|
96
110
|
end
|
|
97
111
|
end
|