Class: Ukiryu::Shell::Cmd
Overview
Windows cmd.exe shell implementation
cmd.exe uses caret (^) as the escape character and double quotes for strings containing spaces. Environment variables use %VAR% syntax.
Constant Summary collapse
- SHELL_NAME =
:cmd- PLATFORM =
:windows- EXECUTABLE =
'cmd'- WHITESPACE_PATTERN =
Pre-compiled pattern for whitespace detection
/[ \t]/.freeze
Constants inherited from Base
Class Method Summary collapse
-
.detect_alias(_command_name) ⇒ Hash?
Detect if a command is a cmd.exe alias.
Instance Method Summary collapse
-
#capabilities ⇒ Hash
cmd.exe capabilities on Windows.
-
#env_var(name) ⇒ String
Format an environment variable reference.
-
#escape(string) ⇒ String
Escape a string for cmd.exe Caret is the escape character for special characters: % ^ < > & |.
-
#execute_command(executable, args, env, timeout, cwd = nil) ⇒ Hash
Execute a command using cmd.exe.
-
#execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data) ⇒ Hash
Execute a command with stdin input using cmd.exe.
-
#format_path(path) ⇒ String
Format a file path for cmd.exe Convert forward slashes to backslashes.
-
#headless_environment ⇒ Hash
cmd.exe doesn’t need DISPLAY variable.
-
#join(executable, *args) ⇒ String
Join executable and arguments into a command line Uses smart quoting: only quote arguments that need it.
- #name ⇒ Object
-
#quote(string) ⇒ String
Quote an argument for cmd.exe Uses double quotes for strings with spaces.
Methods inherited from Base
#encoding, #environment_to_h, #format_environment, #needs_quoting?, #supports?
Class Method Details
.detect_alias(_command_name) ⇒ Hash?
Detect if a command is a cmd.exe alias
cmd.exe has doskey macros but they are not traditional shell aliases. We don’t detect doskey macros for executable discovery.
27 28 29 30 |
# File 'lib/ukiryu/shell/cmd.rb', line 27 def self.detect_alias(_command_name) # cmd.exe doskey macros are not shell aliases in the Unix sense nil end |
Instance Method Details
#capabilities ⇒ Hash
cmd.exe capabilities on Windows
127 128 129 130 131 132 133 |
# File 'lib/ukiryu/shell/cmd.rb', line 127 def capabilities { supports_display: false, # Windows doesn't use DISPLAY supports_ansi_colors: true, encoding: Encoding::CP_1252 # cmd.exe default is CP1252 (Windows-1252) } end |
#env_var(name) ⇒ String
Format an environment variable reference
74 75 76 |
# File 'lib/ukiryu/shell/cmd.rb', line 74 def env_var(name) "%#{name}%" end |
#escape(string) ⇒ String
Escape a string for cmd.exe Caret is the escape character for special characters: % ^ < > & |
41 42 43 |
# File 'lib/ukiryu/shell/cmd.rb', line 41 def escape(string) string.to_s.gsub(/[%^<>&|]/) { "^#{::Regexp.last_match(0)}" } end |
#execute_command(executable, args, env, timeout, cwd = nil) ⇒ Hash
Execute a command using cmd.exe
Uses cmd.exe’s /c flag to execute the command string.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/ukiryu/shell/cmd.rb', line 146 def execute_command(executable, args, env, timeout, cwd = nil) # Build command string using this shell's quoting rules command_string = join(executable, *args) # Convert Environment to Hash ONLY at Open3 call site env_hash = environment_to_h(env) # Execute using cmd.exe's /c flag Timeout.timeout(timeout) do execution = lambda do stdout, stderr, status = Open3.capture3(env_hash, 'cmd', '/c', command_string) { status: Ukiryu::Executor.extract_status(status), stdout: stdout, stderr: stderr } end if cwd Dir.chdir(cwd) { execution.call } else execution.call end end rescue Timeout::Error, Timeout::ExitException # Re-raise with context raise Timeout::Error, "Command timed out after #{timeout}s: #{executable}" end |
#execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data) ⇒ Hash
Execute a command with stdin input using cmd.exe
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/ukiryu/shell/cmd.rb', line 185 def execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data) # Build command string using this shell's quoting rules command_string = join(executable, *args) # Convert Environment to Hash ONLY at Open3 call site env_hash = environment_to_h(env) # Execute using cmd.exe's /c flag Timeout.timeout(timeout) do execution = lambda do Open3.popen3(env_hash, 'cmd', '/c', command_string) do |stdin, stdout, stderr, wait_thr| # Write stdin data begin if stdin_data.is_a?(IO) IO.copy_stream(stdin_data, stdin) elsif stdin_data.is_a?(String) stdin.write(stdin_data) end rescue Errno::EPIPE # Process closed stdin early ensure stdin.close end # Read output out = stdout.read err = stderr.read # Wait for process to complete status = wait_thr.value { status: Ukiryu::Executor.extract_status(status), stdout: out, stderr: err } end end if cwd Dir.chdir(cwd) { execution.call } else execution.call end end rescue Timeout::Error, Timeout::ExitException # Re-raise with context raise Timeout::Error, "Command timed out after #{timeout}s: #{executable}" end |
#format_path(path) ⇒ String
Format a file path for cmd.exe Convert forward slashes to backslashes
66 67 68 |
# File 'lib/ukiryu/shell/cmd.rb', line 66 def format_path(path) path.to_s.gsub('/', '\\') end |
#headless_environment ⇒ Hash
cmd.exe doesn’t need DISPLAY variable
120 121 122 |
# File 'lib/ukiryu/shell/cmd.rb', line 120 def headless_environment {} end |
#join(executable, *args) ⇒ String
Join executable and arguments into a command line Uses smart quoting: only quote arguments that need it
Special handling for /c: When using cmd.exe’s /c flag, the command string that follows should NOT be quoted, as cmd.exe treats it as a single command to execute. The quotes become literal characters.
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 |
# File 'lib/ukiryu/shell/cmd.rb', line 88 def join(executable, *args) # Quote executable if it needs quoting (e.g., contains spaces) exe_formatted = needs_quoting?(executable) ? quote(executable) : escape(executable) # Special handling for cmd.exe /c flag # When using /c, the rest is a single command string - don't quote it if args[0] == '/c' && args.length > 1 # Build command string from the remaining arguments # They form a single command that cmd.exe will execute command_parts = args[1..] # For the command string, we escape special chars but don't wrap in quotes # This allows cmd.exe to parse operators like &&, |, etc. command_string = command_parts.map { |a| escape(a) }.join(' ') [exe_formatted, args[0], command_string].join(' ') else # Normal quoting for all arguments args_formatted = args.map do |a| if needs_quoting?(a) quote(a) else # For simple strings, pass without quotes # cmd.exe treats them as literal strings escape(a) end end [exe_formatted, *args_formatted].join(' ') end end |
#name ⇒ Object
32 33 34 |
# File 'lib/ukiryu/shell/cmd.rb', line 32 def name :cmd end |
#quote(string) ⇒ String
Quote an argument for cmd.exe Uses double quotes for strings with spaces
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/ukiryu/shell/cmd.rb', line 50 def quote(string) if string.to_s =~ WHITESPACE_PATTERN # Contains whitespace, use double quotes # Note: cmd.exe doesn't escape quotes inside double quotes the same way "\"#{string}\"" else # No whitespace, escape special characters escape(string) end end |