reviewer 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.alexignore +1 -0
  3. data/.github/workflows/main.yml +5 -3
  4. data/.reviewer.example.yml +20 -10
  5. data/.reviewer.future.yml +221 -0
  6. data/.reviewer.yml +45 -8
  7. data/.reviewer_stdout +0 -0
  8. data/.rubocop.yml +1 -0
  9. data/Gemfile.lock +29 -28
  10. data/LICENSE.txt +4 -20
  11. data/README.md +26 -7
  12. data/lib/reviewer/arguments/files.rb +14 -8
  13. data/lib/reviewer/arguments/keywords.rb +5 -2
  14. data/lib/reviewer/arguments/tags.rb +11 -4
  15. data/lib/reviewer/arguments.rb +11 -4
  16. data/lib/reviewer/batch.rb +41 -14
  17. data/lib/reviewer/command/string/env.rb +4 -0
  18. data/lib/reviewer/command/string/flags.rb +12 -1
  19. data/lib/reviewer/command/string.rb +12 -18
  20. data/lib/reviewer/command.rb +7 -32
  21. data/lib/reviewer/configuration.rb +8 -1
  22. data/lib/reviewer/conversions.rb +0 -11
  23. data/lib/reviewer/guidance.rb +9 -5
  24. data/lib/reviewer/history.rb +32 -1
  25. data/lib/reviewer/keywords/git/staged.rb +16 -0
  26. data/lib/reviewer/output/printer.rb +44 -0
  27. data/lib/reviewer/output/scrubber.rb +48 -0
  28. data/lib/reviewer/output/token.rb +85 -0
  29. data/lib/reviewer/output.rb +73 -43
  30. data/lib/reviewer/runner/strategies/captured.rb +157 -0
  31. data/lib/reviewer/runner/strategies/{verbose.rb → passthrough.rb} +13 -13
  32. data/lib/reviewer/runner.rb +71 -13
  33. data/lib/reviewer/shell/result.rb +22 -7
  34. data/lib/reviewer/shell/timer.rb +15 -0
  35. data/lib/reviewer/shell.rb +7 -11
  36. data/lib/reviewer/tool/settings.rb +12 -5
  37. data/lib/reviewer/tool.rb +25 -3
  38. data/lib/reviewer/version.rb +1 -1
  39. data/lib/reviewer.rb +11 -10
  40. data/reviewer.gemspec +9 -5
  41. data/structure.svg +1 -0
  42. metadata +34 -28
  43. data/.ruby-version +0 -1
  44. data/lib/reviewer/command/string/verbosity.rb +0 -51
  45. data/lib/reviewer/command/verbosity.rb +0 -65
  46. data/lib/reviewer/printer.rb +0 -25
  47. 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
@@ -1,92 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'colorize'
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
- SUCCESS = 'Success'
9
- FAILURE = 'Failure ·'
10
- DIVIDER = ('-' * 60).to_s
12
+ DEFAULT_CONSOLE_WIDTH = 120
13
+ DIVIDER = '·'
11
14
 
12
15
  attr_reader :printer
13
16
 
14
- def initialize(printer: Reviewer.configuration.printer)
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 info(message)
19
- printer.info message
22
+ def clear
23
+ system('clear')
20
24
  end
21
25
 
22
- def blank_line
23
- printer.info
26
+ def newline
27
+ printer.puts(:default, '')
24
28
  end
25
29
 
26
30
  def divider
27
- blank_line
28
- printer.info DIVIDER.light_black
29
- blank_line
31
+ newline
32
+ printer.print(:muted, DIVIDER * console_width)
30
33
  end
31
34
 
32
- def tool_summary(tool)
33
- printer.info "\n#{tool.name}".bold + ' · '.light_black + tool.description
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
- def current_command(command)
37
- command = String(command)
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
- printer.info "\nNow Running:"
40
- printer.info command.light_black
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
- def exit_status(value)
44
- failure("Exit Status #{value}")
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
- message = SUCCESS.green.bold + " #{timer.total_seconds}s".green
49
- message += " (#{timer.prep_percent}% preparation)".yellow if timer.prepped?
50
-
51
- printer.info message
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.error "#{FAILURE} #{details}".red.bold
83
+ printer.print(:failure, 'Failure')
84
+ printer.puts(:muted, " #{details}")
56
85
 
57
86
  return if command.nil?
58
87
 
59
- blank_line
60
- printer.error 'Failed Command:'.red.bold
61
- printer.error String(command).light_black
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:'.red.bold
66
- printer.error details
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
- blank_line
73
- printer.info summary
74
- printer.info details.to_s.light_black
101
+ newline
102
+ printer.puts(:bold, summary)
103
+ printer.puts(:muted, details)
75
104
  end
76
105
 
77
- def missing_executable_guidance(command)
78
- tool = command.tool
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
- failure("Missing executable for '#{tool}'", command: command)
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
- def syntax_guidance(ignore_link: nil, disable_link: nil)
88
- guidance('Selectively Ignore a Rule:', ignore_link)
89
- guidance('Fully Disable a Rule:', disable_link)
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 verbosely
7
- class Verbose
8
- attr_accessor :runner
9
-
10
- # Create an instance of the verbose strategy for a command runner. This strategy ensures
11
- # that when a command is run, the output isn't suppressed. Essentially, it's a pass-through
12
- # wrapper for running a command and displaying the results.
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 [Runner::Strategies::Verbose] an instance of the relevant verbose strategy
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 verbosely
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 verbosely
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.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'runner/strategies/quiet'
4
- require_relative 'runner/strategies/verbose'
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
- def initialize(tool, command_type, strategy = Strategies::Quiet, output: Reviewer.output)
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 about to run
28
- output.tool_summary(tool)
37
+ # Show which tool is running
38
+ identify_tool
29
39
 
30
- # Run the provided strategy
31
- strategy.new(self).tap do |run_strategy|
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, command.verbosity)
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