Module: Rakit::Shell::CommandService
- Defined in:
- lib/rakit/shell.rb
Overview
CommandService: execute runs a Command; test runs it; format returns display string.
Class Attribute Summary collapse
-
._executed_cache ⇒ Object
readonly
Returns the value of attribute _executed_cache.
Class Method Summary collapse
- ._cache_key(command) ⇒ Object
- ._command_with_result(command, exit_status, stdout, stderr) ⇒ Object
- ._format_command_to_s(command, fmt) ⇒ Object
-
.cache ⇒ Object
Returns the current cache of executed Commands (key => Command with result).
-
.clear_cache ⇒ Object
Clears the in-memory cache of executed commands.
-
.execute(command, use_cache: true) ⇒ Object
Run the command (name + args) with optional cwd and timeout.
-
.format(format_request) ⇒ Object
Format RPC: returns FormatResponse with the command formatted for display.
-
.show(command, format_enum = nil) ⇒ Object
Display the command in the given format (default ONE_LINE).
-
.test(command) ⇒ Object
Run the command and check expected_exit_code, expected_stdout, expected_stderr, and acceptance_criteria.
Class Attribute Details
._executed_cache ⇒ Object (readonly)
Returns the value of attribute _executed_cache.
18 19 20 |
# File 'lib/rakit/shell.rb', line 18 def _executed_cache @_executed_cache end |
Class Method Details
._cache_key(command) ⇒ Object
31 32 33 34 35 36 37 38 |
# File 'lib/rakit/shell.rb', line 31 def self._cache_key(command) name = (command.respond_to?(:name) ? command.name : "").to_s args = (command.respond_to?(:args) ? command.args.to_a : []).map(&:to_s) cwd = (command.respond_to?(:working_directory) ? command.working_directory : "").to_s timeout_sec = command.respond_to?(:timeout_seconds) ? command.timeout_seconds.to_i : 10 timeout_sec = 10 if timeout_sec <= 0 [name, *args, cwd, timeout_sec].join("\0") end |
._command_with_result(command, exit_status, stdout, stderr) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/rakit/shell.rb', line 133 def self._command_with_result(command, exit_status, stdout, stderr) criteria = command.respond_to?(:acceptance_criteria) ? command.acceptance_criteria.to_a : [] Command.new( name: command.name, args: command.args.to_a, working_directory: command.respond_to?(:working_directory) ? command.working_directory.to_s : "", timeout_seconds: command.respond_to?(:timeout_seconds) ? command.timeout_seconds.to_i : 0, expected_exit_code: command.respond_to?(:expected_exit_code) ? command.expected_exit_code : 0, expected_stdout: command.respond_to?(:expected_stdout) ? command.expected_stdout.to_s : "", expected_stderr: command.respond_to?(:expected_stderr) ? command.expected_stderr.to_s : "", acceptance_criteria: criteria, exit_status: exit_status, stdout: stdout, stderr: stderr, ) end |
._format_command_to_s(command, fmt) ⇒ Object
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 |
# File 'lib/rakit/shell.rb', line 56 def self._format_command_to_s(command, fmt) return "" unless command name = (command.respond_to?(:name) ? command.name : "").to_s args = command.respond_to?(:args) ? command.args.to_a : [] cwd = command.respond_to?(:working_directory) ? command.working_directory.to_s : "" timeout_sec = command.respond_to?(:timeout_seconds) ? command.timeout_seconds.to_i : 0 argv = [name, *args].reject { |a| a.nil? || a.to_s.empty? } cmd_line = argv.map { |a| a.include?(" ") ? a.inspect : a }.join(" ") case fmt when CommandFormat::MULTI_LINE lines = ["name: #{name}", "args: #{args.inspect}"] lines << "working_directory: #{cwd}" if cwd && !cwd.empty? lines << "timeout_seconds: #{timeout_sec}" if timeout_sec.positive? lines.join("\n") when CommandFormat::COMPACT exit_status = command.respond_to?(:exit_status) ? command.exit_status.to_i : 0 out_str = command.respond_to?(:stdout) ? command.stdout.to_s : "" err_str = command.respond_to?(:stderr) ? command.stderr.to_s : "" if exit_status != 0 lines = ["#{CROSS} #{cmd_line}"] lines << "stdout:\n#{out_str}" if out_str && !out_str.empty? lines << "stderr:\n#{err_str}" if err_str && !err_str.empty? lines.join("\n") else "#{CHECK} #{cmd_line}" end else cmd_line end end |
.cache ⇒ Object
Returns the current cache of executed Commands (key => Command with result). Read-only view; use clear_cache to reset.
22 23 24 |
# File 'lib/rakit/shell.rb', line 22 def self.cache _executed_cache.dup end |
.clear_cache ⇒ Object
Clears the in-memory cache of executed commands.
27 28 29 |
# File 'lib/rakit/shell.rb', line 27 def self.clear_cache _executed_cache.clear end |
.execute(command, use_cache: true) ⇒ Object
Run the command (name + args) with optional cwd and timeout. Returns Command with exit_status, stdout, stderr set. When use_cache: true (default), returns a cached result if the same command was already executed.
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 |
# File 'lib/rakit/shell.rb', line 91 def self.execute(command, use_cache: true) key = _cache_key(command) if use_cache && _executed_cache.key?(key) return _executed_cache[key] end name = (command.respond_to?(:name) ? command.name : "").to_s args = command.respond_to?(:args) ? command.args.to_a : [] argv = [name, *args].reject { |a| a.nil? || a.to_s.empty? } raise ArgumentError, "Command name is required" if argv.empty? cwd = command.respond_to?(:working_directory) ? command.working_directory.to_s : "" cwd = nil if cwd.nil? || cwd.empty? timeout_sec = command.respond_to?(:timeout_seconds) ? command.timeout_seconds.to_i : 10 timeout_sec = 10 if timeout_sec <= 0 opts = {} opts[:chdir] = cwd if cwd stdout = "" stderr = "" exit_status = -1 run_block = lambda do Open3.popen3(*argv, **opts) do |_stdin, out, err, wait_thr| stdout = out.read stderr = err.read exit_status = wait_thr.value&.exitstatus || -1 end end Timeout.timeout(timeout_sec) { run_block.call } result = _command_with_result(command, exit_status, stdout, stderr) _executed_cache[key] = result result rescue Timeout::Error result = _command_with_result(command, -1, stdout, stderr + " (timeout after #{timeout_sec}s)") _executed_cache[key] = result result end |
.format(format_request) ⇒ Object
Format RPC: returns FormatResponse with the command formatted for display.
41 42 43 44 45 46 |
# File 'lib/rakit/shell.rb', line 41 def self.format(format_request) command = format_request.respond_to?(:command) ? format_request.command : nil fmt = format_request.respond_to?(:format) ? format_request.format : CommandFormat::ONE_LINE output = _format_command_to_s(command, fmt) FormatResponse.new(output: output) end |
.show(command, format_enum = nil) ⇒ Object
Display the command in the given format (default ONE_LINE). Prints to stdout. Builds a FormatRequest and uses the Format RPC.
50 51 52 53 54 |
# File 'lib/rakit/shell.rb', line 50 def self.show(command, format_enum = nil) fmt = format_enum || CommandFormat::ONE_LINE req = FormatRequest.new(command: command, format: fmt) puts CommandService.format(req).output end |
.test(command) ⇒ Object
Run the command and check expected_exit_code, expected_stdout, expected_stderr, and acceptance_criteria. Returns TestResult.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/rakit/shell.rb', line 151 def self.test(command) result = execute(command) errors = [] expected_exit = command.respond_to?(:expected_exit_code) ? command.expected_exit_code : 0 errors << "exit_code: expected #{expected_exit}, got #{result.exit_status}" if result.exit_status != expected_exit if command.respond_to?(:expected_stdout) && !command.expected_stdout.to_s.empty? && result.stdout != command.expected_stdout errors << "stdout: expected #{command.expected_stdout.inspect}, got #{result.stdout.inspect}" end if command.respond_to?(:expected_stderr) && !command.expected_stderr.to_s.empty? && result.stderr != command.expected_stderr errors << "stderr: expected #{command.expected_stderr.inspect}, got #{result.stderr.inspect}" end if command.respond_to?(:acceptance_criteria) command.acceptance_criteria.each do |crit| kind = (crit.respond_to?(:kind) ? crit.kind : "").to_s value = (crit.respond_to?(:value) ? crit.value : "").to_s case kind when "exit_code" errors << "acceptance exit_code: expected #{value}, got #{result.exit_status}" unless result.exit_status.to_s == value when "stdout_contains" errors << "acceptance stdout_contains #{value.inspect}" unless result.stdout.to_s.include?(value) when "stderr_contains" errors << "acceptance stderr_contains #{value.inspect}" unless result.stderr.to_s.include?(value) when "stdout_matches" errors << "acceptance stdout_matches #{value.inspect}" unless result.stdout.to_s.match?(Regexp.new(value)) when "stderr_matches" errors << "acceptance stderr_matches #{value.inspect}" unless result.stderr.to_s.match?(Regexp.new(value)) end end end TestResult.new(success: errors.empty?, errors: errors) end |