reviewer 0.1.1 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.alexignore +1 -0
- data/.flayignore +1 -0
- data/.github/workflows/main.yml +14 -4
- data/.gitignore +5 -0
- data/.inch.yml +4 -0
- data/.reviewer.example.yml +63 -0
- data/.reviewer.future.yml +221 -0
- data/.reviewer.yml +140 -0
- data/.reviewer_stdout +0 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +38 -3
- data/Gemfile +2 -4
- data/Gemfile.lock +103 -20
- data/LICENSE.txt +4 -20
- data/README.md +23 -29
- data/Rakefile +5 -5
- data/bin/console +4 -4
- data/exe/fmt +7 -0
- data/exe/rvw +7 -0
- data/lib/reviewer/arguments/files.rb +94 -0
- data/lib/reviewer/arguments/keywords.rb +133 -0
- data/lib/reviewer/arguments/tags.rb +71 -0
- data/lib/reviewer/arguments.rb +54 -10
- data/lib/reviewer/batch.rb +91 -0
- data/lib/reviewer/command/string/env.rb +44 -0
- data/lib/reviewer/command/string/flags.rb +51 -0
- data/lib/reviewer/command/string.rb +66 -0
- data/lib/reviewer/command.rb +75 -0
- data/lib/reviewer/configuration.rb +32 -7
- data/lib/reviewer/conversions.rb +16 -0
- data/lib/reviewer/guidance.rb +77 -0
- data/lib/reviewer/history.rb +69 -0
- data/lib/reviewer/keywords/git/staged.rb +64 -0
- data/lib/reviewer/keywords/git.rb +14 -0
- data/lib/reviewer/keywords.rb +9 -0
- data/lib/reviewer/loader.rb +36 -9
- data/lib/reviewer/output/printer.rb +44 -0
- data/lib/reviewer/output/scrubber.rb +48 -0
- data/lib/reviewer/output/token.rb +85 -0
- data/lib/reviewer/output.rb +122 -0
- data/lib/reviewer/runner/strategies/captured.rb +157 -0
- data/lib/reviewer/runner/strategies/passthrough.rb +63 -0
- data/lib/reviewer/runner.rb +131 -0
- data/lib/reviewer/shell/result.rb +84 -0
- data/lib/reviewer/shell/timer.rb +72 -0
- data/lib/reviewer/shell.rb +54 -0
- data/lib/reviewer/tool/settings.rb +38 -19
- data/lib/reviewer/tool.rb +137 -23
- data/lib/reviewer/tools.rb +87 -8
- data/lib/reviewer/version.rb +1 -1
- data/lib/reviewer.rb +107 -28
- data/reviewer.gemspec +34 -19
- data/structure.svg +1 -0
- metadata +244 -12
- data/bin/review +0 -5
- data/lib/reviewer/tool/command.rb +0 -78
- data/lib/reviewer/tool/env.rb +0 -39
- data/lib/reviewer/tool/flags.rb +0 -39
- data/lib/reviewer/tool/verbosity.rb +0 -39
data/lib/reviewer/arguments.rb
CHANGED
@@ -2,43 +2,87 @@
|
|
2
2
|
|
3
3
|
require 'slop'
|
4
4
|
|
5
|
-
|
5
|
+
require_relative 'arguments/keywords'
|
6
|
+
require_relative 'arguments/files'
|
7
|
+
require_relative 'arguments/tags'
|
8
|
+
|
6
9
|
module Reviewer
|
10
|
+
# Handles option parsing for `rvw` and `fmt` commands
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# `rvw`
|
15
|
+
# `rvw -t ruby`
|
16
|
+
# `rvw -f ./example.rb,./example_test.rb`
|
17
|
+
# `rvw staged`
|
18
|
+
# `rvw --files ./example.rb,./example_test.rb --tags syntax`
|
19
|
+
# `rvw ruby staged`
|
20
|
+
#
|
7
21
|
class Arguments
|
8
22
|
attr_accessor :options
|
9
23
|
|
24
|
+
attr_reader :output
|
25
|
+
|
26
|
+
# A catch all for aguments passed to reviewer via the command-line so they can be interpreted
|
27
|
+
# and made available via the relevant classes.
|
28
|
+
# @param options = ARGV [Hash] options to parse and extract the relevant values for a run
|
29
|
+
#
|
30
|
+
# @example Using all options: `rvw keyword_one keyword_two --files ./example.rb,./example_test.rb --tags syntax`
|
31
|
+
# reviewer = Reviewer::Arguments.new
|
32
|
+
# reviewer.files.to_a # => ['./example.rb','./example_test.rb']
|
33
|
+
# reviewer.tags.to_a # => ['syntax']
|
34
|
+
# reviewer.keywords.to_a # => ['keyword_one', 'keyword_two']
|
35
|
+
#
|
36
|
+
# @return [self]
|
10
37
|
def initialize(options = ARGV)
|
38
|
+
@output = Output.new
|
11
39
|
@options = Slop.parse options do |opts|
|
12
40
|
opts.array '-f', '--files', 'a list of comma-separated files or paths', delimiter: ',', default: []
|
13
41
|
opts.array '-t', '--tags', 'a list of comma-separated tags', delimiter: ',', default: []
|
14
42
|
|
15
43
|
opts.on '-v', '--version', 'print the version' do
|
16
|
-
|
44
|
+
@output.help VERSION
|
17
45
|
exit
|
18
46
|
end
|
19
47
|
|
20
48
|
opts.on '-h', '--help', 'print the help' do
|
21
|
-
|
49
|
+
@output.help opts
|
22
50
|
exit
|
23
51
|
end
|
24
52
|
end
|
25
53
|
end
|
26
54
|
|
27
|
-
|
28
|
-
|
55
|
+
# Converts the arguments to a hash for versatility
|
56
|
+
#
|
57
|
+
# @return [Hash] The files, tags, and keywords collected from the command line options
|
58
|
+
def to_h
|
59
|
+
{
|
60
|
+
files: files.raw,
|
61
|
+
tags: tags.raw,
|
62
|
+
keywords: keywords.raw
|
63
|
+
}
|
29
64
|
end
|
65
|
+
alias inspect to_h
|
30
66
|
|
67
|
+
# The tag arguments collected from the command line via the `-t` or `--tags` flag
|
68
|
+
#
|
69
|
+
# @return [Arguments::Tags] an colelction of the tag arguments collected from the command-line
|
31
70
|
def tags
|
32
|
-
options[:tags]
|
71
|
+
@tags ||= Arguments::Tags.new(provided: options[:tags])
|
33
72
|
end
|
34
73
|
|
35
|
-
|
36
|
-
|
74
|
+
# The file arguments collected from the command line via the `-f` or `--files` flag
|
75
|
+
#
|
76
|
+
# @return [Arguments::Files] an collection of the file arguments collected from the command-line
|
77
|
+
def files
|
78
|
+
@files ||= Arguments::Files.new(provided: options[:files])
|
37
79
|
end
|
38
80
|
|
81
|
+
# The leftover arguments collected from the command line without being associated with a flag
|
82
|
+
#
|
83
|
+
# @return [Arguments::Keywords] an collection of the leftover arguments as keywords
|
39
84
|
def keywords
|
40
|
-
|
41
|
-
arguments
|
85
|
+
@keywords ||= Arguments::Keywords.new(options.arguments)
|
42
86
|
end
|
43
87
|
end
|
44
88
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
# Provides a structure for running commands for a given set of tools
|
5
|
+
class Batch
|
6
|
+
class UnrecognizedCommandError < ArgumentError; end
|
7
|
+
|
8
|
+
attr_reader :command_type, :tools, :output, :results
|
9
|
+
|
10
|
+
# Generates an instance of Batch for running multiple tools together
|
11
|
+
# @param command_type [Symbol] the type of command to run for each tool.
|
12
|
+
# @param tools [Array<Tool>] the tools to run the commands for
|
13
|
+
# @param output: Reviewer.output [Output] the output channel to print results to
|
14
|
+
#
|
15
|
+
# @return [self]
|
16
|
+
def initialize(command_type, tools, output: Reviewer.output)
|
17
|
+
@command_type = command_type
|
18
|
+
@tools = tools
|
19
|
+
@output = output
|
20
|
+
@results = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Iterates over the tools in the batch to successfully run the commands. Also times the entire
|
24
|
+
# batch in order to provide a total execution time.
|
25
|
+
#
|
26
|
+
# @return [Results] the results summary for all commands run
|
27
|
+
def run
|
28
|
+
benchmark_batch do
|
29
|
+
matching_tools.each do |tool|
|
30
|
+
# Create and execute a runner for the given tool, command type, and strategy
|
31
|
+
runner = Runner.new(tool, command_type, strategy)
|
32
|
+
runner.run
|
33
|
+
|
34
|
+
# Record the exit status for this tool
|
35
|
+
record_exit_status(runner)
|
36
|
+
|
37
|
+
# If the tool fails, stop running other tools
|
38
|
+
break unless runner.success?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
results
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def multiple_tools?
|
48
|
+
tools.size > 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def strategy
|
52
|
+
multiple_tools? ? Runner::Strategies::Captured : Runner::Strategies::Passthrough
|
53
|
+
end
|
54
|
+
|
55
|
+
# Notes the exit status for the runner based on whether the runner was considered successful or
|
56
|
+
# not based on the configured `max_exit_status` for the tool. For example, some tools use exit
|
57
|
+
# status to convey significance. So even though it returns a non-zero exit status like 2, it
|
58
|
+
# can still be successful.
|
59
|
+
# @param runner [Runner] the instance of the runner that's being inspected
|
60
|
+
#
|
61
|
+
# @return [Integer] the adjusted exit status for the runner
|
62
|
+
def record_exit_status(runner)
|
63
|
+
# Since some tools can "succeed" with a positive exit status, the overall batch is only
|
64
|
+
# interested in subjective failure. So if the runner succeeded according to the tool's max
|
65
|
+
# exit status, it should record the tool's run as a success for the purposes of the larger
|
66
|
+
# batch success/failure
|
67
|
+
@results[runner.tool.key] = runner.success? ? 0 : runner.exit_status
|
68
|
+
end
|
69
|
+
|
70
|
+
# Records and prints the total runtime of a block
|
71
|
+
# @param &block [type] section of code to be timed
|
72
|
+
#
|
73
|
+
# @return [void] prints the elapsed time
|
74
|
+
def benchmark_batch(&block)
|
75
|
+
elapsed_time = Benchmark.realtime(&block)
|
76
|
+
|
77
|
+
# If there's failures, skip showing the total time to focus on the issues
|
78
|
+
return if @results.values.sum.positive?
|
79
|
+
|
80
|
+
output.batch_summary(results.size, elapsed_time)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the set of tools matching the provided command. So when formatting, if a tool does not
|
84
|
+
# have a format command, then it will be skipped.
|
85
|
+
#
|
86
|
+
# @return [Array<Tool>] the enabled tools that support the provided command
|
87
|
+
def matching_tools
|
88
|
+
tools.select { |tool| tool.settings.commands.key?(command_type) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
class Command
|
5
|
+
class String
|
6
|
+
# Assembles tool environment variables into a single string or array
|
7
|
+
class Env
|
8
|
+
attr_reader :env_pairs
|
9
|
+
|
10
|
+
# Creates an instance of env variables for a tool to help generate the command string
|
11
|
+
# @param env_pairs [Hash] [description]
|
12
|
+
#
|
13
|
+
# @return [self]
|
14
|
+
def initialize(env_pairs)
|
15
|
+
@env_pairs = env_pairs
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
to_a.compact.join(' ')
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_a
|
23
|
+
env = []
|
24
|
+
env_pairs.each { |key, value| env << env(key, value) }
|
25
|
+
env
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def env(key, value)
|
31
|
+
return nil if key.to_s.strip.empty? || value.to_s.strip.empty?
|
32
|
+
|
33
|
+
value = needs_quotes?(value) ? "'#{value}'" : value
|
34
|
+
|
35
|
+
"#{key.to_s.strip.upcase}=#{value.to_s.strip}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def needs_quotes?(value)
|
39
|
+
value.is_a?(::String) && value.include?(' ')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
class Command
|
5
|
+
class String
|
6
|
+
# Translates tool flag settings from the tool's configuration values into a single string or
|
7
|
+
# array that can be used to generate the command string
|
8
|
+
class Flags
|
9
|
+
attr_reader :flag_pairs
|
10
|
+
|
11
|
+
# Creates an instance of command-string friendly flags
|
12
|
+
# @param flag_pairs [Hash] the flags (keys) and their values
|
13
|
+
#
|
14
|
+
# @return [self]
|
15
|
+
def initialize(flag_pairs)
|
16
|
+
@flag_pairs = flag_pairs
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates a string-friendly format to use in a command
|
20
|
+
#
|
21
|
+
# @return [String] a string of flags that can be safely passed to a command
|
22
|
+
def to_s
|
23
|
+
to_a.join(' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates an array of all flag name/value pairs
|
27
|
+
#
|
28
|
+
# @return [Array<String>] array of all flag strings to use to when running the command
|
29
|
+
def to_a
|
30
|
+
flags = []
|
31
|
+
flag_pairs.each { |key, value| flags << flag(key, value) }
|
32
|
+
flags
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def flag(key, value)
|
38
|
+
dash = key.to_s.size == 1 ? '-' : '--'
|
39
|
+
|
40
|
+
value = needs_quotes?(value) ? "'#{value}'" : value
|
41
|
+
|
42
|
+
"#{dash}#{key} #{value}".strip
|
43
|
+
end
|
44
|
+
|
45
|
+
def needs_quotes?(value)
|
46
|
+
value.is_a?(::String) && value.include?(' ')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'string/env'
|
4
|
+
require_relative 'string/flags'
|
5
|
+
|
6
|
+
module Reviewer
|
7
|
+
class Command
|
8
|
+
# Assembles tool tool_settings into a usable command string for the command type
|
9
|
+
class String
|
10
|
+
include Conversions
|
11
|
+
|
12
|
+
attr_reader :command_type, :tool_settings
|
13
|
+
|
14
|
+
def initialize(command_type, tool_settings:)
|
15
|
+
@command_type = command_type
|
16
|
+
@tool_settings = tool_settings
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
to_a
|
21
|
+
.map(&:strip) # Remove extra spaces on the components
|
22
|
+
.join(' ') # Merge the components
|
23
|
+
.strip # Strip extra spaces from the end result
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_a
|
27
|
+
[
|
28
|
+
env_variables,
|
29
|
+
body,
|
30
|
+
flags
|
31
|
+
].compact
|
32
|
+
end
|
33
|
+
|
34
|
+
# The string of environment variables built from a tool's configuration settings
|
35
|
+
#
|
36
|
+
# @return [String] the environment variable names and values concatened for the command
|
37
|
+
def env_variables
|
38
|
+
Env.new(tool_settings.env).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def body
|
42
|
+
tool_settings.commands.fetch(command_type)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gets the flags to be used in conjunction with the review command for a tool
|
46
|
+
# 1. The `review` commands are the only commands that use flags
|
47
|
+
# 2. If no flags are configured, this won't do anything
|
48
|
+
#
|
49
|
+
# @return [String] the concatenated list of flags to pass to the review command
|
50
|
+
def flags
|
51
|
+
return nil unless flags?
|
52
|
+
|
53
|
+
Flags.new(tool_settings.flags).to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Determines whether the string needs flags added
|
59
|
+
#
|
60
|
+
# @return [Boolean] true if it's a review command and it has flags configured
|
61
|
+
def flags?
|
62
|
+
command_type == :review && tool_settings.flags.any?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'command/string'
|
4
|
+
|
5
|
+
module Reviewer
|
6
|
+
# The core funtionality to translate a tool, command type, and verbosity into a runnable command
|
7
|
+
class Command
|
8
|
+
include Conversions
|
9
|
+
|
10
|
+
SEED_SUBSTITUTION_VALUE = '$SEED'
|
11
|
+
|
12
|
+
attr_reader :tool
|
13
|
+
|
14
|
+
attr_accessor :type
|
15
|
+
|
16
|
+
# Creates an instance of the Command class to synthesize a command string using the tool,
|
17
|
+
# command type, and verbosity.
|
18
|
+
# @param tool [Tool, Symbol] a tool or tool key to use to look up the command and options
|
19
|
+
# @param type [Symbol] the desired command type (:install, :prepare, :review, :format)
|
20
|
+
#
|
21
|
+
# @return [Command] the intersection of a tool, command type, and verbosity
|
22
|
+
def initialize(tool, type)
|
23
|
+
@tool = Tool(tool)
|
24
|
+
@type = type.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
# The final command string with all of the conditions appled
|
28
|
+
#
|
29
|
+
# @return [String] the final, valid command string to run
|
30
|
+
def string
|
31
|
+
@string ||= seed_substitution? ? seeded_string : raw_string
|
32
|
+
end
|
33
|
+
alias to_s string
|
34
|
+
|
35
|
+
# Generates a seed that can be re-used across runs so that the results are consistent across
|
36
|
+
# related runs for tools that would otherwise change the seed automatically every run.
|
37
|
+
# Since not all tools will use the seed, there's no need to generate it in the initializer.
|
38
|
+
# Instead, it's memoized if it's used.
|
39
|
+
#
|
40
|
+
# @return [Integer] a random integer to pass to tools that use seeds
|
41
|
+
def seed
|
42
|
+
@seed ||= Random.rand(100_000)
|
43
|
+
|
44
|
+
# Store the seed for reference
|
45
|
+
Reviewer.history.set(tool.key, :last_seed, @seed)
|
46
|
+
|
47
|
+
@seed
|
48
|
+
end
|
49
|
+
|
50
|
+
# The raw command string before any substitutions. For example, since seeds need to remain
|
51
|
+
# consistent from one run to the next, they're
|
52
|
+
#
|
53
|
+
# @return [type] [description]
|
54
|
+
def raw_string
|
55
|
+
@raw_string ||= String.new(type, tool_settings: tool.settings).to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# The version of the command with the SEED_SUBSTITUTION_VALUE replaced
|
61
|
+
#
|
62
|
+
# @return [String] the command string with the SEED_SUBSTITUTION_VALUE replaced
|
63
|
+
def seeded_string
|
64
|
+
# Update the string with the memoized seed value
|
65
|
+
raw_string.gsub(SEED_SUBSTITUTION_VALUE, seed.to_s)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Determines if the raw command string has a SEED_SUBSTITUTION_VALUE that needs replacing
|
69
|
+
#
|
70
|
+
# @return [Boolean] true if the raw command string contains the SEED_SUBSTITUTION_VALUE
|
71
|
+
def seed_substitution?
|
72
|
+
raw_string.include?(SEED_SUBSTITUTION_VALUE)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,18 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pathname'
|
4
|
+
|
3
5
|
module Reviewer
|
6
|
+
# Configuration values container for Reviewer
|
7
|
+
#
|
8
|
+
# @!attribute file
|
9
|
+
# @return [Pathname] the pathname for the primary configuraton file
|
10
|
+
# @!attribute history_file
|
11
|
+
# @return [Pathname] the pathname for the history file to store data across runs
|
12
|
+
#
|
13
|
+
# @author [garrettdimon]
|
14
|
+
#
|
4
15
|
class Configuration
|
5
|
-
|
6
|
-
|
16
|
+
DEFAULT_PATH = Dir.pwd.freeze
|
17
|
+
|
18
|
+
DEFAULT_CONFIG_FILE_NAME = '.reviewer.yml'
|
19
|
+
DEFAULT_HISTORY_FILE_NAME = '.reviewer_history.yml'
|
7
20
|
|
8
|
-
|
21
|
+
DEFAULT_CONFIG_LOCATION = "#{DEFAULT_PATH}/#{DEFAULT_CONFIG_FILE_NAME}"
|
22
|
+
DEFAULT_HISTORY_LOCATION = "#{DEFAULT_PATH}/#{DEFAULT_HISTORY_FILE_NAME}"
|
23
|
+
|
24
|
+
attr_accessor :file, :history_file, :printer
|
9
25
|
|
10
26
|
def initialize
|
11
|
-
@file =
|
12
|
-
|
27
|
+
@file = Pathname(DEFAULT_CONFIG_LOCATION)
|
28
|
+
@history_file = Pathname(DEFAULT_HISTORY_LOCATION)
|
13
29
|
|
14
|
-
|
15
|
-
|
30
|
+
# Future Configuration Options:
|
31
|
+
# - seed_substitution_value(string): Currently a constant of `$SEED` in Reviewer::Command, but
|
32
|
+
# may need to be configurable in case any command-line strings have other legitimate uses
|
33
|
+
# for the value such that it may need to be override. Ideally, it woudl be changed to3
|
34
|
+
# something obscure enough that conflicts wouldn't happen, but you never know
|
35
|
+
# - benchmark_everything(:dev, :optimize): Use the `time_up` gem to measure and show all the results
|
36
|
+
# for each tool and step to help identify and reduce bottlenecks. It would mainly be a flag
|
37
|
+
# for use in development, but it could also help folks troubleshoot their speed in finer
|
38
|
+
# detail than the standard Reviewer output
|
39
|
+
# - default_preparation_refresh(integer time): Right now, it's hard-coded at 6 hours, but that may require
|
40
|
+
# tuning for individual tools
|
16
41
|
end
|
17
42
|
end
|
18
43
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
# Conversion functions for special types in Reviewer
|
5
|
+
module Conversions
|
6
|
+
def Tool(value) # rubocop:disable Naming/MethodName
|
7
|
+
case value
|
8
|
+
when Tool then value
|
9
|
+
when Symbol then Tool.new(value)
|
10
|
+
when String then Tool.new(value.to_sym)
|
11
|
+
else raise TypeError, "Cannot convert #{value} to Tool"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
module_function :Tool
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml/store'
|
4
|
+
|
5
|
+
module Reviewer
|
6
|
+
# Handles the logic around what to display after a command has been run
|
7
|
+
class Guidance
|
8
|
+
attr_reader :command, :result, :output
|
9
|
+
|
10
|
+
# Create an instance of guidance for suggesting recovery steps after errors
|
11
|
+
# @param command: [Command] the command that was run and needs recovery guidance
|
12
|
+
# @param result: [Result] the result of the command
|
13
|
+
# @param output: Reviewer.output [Output] the output channel for displaying content
|
14
|
+
#
|
15
|
+
# @return [Guidance] the guidance class to suggest relevant recovery steps
|
16
|
+
def initialize(command:, result:, output: Reviewer.output)
|
17
|
+
@command = command
|
18
|
+
@result = result
|
19
|
+
@output = output
|
20
|
+
end
|
21
|
+
|
22
|
+
# Prints the relevant guidance based on the command and result context
|
23
|
+
#
|
24
|
+
# @return [void] prints the relevant guidance to the stream
|
25
|
+
def show
|
26
|
+
case result
|
27
|
+
when executable_not_found? then show_missing_executable_guidance
|
28
|
+
when cannot_execute? then show_unrecoverable_guidance
|
29
|
+
else show_syntax_guidance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Conditional check for when the command result was that the executable couldn't be found
|
36
|
+
#
|
37
|
+
# @return [Boolean] true if the result indicates the command couldn't be found
|
38
|
+
def executable_not_found?
|
39
|
+
->(result) { result.executable_not_found? }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Conditional check for when the command result was that it was unable to be executed
|
43
|
+
#
|
44
|
+
# @return [Boolean] true if the result indicates the command couldn't be executed
|
45
|
+
def cannot_execute?
|
46
|
+
->(result) { result.cannot_execute? }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Shows the recovery guidance for when a command is missing
|
50
|
+
#
|
51
|
+
# @return [void] prints missing executable guidance
|
52
|
+
def show_missing_executable_guidance
|
53
|
+
tool = command.tool
|
54
|
+
installation_command = Command.new(tool, :install).string if tool.installable?
|
55
|
+
install_link = tool.install_link
|
56
|
+
|
57
|
+
output.failure("Missing executable for '#{tool}'", command: command)
|
58
|
+
output.guidance('Try installing the tool:', installation_command)
|
59
|
+
output.guidance('Read the installation guidance:', install_link)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Shows the recovery guidance for when a command generates an unrecoverable error
|
63
|
+
#
|
64
|
+
# @return [void] prints unrecoverable error guidance
|
65
|
+
def show_unrecoverable_guidance
|
66
|
+
output.unrecoverable(result.stderr)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Shows suggestions for ignoring or disable rules when a command fails after reviewing code
|
70
|
+
#
|
71
|
+
# @return [void] prints syntax guidance
|
72
|
+
def show_syntax_guidance
|
73
|
+
output.guidance('Selectively Ignore a Rule:', command.tool.links[:ignore_syntax])
|
74
|
+
output.guidance('Fully Disable a Rule:', command.tool.links[:disable_syntax])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml/store'
|
4
|
+
|
5
|
+
module Reviewer
|
6
|
+
# Provides an interface to a local storage resource for persisting data across runs. For example,
|
7
|
+
# it enables remembering when `prepare` commands were run for reviews so they can be run less
|
8
|
+
# frequently and thus improve performance.
|
9
|
+
#
|
10
|
+
# It also enables remembering seeds across runs. Eventually `rvw rerun` could reuse the seeds from
|
11
|
+
# the immediately preceding run to more easily facilitate fixing tests that are accidentally
|
12
|
+
# order-dependent. Or it could automatically record a list of seeds that led to failures.
|
13
|
+
#
|
14
|
+
# Long term, it could serve to record timing details across runs to provide insight to min, max,
|
15
|
+
# and means. Those times could then be used for reviewer to make more informed decisions about
|
16
|
+
# default behavior to ensure each run remains fast.
|
17
|
+
class History
|
18
|
+
attr_reader :file, :store
|
19
|
+
|
20
|
+
# Creates an instance of a YAML::Store-backed history file.
|
21
|
+
# @param file = Reviewer.configuration.history_file [Pathname] the history file to store data
|
22
|
+
#
|
23
|
+
# @return [History] an instance of history
|
24
|
+
def initialize(file = Reviewer.configuration.history_file)
|
25
|
+
@file = file
|
26
|
+
@store = YAML::Store.new(file)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Saves a value to a given location in the history
|
30
|
+
# @param group [Symbol] the first-level key to use for saving the value--frequently a tool name
|
31
|
+
# @param attribute [Symbol] the second-level key to use for retrieving the value
|
32
|
+
# @param value [Primitive] any value that can be cleanly stored in YAML
|
33
|
+
#
|
34
|
+
# @return [Primitive] the value being stored
|
35
|
+
def set(group, attribute, value)
|
36
|
+
store.transaction do |s|
|
37
|
+
s[group] = {} if s[group].nil?
|
38
|
+
s[group][attribute] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Retrieves a stored value from the history file
|
43
|
+
# @param group [Symbol] the first-level key to use for retrieving the value
|
44
|
+
# @param attribute [Symbol] the second-level key to use for retrieving the value
|
45
|
+
#
|
46
|
+
# @return [Primitive] the value being stored
|
47
|
+
def get(group, attribute)
|
48
|
+
store.transaction do |s|
|
49
|
+
s[group].nil? ? nil : s[group][attribute]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes the existing history file.
|
54
|
+
#
|
55
|
+
# @return [void]
|
56
|
+
def reset!
|
57
|
+
return unless File.exist?(file)
|
58
|
+
|
59
|
+
FileUtils.rm(file)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Convenience class method for removing the history file.
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def self.reset!
|
66
|
+
new.reset!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|