reviewer 0.1.1 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.alexignore +1 -0
  3. data/.flayignore +1 -0
  4. data/.github/workflows/main.yml +14 -4
  5. data/.gitignore +5 -0
  6. data/.inch.yml +4 -0
  7. data/.reviewer.example.yml +63 -0
  8. data/.reviewer.future.yml +221 -0
  9. data/.reviewer.yml +140 -0
  10. data/.reviewer_stdout +0 -0
  11. data/.rubocop.yml +20 -0
  12. data/CHANGELOG.md +38 -3
  13. data/Gemfile +2 -4
  14. data/Gemfile.lock +103 -20
  15. data/LICENSE.txt +4 -20
  16. data/README.md +23 -29
  17. data/Rakefile +5 -5
  18. data/bin/console +4 -4
  19. data/exe/fmt +7 -0
  20. data/exe/rvw +7 -0
  21. data/lib/reviewer/arguments/files.rb +94 -0
  22. data/lib/reviewer/arguments/keywords.rb +133 -0
  23. data/lib/reviewer/arguments/tags.rb +71 -0
  24. data/lib/reviewer/arguments.rb +54 -10
  25. data/lib/reviewer/batch.rb +91 -0
  26. data/lib/reviewer/command/string/env.rb +44 -0
  27. data/lib/reviewer/command/string/flags.rb +51 -0
  28. data/lib/reviewer/command/string.rb +66 -0
  29. data/lib/reviewer/command.rb +75 -0
  30. data/lib/reviewer/configuration.rb +32 -7
  31. data/lib/reviewer/conversions.rb +16 -0
  32. data/lib/reviewer/guidance.rb +77 -0
  33. data/lib/reviewer/history.rb +69 -0
  34. data/lib/reviewer/keywords/git/staged.rb +64 -0
  35. data/lib/reviewer/keywords/git.rb +14 -0
  36. data/lib/reviewer/keywords.rb +9 -0
  37. data/lib/reviewer/loader.rb +36 -9
  38. data/lib/reviewer/output/printer.rb +44 -0
  39. data/lib/reviewer/output/scrubber.rb +48 -0
  40. data/lib/reviewer/output/token.rb +85 -0
  41. data/lib/reviewer/output.rb +122 -0
  42. data/lib/reviewer/runner/strategies/captured.rb +157 -0
  43. data/lib/reviewer/runner/strategies/passthrough.rb +63 -0
  44. data/lib/reviewer/runner.rb +131 -0
  45. data/lib/reviewer/shell/result.rb +84 -0
  46. data/lib/reviewer/shell/timer.rb +72 -0
  47. data/lib/reviewer/shell.rb +54 -0
  48. data/lib/reviewer/tool/settings.rb +38 -19
  49. data/lib/reviewer/tool.rb +137 -23
  50. data/lib/reviewer/tools.rb +87 -8
  51. data/lib/reviewer/version.rb +1 -1
  52. data/lib/reviewer.rb +107 -28
  53. data/reviewer.gemspec +34 -19
  54. data/structure.svg +1 -0
  55. metadata +244 -12
  56. data/bin/review +0 -5
  57. data/lib/reviewer/tool/command.rb +0 -78
  58. data/lib/reviewer/tool/env.rb +0 -39
  59. data/lib/reviewer/tool/flags.rb +0 -39
  60. data/lib/reviewer/tool/verbosity.rb +0 -39
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewer
4
+ module Keywords
5
+ module Git
6
+ # Provides a convenient interface to get the list of staged files via Git
7
+ class Staged
8
+ OPTIONS = [
9
+ 'diff',
10
+ '--staged',
11
+ '--name-only'
12
+ ].freeze
13
+
14
+ attr_reader :stdout, :stderr, :status, :exit_status
15
+
16
+ def to_a
17
+ stdout.strip.empty? ? [] : stdout.split("\n")
18
+ end
19
+
20
+ # Gets the list of staged files
21
+ #
22
+ # @example Get the list of files
23
+ # staged.list #=> ['/Code/example.rb', '/Code/run.rb']
24
+ #
25
+ # @return [Array<String>] the array of staged filenames as strings
26
+ def list
27
+ @stdout, @stderr, @status = Open3.capture3(command)
28
+ @exit_status = @status.exitstatus.to_i
29
+
30
+ @status.success? ? to_a : raise_command_line_error
31
+ end
32
+
33
+ # Convenience method for retrieving the list of staged files since there's no parameters
34
+ # for an initializer.
35
+ #
36
+ # @example Get the list of files
37
+ # Reviewer::Keywords::Git::Staged.list #=> ['/Code/example.rb', '/Code/run.rb']
38
+ #
39
+ # @return [Array<String>] the array of staged filenames as strings
40
+ def self.list
41
+ new.list
42
+ end
43
+
44
+ # Assembles the pieces of the command that gets the list of staged files
45
+ #
46
+ # @return [String] the full command to run to retrieve the list of staged files
47
+ def command
48
+ command_parts.join(' ')
49
+ end
50
+
51
+ private
52
+
53
+ def raise_command_line_error
54
+ message = "Git Error: #{stderr} (#{command})"
55
+ raise SystemCallError.new(message, exit_status)
56
+ end
57
+
58
+ def command_parts
59
+ BASE_COMMAND + OPTIONS
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'git/staged'
4
+
5
+ module Reviewer
6
+ module Keywords
7
+ module Git
8
+ BASE_COMMAND = [
9
+ 'git',
10
+ '--no-pager'
11
+ ].freeze
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'keywords/git'
4
+
5
+ module Keywords
6
+ # Grouping for individual commands
7
+ module Git
8
+ end
9
+ end
@@ -1,32 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
- require "active_support/core_ext/hash/indifferent_access"
3
+ require 'yaml'
5
4
 
6
- # Provides a collection of the configured tools
7
5
  module Reviewer
6
+ # Provides a collection of the configured tools
8
7
  class Loader
9
8
  class MissingConfigurationError < StandardError; end
9
+
10
10
  class InvalidConfigurationError < StandardError; end
11
11
 
12
- attr_reader :configuration
12
+ class MissingReviewCommandError < StandardError; end
13
+
14
+ attr_reader :configuration, :file
15
+
16
+ def initialize(file = Reviewer.configuration.file)
17
+ @file = file
18
+ @configuration = configuration_hash
13
19
 
14
- def initialize
15
- @configuration = HashWithIndifferentAccess.new(configuration_hash)
20
+ validate_configuration!
16
21
  end
17
22
 
18
23
  def to_h
19
24
  configuration
20
25
  end
21
26
 
27
+ def self.configuration
28
+ new.configuration
29
+ end
30
+
22
31
  private
23
32
 
33
+ def validate_configuration!
34
+ # Any additional guidance for configuration issues will live here
35
+ require_review_commands!
36
+ end
37
+
38
+ def require_review_commands!
39
+ configuration.each do |key, value|
40
+ commands = value[:commands]
41
+
42
+ next if commands.key?(:review)
43
+
44
+ # Ideally, folks would want to fill out everything to receive the most benefit,
45
+ # but realistically, the 'review' command is the only required value. If the key
46
+ # is missing, or maybe there was a typo, fail right away.
47
+ raise MissingReviewCommandError, "'#{key}' does not have a 'review' key under 'commands' in `#{file}`"
48
+ end
49
+ end
50
+
24
51
  def configuration_hash
25
- @configuration_hash ||= YAML.load_file(Reviewer.configuration.file)
52
+ @configuration_hash ||= Psych.safe_load_file(@file, symbolize_names: true)
26
53
  rescue Errno::ENOENT
27
- raise MissingConfigurationError, "Tools configuration file couldn't be found - #{Reviewer.configuration.file}"
54
+ raise MissingConfigurationError, "Tools configuration file couldn't be found at `#{file}`"
28
55
  rescue Psych::SyntaxError => e
29
- raise InvalidConfigurationError, "Tools configuration file has a syntax error - #{e.message}"
56
+ raise InvalidConfigurationError, "Tools configuration file (#{file}) has a syntax error: #{e.message}"
30
57
  end
31
58
  end
32
59
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console' # For determining console width/height
4
+
5
+ module Reviewer
6
+ class Output
7
+ # Wrapper to encapsulate some lower-level details of printing to $stdout
8
+ class Printer
9
+ attr_reader :stream
10
+
11
+ # Creates an instance of Output to print Reviewer activity and results to the console
12
+ def initialize(stream = $stdout)
13
+ @stream = stream.tap do |str|
14
+ # If the IO channel supports flushing the output immediately, then ensure it's enabled
15
+ str.sync = str.respond_to?(:sync=)
16
+ end
17
+ end
18
+
19
+ def print(style, content)
20
+ text(style, content)
21
+ end
22
+
23
+ def puts(style, content)
24
+ text(style, content)
25
+ stream.puts
26
+ end
27
+
28
+ def tty?
29
+ stream.tty?
30
+ end
31
+ alias style_enabled? tty?
32
+
33
+ private
34
+
35
+ def text(style, content)
36
+ if style_enabled?
37
+ stream.print Token.new(style, content).to_s
38
+ else
39
+ stream.print content
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -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
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
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'
8
+
9
+ module Reviewer
10
+ # Friendly API for printing nicely-formatted output to the console
11
+ class Output
12
+ DEFAULT_CONSOLE_WIDTH = 120
13
+ DIVIDER = '·'
14
+
15
+ attr_reader :printer
16
+
17
+ # Creates an instance of Output to print Reviewer activity and results to the console
18
+ def initialize(printer = Printer.new)
19
+ @printer = printer
20
+ end
21
+
22
+ def clear
23
+ system('clear')
24
+ end
25
+
26
+ def newline
27
+ printer.puts(:default, '')
28
+ end
29
+
30
+ def divider
31
+ newline
32
+ printer.print(:muted, DIVIDER * console_width)
33
+ end
34
+
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)
41
+ end
42
+
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
53
+
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}")
62
+ end
63
+
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))
72
+ end
73
+
74
+ def success(timer)
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
80
+ end
81
+
82
+ def failure(details, command: nil)
83
+ printer.print(:failure, 'Failure')
84
+ printer.puts(:muted, " #{details}")
85
+
86
+ return if command.nil?
87
+
88
+ newline
89
+ printer.puts(:bold, 'Failed Command:')
90
+ printer.puts(:muted, String(command))
91
+ end
92
+
93
+ def unrecoverable(details)
94
+ printer.puts(:error, 'Unrecoverable Error:')
95
+ printer.puts(:muted, details)
96
+ end
97
+
98
+ def guidance(summary, details)
99
+ return if details.nil?
100
+
101
+ newline
102
+ printer.puts(:bold, summary)
103
+ printer.puts(:muted, details)
104
+ end
105
+
106
+ def unfiltered(value)
107
+ return if value.nil? || value.strip.empty?
108
+
109
+ printer.stream << value
110
+ end
111
+
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
120
+ end
121
+ end
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
@@ -0,0 +1,63 @@
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 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.
16
+ # @param runner [Runner] the instance of the runner to apply the strategy to
17
+ #
18
+ # @return [self]
19
+ def initialize(runner)
20
+ @runner = runner
21
+ end
22
+
23
+ # The prepare command strategy when running a command transparently
24
+ #
25
+ # @return [void]
26
+ def prepare
27
+ # Running the prepare command, so make sure the timestamp is updated
28
+ runner.update_last_prepared_at
29
+
30
+ # Display the exact command syntax that's being run. This can come in handy if there's an
31
+ # issue and the command can be copied/pasted or if the generated command somehow has some
32
+ # incorrect syntax or options that need to be corrected.
33
+ runner.output.current_command(runner.prepare_command)
34
+
35
+ # Add a divider to visually delineate the results
36
+ runner.output.divider
37
+
38
+ # Run the command through the shell directly so no output is suppressed
39
+ runner.shell.direct(runner.prepare_command)
40
+ end
41
+
42
+ # The run command strategy when running a command transparently
43
+ #
44
+ # @return [void]
45
+ def run
46
+ # Display the exact command syntax that's being run. This can come in handy if there's an
47
+ # issue and the command can be copied/pasted or if the generated command somehow has some
48
+ # incorrect syntax or options that need to be corrected.
49
+ runner.output.current_command(runner.command)
50
+
51
+ # Add a divider to visually delineate the results
52
+ runner.output.divider
53
+
54
+ # Run the command through the shell directly so no output is suppressed
55
+ runner.shell.direct(runner.command)
56
+
57
+ # Add a final divider to visually delineate the results
58
+ runner.output.divider
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end