reviewer 0.1.4 → 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/.github/workflows/main.yml +5 -3
- data/.reviewer.example.yml +20 -10
- data/.reviewer.future.yml +221 -0
- data/.reviewer.yml +45 -8
- data/.reviewer_stdout +0 -0
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +29 -28
- data/LICENSE.txt +4 -20
- data/README.md +26 -7
- data/lib/reviewer/arguments/files.rb +14 -8
- data/lib/reviewer/arguments/keywords.rb +5 -2
- data/lib/reviewer/arguments/tags.rb +11 -4
- data/lib/reviewer/arguments.rb +11 -4
- data/lib/reviewer/batch.rb +41 -14
- data/lib/reviewer/command/string/env.rb +4 -0
- data/lib/reviewer/command/string/flags.rb +12 -1
- data/lib/reviewer/command/string.rb +12 -18
- data/lib/reviewer/command.rb +7 -32
- data/lib/reviewer/configuration.rb +8 -1
- data/lib/reviewer/conversions.rb +0 -11
- data/lib/reviewer/guidance.rb +9 -5
- data/lib/reviewer/history.rb +32 -1
- data/lib/reviewer/keywords/git/staged.rb +16 -0
- 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 +73 -43
- data/lib/reviewer/runner/strategies/captured.rb +157 -0
- data/lib/reviewer/runner/strategies/{verbose.rb → passthrough.rb} +13 -13
- data/lib/reviewer/runner.rb +71 -13
- data/lib/reviewer/shell/result.rb +22 -7
- data/lib/reviewer/shell/timer.rb +15 -0
- data/lib/reviewer/shell.rb +7 -11
- data/lib/reviewer/tool/settings.rb +12 -5
- data/lib/reviewer/tool.rb +25 -3
- data/lib/reviewer/version.rb +1 -1
- data/lib/reviewer.rb +11 -10
- data/reviewer.gemspec +9 -5
- data/structure.svg +1 -0
- metadata +34 -28
- data/.ruby-version +0 -1
- data/lib/reviewer/command/string/verbosity.rb +0 -51
- data/lib/reviewer/command/verbosity.rb +0 -65
- data/lib/reviewer/printer.rb +0 -25
- data/lib/reviewer/runner/strategies/quiet.rb +0 -90
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
class Output
|
5
|
+
# Provides a structure interface for the results of running a command
|
6
|
+
class Scrubber
|
7
|
+
# A lot of tools are run via rake which inclues some unhelpful drive when there's a non-zero
|
8
|
+
# exit status. This is what it starts with so Reviewer can recognize and remove it.
|
9
|
+
RAKE_ABORTED_TEXT = <<~DRIVEL
|
10
|
+
rake aborted!
|
11
|
+
DRIVEL
|
12
|
+
|
13
|
+
attr_accessor :raw
|
14
|
+
|
15
|
+
# Creates a scrubber instance for the provided text content
|
16
|
+
# @param raw [String] the text to be scrubbed of unhelpful content
|
17
|
+
#
|
18
|
+
# @return [self]
|
19
|
+
def initialize(raw)
|
20
|
+
@raw = raw || ''
|
21
|
+
end
|
22
|
+
|
23
|
+
def clean
|
24
|
+
rake_aborted_text? ? preceding_text : raw
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def rake_aborted_text?
|
30
|
+
raw.include?(RAKE_ABORTED_TEXT)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Removes any unhelpful rake exit status details from $stderr. Reviewew uses `exit` when a
|
34
|
+
# command fails so that the resulting command-line exit status can be interpreted correctly
|
35
|
+
# in CI and similar environments. Without that exit status, those environments wouldn't
|
36
|
+
# recognize the failure. As a result, Rake almost always adds noise that begins with the value
|
37
|
+
# in RAKE_EXIT_DRIVEL when `exit` is called. Frequently, that RAKE_EXIT_DRIVEL is the only
|
38
|
+
# information in $stderr, and it's not helpful in the human-readable output, but other times
|
39
|
+
# when a valid exception occurs, there's useful error information preceding RAKE_EXIT_DRIVEL.
|
40
|
+
# So this ensures that the unhelpful part is always removed so the output is cluttered with
|
41
|
+
# red herrings since the command is designed to fail with an exit status of 1 under normal
|
42
|
+
# operation with tool failures.
|
43
|
+
def preceding_text
|
44
|
+
raw.split(RAKE_ABORTED_TEXT).first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
class Output
|
5
|
+
# Simple class for streamlining the output of 'tokens' representing a style and content
|
6
|
+
#
|
7
|
+
# @author [garrettdimon]
|
8
|
+
#
|
9
|
+
class Token
|
10
|
+
ESC = "\e["
|
11
|
+
|
12
|
+
attr_accessor :style, :content
|
13
|
+
|
14
|
+
def initialize(style, content)
|
15
|
+
@style = style
|
16
|
+
@content = content
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
[
|
21
|
+
style_string,
|
22
|
+
content,
|
23
|
+
reset_string
|
24
|
+
].join
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def style_string
|
30
|
+
"#{ESC}#{weight};#{color}m"
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset_string
|
34
|
+
"#{ESC}0m"
|
35
|
+
end
|
36
|
+
|
37
|
+
def weight_key
|
38
|
+
style_components[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
def color_key
|
42
|
+
style_components[1]
|
43
|
+
end
|
44
|
+
|
45
|
+
def weight
|
46
|
+
{
|
47
|
+
default: 0,
|
48
|
+
bold: 1,
|
49
|
+
light: 2,
|
50
|
+
italic: 3
|
51
|
+
}.fetch(weight_key)
|
52
|
+
end
|
53
|
+
|
54
|
+
def color
|
55
|
+
{
|
56
|
+
black: 30,
|
57
|
+
red: 31,
|
58
|
+
green: 32,
|
59
|
+
yellow: 33,
|
60
|
+
blue: 34,
|
61
|
+
magenta: 35,
|
62
|
+
cyan: 36,
|
63
|
+
gray: 37,
|
64
|
+
default: 39
|
65
|
+
}.fetch(color_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def style_components
|
69
|
+
{
|
70
|
+
success_bold: %i[bold green],
|
71
|
+
success: %i[default green],
|
72
|
+
success_light: %i[light green],
|
73
|
+
error: %i[bold red],
|
74
|
+
failure: %i[default red],
|
75
|
+
warning: %i[bold yellow],
|
76
|
+
warning_light: %i[light yellow],
|
77
|
+
source: %i[italic default],
|
78
|
+
bold: %i[default default],
|
79
|
+
default: %i[default default],
|
80
|
+
muted: %i[light gray]
|
81
|
+
}.fetch(style)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/reviewer/output.rb
CHANGED
@@ -1,92 +1,122 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'io/console/size' # For determining console width/height
|
4
|
+
|
5
|
+
require_relative 'output/printer'
|
6
|
+
require_relative 'output/scrubber'
|
7
|
+
require_relative 'output/token'
|
4
8
|
|
5
9
|
module Reviewer
|
6
10
|
# Friendly API for printing nicely-formatted output to the console
|
7
11
|
class Output
|
8
|
-
|
9
|
-
|
10
|
-
DIVIDER = ('-' * 60).to_s
|
12
|
+
DEFAULT_CONSOLE_WIDTH = 120
|
13
|
+
DIVIDER = '·'
|
11
14
|
|
12
15
|
attr_reader :printer
|
13
16
|
|
14
|
-
|
17
|
+
# Creates an instance of Output to print Reviewer activity and results to the console
|
18
|
+
def initialize(printer = Printer.new)
|
15
19
|
@printer = printer
|
16
20
|
end
|
17
21
|
|
18
|
-
def
|
19
|
-
|
22
|
+
def clear
|
23
|
+
system('clear')
|
20
24
|
end
|
21
25
|
|
22
|
-
def
|
23
|
-
printer.
|
26
|
+
def newline
|
27
|
+
printer.puts(:default, '')
|
24
28
|
end
|
25
29
|
|
26
30
|
def divider
|
27
|
-
|
28
|
-
printer.
|
29
|
-
blank_line
|
31
|
+
newline
|
32
|
+
printer.print(:muted, DIVIDER * console_width)
|
30
33
|
end
|
31
34
|
|
32
|
-
|
33
|
-
|
35
|
+
# Prints plain text to the console
|
36
|
+
# @param message [String] the text to write to the console
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def help(message)
|
40
|
+
printer.puts(:default, message)
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
|
-
|
43
|
+
# Prints a summary of the total time and results for a batch run. If multiple tools, it will
|
44
|
+
# show the total tool count
|
45
|
+
# @param tool_count [Integer] the number of commands run in the batch
|
46
|
+
# @param seconds [Float] the total number of seconds the batch ran in realtime
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
def batch_summary(tool_count, seconds)
|
50
|
+
printer.print(:bold, "~#{seconds.round(1)} seconds")
|
51
|
+
printer.puts(:muted, " for #{tool_count} tools") if tool_count > 1
|
52
|
+
end
|
38
53
|
|
39
|
-
|
40
|
-
|
54
|
+
# Print a tool summary using the name and description. Used before running a command to help
|
55
|
+
# identify which tool is running at any given moment.
|
56
|
+
# @param tool [Tool] the tool to identify and describe
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
def tool_summary(tool)
|
60
|
+
printer.print(:bold, tool.name)
|
61
|
+
printer.puts(:muted, " #{tool.description}")
|
41
62
|
end
|
42
63
|
|
43
|
-
|
44
|
-
|
64
|
+
# Prints the text of a command to the console to help proactively expose potentials issues with
|
65
|
+
# syntax if Reviewer translated thte provided options in an unexpected way
|
66
|
+
# @param command [String, Command] the command to identify on the console
|
67
|
+
#
|
68
|
+
# @return [void] [description]
|
69
|
+
def current_command(command)
|
70
|
+
printer.puts(:bold, 'Now Running:')
|
71
|
+
printer.puts(:default, String(command))
|
45
72
|
end
|
46
73
|
|
47
74
|
def success(timer)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
75
|
+
printer.print(:success, 'Success')
|
76
|
+
printer.print(:success_light, " #{timer.total_seconds}s")
|
77
|
+
printer.print(:warning_light, " (#{timer.prep_percent}% prep ~#{timer.prep_seconds}s)") if timer.prepped?
|
78
|
+
newline
|
79
|
+
newline
|
52
80
|
end
|
53
81
|
|
54
82
|
def failure(details, command: nil)
|
55
|
-
printer.
|
83
|
+
printer.print(:failure, 'Failure')
|
84
|
+
printer.puts(:muted, " #{details}")
|
56
85
|
|
57
86
|
return if command.nil?
|
58
87
|
|
59
|
-
|
60
|
-
printer.
|
61
|
-
printer.
|
88
|
+
newline
|
89
|
+
printer.puts(:bold, 'Failed Command:')
|
90
|
+
printer.puts(:muted, String(command))
|
62
91
|
end
|
63
92
|
|
64
93
|
def unrecoverable(details)
|
65
|
-
printer.error 'Unrecoverable Error:'
|
66
|
-
printer.
|
94
|
+
printer.puts(:error, 'Unrecoverable Error:')
|
95
|
+
printer.puts(:muted, details)
|
67
96
|
end
|
68
97
|
|
69
98
|
def guidance(summary, details)
|
70
99
|
return if details.nil?
|
71
100
|
|
72
|
-
|
73
|
-
printer.
|
74
|
-
printer.
|
101
|
+
newline
|
102
|
+
printer.puts(:bold, summary)
|
103
|
+
printer.puts(:muted, details)
|
75
104
|
end
|
76
105
|
|
77
|
-
def
|
78
|
-
|
79
|
-
installation_command = Command.new(tool, :install, :no_silence).string if tool.installable?
|
80
|
-
install_link = tool.install_link
|
106
|
+
def unfiltered(value)
|
107
|
+
return if value.nil? || value.strip.empty?
|
81
108
|
|
82
|
-
|
83
|
-
guidance('Try installing the tool:', installation_command)
|
84
|
-
guidance('Read the installation guidance:', install_link)
|
109
|
+
printer.stream << value
|
85
110
|
end
|
86
111
|
|
87
|
-
|
88
|
-
|
89
|
-
|
112
|
+
protected
|
113
|
+
|
114
|
+
def console_width
|
115
|
+
return DEFAULT_CONSOLE_WIDTH if IO.console.nil?
|
116
|
+
|
117
|
+
_height, width = IO.console.winsize
|
118
|
+
|
119
|
+
width
|
90
120
|
end
|
91
121
|
end
|
92
122
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reviewer
|
4
|
+
class Runner
|
5
|
+
module Strategies
|
6
|
+
# Execution strategy for a runner to run a command quietly by capturing the output and only
|
7
|
+
# displaying it if there's a failure that justifies it
|
8
|
+
# @attr_reader runner [Runner] the instance of the runner that will be executed with this strategy
|
9
|
+
# @attr_reader start_time [Time] the start time for the strategy_for timing purposes
|
10
|
+
class Captured
|
11
|
+
attr_reader :runner, :start_time
|
12
|
+
|
13
|
+
# Create an instance of the captured strategy for a command runner so that any output is
|
14
|
+
# fully suppressed so as to not create too much noise when running multiple commands.
|
15
|
+
# @param runner [Runner] the instance of the runner to apply the strategy to
|
16
|
+
#
|
17
|
+
# @return [self]
|
18
|
+
def initialize(runner)
|
19
|
+
@runner = runner
|
20
|
+
@start_time = Time.now
|
21
|
+
end
|
22
|
+
|
23
|
+
# The prepare command strategy when running a command and capturing the results
|
24
|
+
#
|
25
|
+
# @return [void]
|
26
|
+
def prepare
|
27
|
+
command = runner.prepare_command
|
28
|
+
|
29
|
+
display_progress(command) { runner.shell.capture_prep(command) }
|
30
|
+
|
31
|
+
# Running the prepare command, so make sure the timestamp is updated
|
32
|
+
runner.update_last_prepared_at
|
33
|
+
end
|
34
|
+
|
35
|
+
# The run command strategy when running a command and capturing the results
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
def run
|
39
|
+
command = runner.command
|
40
|
+
|
41
|
+
display_progress(command) { runner.shell.capture_main(command) }
|
42
|
+
|
43
|
+
# If it's successful, show that it was a success and how long it took to run, otherwise,
|
44
|
+
# it wasn't successful and we got some explaining to do...
|
45
|
+
runner.success? ? show_timing_result : show_command_output
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Displays the progress of the current command since the output is captured/suppressed.
|
51
|
+
# Helps people know that the sub-command is running within expectations.
|
52
|
+
# @param command [String] the precise command string generated by Reviewer. Serves as the
|
53
|
+
# key for looking up the command's history.
|
54
|
+
# @param &block [Block] the runner for the command that's being timed and having its
|
55
|
+
# progress updated and printed
|
56
|
+
#
|
57
|
+
# @return [void]
|
58
|
+
def display_progress(command, &block) # rubocop:disable Metrics/AbcSize
|
59
|
+
start_time = Time.now
|
60
|
+
average_time = runner.tool.average_time(command)
|
61
|
+
|
62
|
+
thread = Thread.new { block.call }
|
63
|
+
|
64
|
+
while thread.alive?
|
65
|
+
elapsed = (Time.now - start_time).to_f.round(1)
|
66
|
+
progress = if average_time.zero?
|
67
|
+
"#{elapsed}s"
|
68
|
+
else
|
69
|
+
"~#{((elapsed / average_time) * 100).round}%"
|
70
|
+
end
|
71
|
+
|
72
|
+
$stdout.print "> #{progress} \r"
|
73
|
+
$stdout.flush
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Determines if stdout or stderr captured any useful output that can be displayed in order
|
78
|
+
# to more rapidly display output when a command fails. As long as both aren't nil or
|
79
|
+
# otherwise 'blank' strings, then that's enough.
|
80
|
+
#
|
81
|
+
# @return [Boolean] true if either stdout or stderr contain printable content
|
82
|
+
def usable_output_captured?
|
83
|
+
[runner.stdout, runner.stderr].reject { |value| value.nil? || value.strip.empty? }.any?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Prints "Success" and the resulting timing details before moving on to the next tool
|
87
|
+
#
|
88
|
+
# @return [void]
|
89
|
+
def show_timing_result
|
90
|
+
runner.record_timing
|
91
|
+
runner.output.success(runner.timer)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Prints "Failure" and the resulting exit status. Shows the precise command that led to the
|
95
|
+
# failure for easier copy and paste or making it easier to see any incorrect syntax or
|
96
|
+
# options that could be corrected.
|
97
|
+
#
|
98
|
+
# @return [void]
|
99
|
+
def show_command_output
|
100
|
+
# If there's a failure, clear the successful command output to focus on the issues
|
101
|
+
runner.output.clear
|
102
|
+
|
103
|
+
# Show the exit status and failed command
|
104
|
+
runner.output.failure("Exit Status #{runner.exit_status}", command: runner.command)
|
105
|
+
|
106
|
+
# If it can't be rerun, then don't try
|
107
|
+
usable_output_captured? ? show_captured_output : rerun_via_passthrough
|
108
|
+
end
|
109
|
+
|
110
|
+
# If the command sent output to stdout/stderr as most will, simply display what was captured
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
def show_captured_output
|
114
|
+
show_captured_stdout
|
115
|
+
show_captured_stderr
|
116
|
+
end
|
117
|
+
|
118
|
+
# If there's a useful stdout value, display it with a divider to visually separate it.
|
119
|
+
#
|
120
|
+
# @return [void]
|
121
|
+
def show_captured_stdout # rubocop:disable Metrics/AbcSize
|
122
|
+
return if runner.stdout.nil? || runner.stdout.empty?
|
123
|
+
|
124
|
+
runner.output.divider
|
125
|
+
runner.output.newline
|
126
|
+
runner.output.unfiltered(runner.stdout)
|
127
|
+
end
|
128
|
+
|
129
|
+
# If there's a useful stderr value, display it with a divider to visually separate it.
|
130
|
+
#
|
131
|
+
# @return [void]
|
132
|
+
def show_captured_stderr # rubocop:disable Metrics/AbcSize
|
133
|
+
return if runner.stderr.nil? || runner.stderr.empty?
|
134
|
+
|
135
|
+
scrubbed_stderr = Reviewer::Output::Scrubber.new(runner.stderr).clean
|
136
|
+
|
137
|
+
runner.output.divider
|
138
|
+
runner.output.newline
|
139
|
+
runner.output.guidance('Runtime Errors:', scrubbed_stderr)
|
140
|
+
end
|
141
|
+
|
142
|
+
# If for some reason, the command didn't send anything to stdout/stderr, the only option to
|
143
|
+
# show results is to rerun it via the passthrough strategy
|
144
|
+
#
|
145
|
+
# @return [void]
|
146
|
+
def rerun_via_passthrough
|
147
|
+
return unless runner.rerunnable?
|
148
|
+
|
149
|
+
runner.strategy = Strategies::Passthrough
|
150
|
+
|
151
|
+
runner.output.divider
|
152
|
+
runner.run
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -3,22 +3,24 @@
|
|
3
3
|
module Reviewer
|
4
4
|
class Runner
|
5
5
|
module Strategies
|
6
|
-
# Execution strategy to run a command
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
# Execution strategy for a runner to run a command transparently-displaying the command's
|
7
|
+
# output in realtime as it runs (as opposed to capturing and suppressing the output unless
|
8
|
+
# the command fails)
|
9
|
+
# @attr_reader runner [Runner] the instance of the runner that will be executed with this strategy
|
10
|
+
class Passthrough
|
11
|
+
attr_reader :runner
|
12
|
+
|
13
|
+
# Create an instance of the passthrough strategy for a command runner. This strategy ensures
|
14
|
+
# that when a command is run, the output isn't suppressed. Essentially, it's a transparent
|
15
|
+
# wrapper for running a command and displaying the results realtime.
|
13
16
|
# @param runner [Runner] the instance of the runner to apply the strategy to
|
14
17
|
#
|
15
|
-
# @return [
|
18
|
+
# @return [self]
|
16
19
|
def initialize(runner)
|
17
20
|
@runner = runner
|
18
|
-
@runner.command.verbosity = Reviewer::Command::Verbosity::NO_SILENCE
|
19
21
|
end
|
20
22
|
|
21
|
-
# The prepare command strategy when running a command
|
23
|
+
# The prepare command strategy when running a command transparently
|
22
24
|
#
|
23
25
|
# @return [void]
|
24
26
|
def prepare
|
@@ -37,12 +39,10 @@ module Reviewer
|
|
37
39
|
runner.shell.direct(runner.prepare_command)
|
38
40
|
end
|
39
41
|
|
40
|
-
# The run command strategy when running a command
|
42
|
+
# The run command strategy when running a command transparently
|
41
43
|
#
|
42
44
|
# @return [void]
|
43
45
|
def run
|
44
|
-
# Display the exact command that's being run
|
45
|
-
|
46
46
|
# Display the exact command syntax that's being run. This can come in handy if there's an
|
47
47
|
# issue and the command can be copied/pasted or if the generated command somehow has some
|
48
48
|
# incorrect syntax or options that need to be corrected.
|
data/lib/reviewer/runner.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'runner/strategies/
|
4
|
-
require_relative 'runner/strategies/
|
3
|
+
require_relative 'runner/strategies/captured'
|
4
|
+
require_relative 'runner/strategies/passthrough'
|
5
5
|
|
6
6
|
module Reviewer
|
7
7
|
# Wrapper for executng a command and printing the results
|
@@ -14,9 +14,19 @@ module Reviewer
|
|
14
14
|
|
15
15
|
def_delegators :@command, :tool
|
16
16
|
def_delegators :@shell, :result, :timer
|
17
|
-
def_delegators :result, :exit_status
|
17
|
+
def_delegators :result, :exit_status, :stdout, :stderr, :rerunnable?
|
18
18
|
|
19
|
-
|
19
|
+
# Creates a wrapper for running commansd through Reviewer in order to provide a more accessible
|
20
|
+
# API for recording execution time and interpreting the results of a command in a more
|
21
|
+
# generous way so that non-zero exit statuses can still potentiall be passing.
|
22
|
+
# @param tool [Symbol] the key for the desired tool to run
|
23
|
+
# @param command_type [Symbol] the key for the type of command to run
|
24
|
+
# @param strategy = Strategies::Captured [Runner::Strategies] how to execute and handle the
|
25
|
+
# results of the command
|
26
|
+
# @param output: Reviewer.output [Review::Output] the output formatter for the results
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
def initialize(tool, command_type, strategy = Strategies::Captured, output: Reviewer.output)
|
20
30
|
@command = Command.new(tool, command_type)
|
21
31
|
@strategy = strategy
|
22
32
|
@shell = Shell.new
|
@@ -24,18 +34,16 @@ module Reviewer
|
|
24
34
|
end
|
25
35
|
|
26
36
|
def run
|
27
|
-
# Show which tool is
|
28
|
-
|
37
|
+
# Show which tool is running
|
38
|
+
identify_tool
|
29
39
|
|
30
|
-
#
|
31
|
-
|
32
|
-
run_strategy.prepare if run_prepare_step?
|
33
|
-
run_strategy.run
|
34
|
-
end
|
40
|
+
# Use the provided strategy to run the command
|
41
|
+
execute_strategy
|
35
42
|
|
36
|
-
# If it failed,
|
43
|
+
# If it failed, display guidance to help them get back on track
|
37
44
|
guidance.show unless success?
|
38
45
|
|
46
|
+
# Return the exit status generated by the tool as interpreted by the Result
|
39
47
|
exit_status
|
40
48
|
end
|
41
49
|
|
@@ -53,19 +61,69 @@ module Reviewer
|
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
64
|
+
# Prints the tool name and description to the console as a frame of reference
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
def identify_tool
|
68
|
+
# If there's an existing result, the runner is being re-run, and identifying the tool would
|
69
|
+
# be redundant.
|
70
|
+
return if result.exists?
|
71
|
+
|
72
|
+
output.tool_summary(tool)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Runs the relevant strategy to either capture or pass through command output.
|
76
|
+
#
|
77
|
+
# @return [void]
|
78
|
+
def execute_strategy
|
79
|
+
# Run the provided strategy
|
80
|
+
strategy.new(self).tap do |run_strategy|
|
81
|
+
run_strategy.prepare if run_prepare_step?
|
82
|
+
run_strategy.run
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Determines whether a preparation step should be run before the primary command. If/when the
|
87
|
+
# primary command is a `:prepare` command, then it shouldn't run twice. So it skips what would
|
88
|
+
# be a superfluous run of the preparation.
|
89
|
+
#
|
90
|
+
# @return [Boolean] true the primary command is not prepare and the tool needs to be prepare
|
56
91
|
def run_prepare_step?
|
57
92
|
command.type != :prepare && tool.prepare?
|
58
93
|
end
|
59
94
|
|
95
|
+
# Creates_an instance of the prepare command for a tool
|
96
|
+
#
|
97
|
+
# @return [Comman] the current tool's prepare command
|
60
98
|
def prepare_command
|
61
|
-
@prepare_command ||= Command.new(tool, :prepare
|
99
|
+
@prepare_command ||= Command.new(tool, :prepare)
|
62
100
|
end
|
63
101
|
|
102
|
+
# Updates the 'last prepared at' timestamp that Reviewer uses to know if a tool's preparation
|
103
|
+
# step is stale and needs to be run again.
|
104
|
+
#
|
105
|
+
# @return [Time] the timestamp `last_prepared_at` is updated to
|
64
106
|
def update_last_prepared_at
|
65
107
|
# Touch the `last_prepared_at` timestamp for the tool so it waits before running again.
|
66
108
|
tool.last_prepared_at = Time.now
|
67
109
|
end
|
68
110
|
|
111
|
+
# Saves the last 5 elapsed times for the commands used this run by using the raw command as a
|
112
|
+
# unique key. This enables the ability to compare times across runs while taking into
|
113
|
+
# consideration that different iterations of the command may be running on fewer files. So
|
114
|
+
# comparing a full run to the average time for a partial run wouldn't be helpful. By using the
|
115
|
+
# raw command string, it will always be apples to apples.
|
116
|
+
#
|
117
|
+
# @return [void]
|
118
|
+
def record_timing
|
119
|
+
tool.record_timing(prepare_command, timer.prep)
|
120
|
+
tool.record_timing(command, timer.main)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Uses the result of the runner to determine what, if any, guidance to display to help the user
|
124
|
+
# get back on track in the event of an unsuccessful run.
|
125
|
+
#
|
126
|
+
# @return [Guidance] the relevant guidance based on the result of the runner
|
69
127
|
def guidance
|
70
128
|
@guidance ||= Reviewer::Guidance.new(command: command, result: result, output: output)
|
71
129
|
end
|