Module: Ukiryu::Executor
- Defined in:
- lib/ukiryu/executor.rb
Overview
Command execution with platform-specific methods
Handles execution of external commands with:
-
Shell-specific command line building
-
Environment variable management
-
Timeout handling
-
Error detection and reporting
Class Method Summary collapse
-
.build_command(executable, args, shell_class) ⇒ String
Build a command line for the given shell.
-
.execute(executable, args = [], options = {}) ⇒ Execution::Result
Execute a command with the given options.
-
.extract_status(status) ⇒ Integer
Extract exit status from Process::Status.
-
.find_executable(command, options = {}) ⇒ String?
Find an executable in the system PATH.
Class Method Details
.build_command(executable, args, shell_class) ⇒ String
Build a command line for the given shell
161 162 163 164 165 166 167 168 169 |
# File 'lib/ukiryu/executor.rb', line 161 def build_command(executable, args, shell_class) shell_instance = shell_class.new # Format executable path if needed exe = shell_instance.format_path(executable) # Join executable and arguments shell_instance.join(exe, *args) end |
.execute(executable, args = [], options = {}) ⇒ Execution::Result
Execute a command with the given options
The user MUST explicitly specify both:
-
env: The Environment to use (inherited, derived, custom, or empty)
-
shell: The Shell to interpret the command (bash, zsh, powershell, cmd, etc.)
-
timeout: Maximum execution time in seconds
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 |
# File 'lib/ukiryu/executor.rb', line 42 def execute(executable, args = [], = {}) # Get shell - must be explicitly specified shell_arg = [:shell] raise ArgumentError, 'shell is required - specify :shell option (e.g., shell: :bash)' unless shell_arg # Get timeout - must be explicitly specified timeout = [:timeout] raise ArgumentError, 'timeout is required - specify :timeout option (e.g., timeout: 90)' unless timeout # Convert shell to shell class shell_class = if shell_arg.is_a?(Class) shell_arg else Ukiryu::Shell.class_for(shell_arg.to_sym) end shell_instance = shell_class.new # Debug logging for Ruby 4.0 CI if ENV['UKIRYU_DEBUG_EXECUTABLE'] warn "[UKIRYU DEBUG Executor#execute] executable: #{executable.inspect}" warn "[UKIRYU DEBUG Executor#execute] args: #{args.inspect}" warn "[UKIRYU DEBUG Executor#execute] args.class: #{args.class}" warn "[UKIRYU DEBUG Executor#execute] shell_class: #{shell_class}" end # Prepare environment (requires Environment or Hash) env = prepare_environment([:env] || {}, shell_class) cwd = [:cwd] stdin = [:stdin] # Suppress thread warnings from Open3 (cosmetic IOError from stream closure) # Open3's internal threads may raise IOError when streams close early original_setting = Thread.report_on_exception Thread.report_on_exception = false started_at = Time.now begin result = if stdin shell_instance.execute_command_with_stdin(executable, args, env, timeout, cwd, stdin) else shell_instance.execute_command(executable, args, env, timeout, cwd) end rescue Timeout::Error Time.now raise Ukiryu::Errors::TimeoutError, "Command timed out after #{timeout} seconds: #{executable}" ensure Thread.report_on_exception = original_setting end finished_at = Time.now # Build command string for display (for error messages and debugging) command = shell_instance.join(executable, *args) # Create OOP result components using Execution namespace command_info = Execution::CommandInfo.new( executable: executable, arguments: args, full_command: command, shell: shell_instance.name, tool_name: [:tool_name], command_name: [:command_name] ) output = Execution::Output.new( stdout: result[:stdout], stderr: result[:stderr], exit_status: result[:status] ) = Execution::ExecutionMetadata.new( started_at: started_at, finished_at: finished_at, timeout: timeout ) # Check exit status if result[:status] != 0 && ![:allow_failure] raise Ukiryu::Errors::ExecutionError, format_error(executable, command, result) end Execution::Result.new( command_info: command_info, output: output, metadata: ) end |
.extract_status(status) ⇒ Integer
Extract exit status from Process::Status
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/ukiryu/executor.rb', line 175 def extract_status(status) if status.exited? status.exitstatus elsif status.signaled? # Process terminated by signal - return 128 + signal number # This matches how shells report terminated processes 128 + status.termsig elsif status.stopped? # Process was stopped - return 128 + stop signal 128 + status.stopsig else # Unknown status - return failure code 1 end end |
.find_executable(command, options = {}) ⇒ String?
Find an executable in the system PATH
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/ukiryu/executor.rb', line 137 def find_executable(command, = {}) # Try with PATHEXT extensions (Windows executables) exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] search_paths = Platform.executable_search_paths search_paths.concat([:additional_paths]) if [:additional_paths] search_paths.uniq! search_paths.each do |dir| exts.each do |ext| exe = File.join(dir, "#{command}#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) end end nil end |