Class: Reviewer::Runner
- Inherits:
-
Object
- Object
- Reviewer::Runner
- Extended by:
- Forwardable
- Defined in:
- lib/reviewer/runner.rb,
lib/reviewer/runner/result.rb,
lib/reviewer/runner/guidance.rb,
lib/reviewer/runner/formatter.rb,
lib/reviewer/runner/failed_files.rb,
lib/reviewer/runner/strategies/captured.rb,
lib/reviewer/runner/strategies/passthrough.rb
Overview
Wrapper for executng a command and printing the results
Defined Under Namespace
Modules: Strategies Classes: FailedFiles, Formatter, Guidance, Result
Instance Attribute Summary collapse
-
#command ⇒ Object
readonly
Returns the value of attribute command.
-
#command_string ⇒ String
readonly
The full command string that was executed.
-
#command_type ⇒ Symbol
readonly
The type of command run (:review, :format, etc.).
-
#duration ⇒ Float
readonly
The execution time in seconds.
-
#exit_status ⇒ Integer
readonly
The exit status code from the command.
-
#missing ⇒ Boolean
readonly
Whether the tool’s executable was not found.
-
#shell ⇒ Object
readonly
Returns the value of attribute shell.
-
#skipped ⇒ Boolean
readonly
Whether the tool was skipped.
-
#stderr ⇒ String?
readonly
The standard error from the command.
-
#stdout ⇒ String?
readonly
The standard output from the command.
-
#strategy ⇒ Class
The strategy class for running the command (Captured or Passthrough).
-
#success ⇒ Boolean
readonly
Whether the command completed successfully.
-
#tool_key ⇒ Symbol
readonly
The unique identifier for the tool.
-
#tool_name ⇒ String
readonly
The human-readable name of the tool.
Instance Method Summary collapse
-
#execute_strategy ⇒ void
Runs the relevant strategy to either capture or pass through command output.
-
#failed_files ⇒ Array<String>
Extracts file paths from stdout/stderr for failed-file tracking.
- #failure? ⇒ Boolean
-
#formatter ⇒ Runner::Formatter
Display formatter for runner-specific output (tool summary, success, failure, etc.) Computed rather than stored to avoid exceeding instance variable threshold.
-
#guidance ⇒ Guidance
Uses the result of the runner to determine what, if any, guidance to display to help the user get back on track in the event of an unsuccessful run.
-
#identify_tool ⇒ void
Prints the tool name and description to the console as a frame of reference.
-
#initialize(tool, command_type, strategy = Strategies::Captured, context:) ⇒ self
constructor
Creates a wrapper for running commands through Reviewer in order to provide a more accessible API for recording execution time and interpreting the results of a command in a more generous way so that non-zero exit statuses can still potentially be passing.
-
#missing? ⇒ Boolean
Whether this runner’s executable was not found (exit status 127).
-
#output ⇒ Output
The output channel for displaying content, delegated from context.
-
#prepare_command ⇒ Comman
Creates_an instance of the prepare command for a tool.
-
#record_timing ⇒ void
Saves the last 5 elapsed times for the commands used this run by using the raw command as a unique key.
-
#run ⇒ Integer
Executes the command and returns the exit status.
-
#run_prepare_step? ⇒ Boolean
Determines whether a preparation step should be run before the primary command.
-
#show_skipped ⇒ void
Shows that a tool was skipped due to no matching files.
-
#skipped? ⇒ Boolean
Whether this runner was skipped due to no matching files.
-
#streaming? ⇒ Boolean
Whether this runner is operating in streaming mode.
-
#success? ⇒ Boolean
Some review tools return a range of non-zero exit statuses and almost never return 0.
-
#to_result ⇒ Runner::Result
Builds an immutable Result object from the current runner state.
-
#update_last_prepared_at ⇒ Time
Updates the ‘last prepared at’ timestamp that Reviewer uses to know if a tool’s preparation step is stale and needs to be run again.
Constructor Details
#initialize(tool, command_type, strategy = Strategies::Captured, context:) ⇒ self
Creates a wrapper for running commands through Reviewer in order to provide a more accessible
API for recording execution time and interpreting the results of a command in a more
generous way so that non-zero exit statuses can still potentially be passing.
34 35 36 37 38 39 40 41 |
# File 'lib/reviewer/runner.rb', line 34 def initialize(tool, command_type, strategy = Strategies::Captured, context:) @command = Command.new(tool, command_type, context: context) @strategy = strategy @shell = Shell.new(stream: context.output.printer.stream) @context = context @skipped = false @missing = false end |
Instance Attribute Details
#command ⇒ Object (readonly)
Returns the value of attribute command.
19 20 21 |
# File 'lib/reviewer/runner.rb', line 19 def command @command end |
#command_string ⇒ String (readonly)
Returns the full command string that was executed.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#command_type ⇒ Symbol (readonly)
Returns the type of command run (:review, :format, etc.).
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#duration ⇒ Float (readonly)
Returns the execution time in seconds.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#exit_status ⇒ Integer (readonly)
Returns the exit status code from the command.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#missing ⇒ Boolean (readonly)
Returns whether the tool’s executable was not found.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#shell ⇒ Object (readonly)
Returns the value of attribute shell.
19 20 21 |
# File 'lib/reviewer/runner.rb', line 19 def shell @shell end |
#skipped ⇒ Boolean (readonly)
Returns whether the tool was skipped.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#stderr ⇒ String? (readonly)
Returns the standard error from the command.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#stdout ⇒ String? (readonly)
Returns the standard output from the command.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#strategy ⇒ Class
Returns the strategy class for running the command (Captured or Passthrough).
17 18 19 |
# File 'lib/reviewer/runner.rb', line 17 def strategy @strategy end |
#success ⇒ Boolean (readonly)
Returns whether the command completed successfully.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#tool_key ⇒ Symbol (readonly)
Returns the unique identifier for the tool.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
#tool_name ⇒ String (readonly)
Returns the human-readable name of the tool.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/reviewer/runner/result.rb', line 29 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(...) super freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) new( **base_attributes(runner), command_string: nil, success: true, exit_status: 0, duration: 0, stdout: nil, stderr: nil, skipped: true ) end def self.build_missing(runner) new( **base_attributes(runner), success: false, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil, skipped: nil, missing: true ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), success: runner.success?, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, skipped: nil, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed alias_method :success?, :success alias_method :skipped?, :skipped alias_method :missing?, :missing # Whether this result represents a tool that actually ran (not skipped or missing) # # @return [Boolean] true if the tool was executed def executed? = !skipped? && !missing? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) { match[Regexp.last_match(1).to_i] } end # Converts the result to a hash suitable for serialization # # @return [Hash] hash representation with nil values removed def to_h { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing }.compact # Excludes summary_pattern/summary_label (config, not results) end end |
Instance Method Details
#execute_strategy ⇒ void
This method returns an undefined value.
Runs the relevant strategy to either capture or pass through command output.
139 140 141 142 143 144 145 |
# File 'lib/reviewer/runner.rb', line 139 def execute_strategy # Run the provided strategy strategy.new(self).tap do |run_strategy| run_strategy.prepare if run_prepare_step? run_strategy.run end end |
#failed_files ⇒ Array<String>
Extracts file paths from stdout/stderr for failed-file tracking
109 110 111 |
# File 'lib/reviewer/runner.rb', line 109 def failed_files FailedFiles.new(stdout, stderr).to_a end |
#failure? ⇒ Boolean
104 |
# File 'lib/reviewer/runner.rb', line 104 def failure? = !success? |
#formatter ⇒ Runner::Formatter
Display formatter for runner-specific output (tool summary, success, failure, etc.) Computed rather than stored to avoid exceeding instance variable threshold.
52 |
# File 'lib/reviewer/runner.rb', line 52 def formatter = @formatter ||= Runner::Formatter.new(output) |
#guidance ⇒ Guidance
Uses the result of the runner to determine what, if any, guidance to display to help the user
get back on track in the event of an unsuccessful run.
181 |
# File 'lib/reviewer/runner.rb', line 181 def guidance = @guidance ||= Guidance.new(command: command, result: result, context: @context) |
#identify_tool ⇒ void
This method returns an undefined value.
Prints the tool name and description to the console as a frame of reference. Only displays in streaming mode; non-streaming strategies handle their own output.
117 118 119 120 121 122 123 |
# File 'lib/reviewer/runner.rb', line 117 def identify_tool # If there's an existing result, the runner is being re-run, and identifying the tool would # be redundant. return if result.exists? stream_output { formatter.tool_summary(tool) } end |
#missing? ⇒ Boolean
Whether this runner’s executable was not found (exit status 127)
88 |
# File 'lib/reviewer/runner.rb', line 88 def missing? = @missing == true |
#output ⇒ Output
The output channel for displaying content, delegated from context.
46 |
# File 'lib/reviewer/runner.rb', line 46 def output = @context.output |
#prepare_command ⇒ Comman
Creates_an instance of the prepare command for a tool
157 |
# File 'lib/reviewer/runner.rb', line 157 def prepare_command = @prepare_command ||= Command.new(tool, :prepare, context: @context) |
#record_timing ⇒ void
This method returns an undefined value.
Saves the last 5 elapsed times for the commands used this run by using the raw command as a
unique key. This enables the ability to compare times across runs while taking into
consideration that different iterations of the command may be running on fewer files. So
comparing a full run to the average time for a partial run wouldn't be helpful. By using the
raw command string, it will always be apples to apples.
172 173 174 175 |
# File 'lib/reviewer/runner.rb', line 172 def record_timing tool.record_timing(prepare_command, timer.prep) tool.record_timing(command, timer.main) end |
#run ⇒ Integer
Executes the command and returns the exit status
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/reviewer/runner.rb', line 62 def run # Skip if files were requested but none match this tool's pattern if command.skip? @skipped = true show_skipped return 0 end # Show which tool is running identify_tool # Use the provided strategy to run the command execute_strategy # Handle the result based on whether the tool was found result.executable_not_found? ? handle_missing : handle_result end |
#run_prepare_step? ⇒ Boolean
Determines whether a preparation step should be run before the primary command. If/when the
primary command is a `:prepare` command, then it shouldn't run twice. So it skips what would
be a superfluous run of the preparation.
152 |
# File 'lib/reviewer/runner.rb', line 152 def run_prepare_step? = command.type != :prepare && tool.prepare? |
#show_skipped ⇒ void
This method returns an undefined value.
Shows that a tool was skipped due to no matching files. Only displays in streaming mode; non-streaming modes report skips in the final summary.
129 130 131 132 133 134 |
# File 'lib/reviewer/runner.rb', line 129 def show_skipped stream_output do formatter.tool_summary(tool) formatter.skipped end end |
#skipped? ⇒ Boolean
Whether this runner was skipped due to no matching files
83 |
# File 'lib/reviewer/runner.rb', line 83 def skipped? = @skipped == true |
#streaming? ⇒ Boolean
Whether this runner is operating in streaming mode
57 |
# File 'lib/reviewer/runner.rb', line 57 def streaming? = @context.arguments.streaming? |
#success? ⇒ Boolean
Some review tools return a range of non-zero exit statuses and almost never return 0. (‘yarn audit` is a good example.) Those tools can be configured to accept a non-zero exit status so they aren’t constantly considered to be failing over minor issues.
But when other command types (prepare, install, format) are run, they either succeed or they fail. With no shades of gray in those cases, anything other than a 0 is a failure.
Skipped tools are always considered successful.
98 99 100 101 102 |
# File 'lib/reviewer/runner.rb', line 98 def success? return true if skipped? command.type == :review ? exit_status <= tool.max_exit_status : exit_status.zero? end |
#to_result ⇒ Runner::Result
Builds an immutable Result object from the current runner state
186 187 188 |
# File 'lib/reviewer/runner.rb', line 186 def to_result Result.from_runner(self) end |
#update_last_prepared_at ⇒ Time
Updates the ‘last prepared at’ timestamp that Reviewer uses to know if a tool’s preparation
step is stale and needs to be run again.
163 |
# File 'lib/reviewer/runner.rb', line 163 def update_last_prepared_at = tool.last_prepared_at = Time.now |