Module: CLI::Kit::System
- Defined in:
- lib/cli/kit/system.rb,
lib/cli/kit/support/test_helper.rb
Constant Summary collapse
- SUDO_PROMPT =
CLI::UI.fmt("{{info:(sudo)}} Password: ")
Class Method Summary collapse
-
.capture2(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.capture2e(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.capture3(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.error_message ⇒ Object
Returns the errors associated to a test run.
-
.fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {}) ⇒ Object
Sets up an expectation for a command and stubs out the call (unless allow is true).
-
.original_capture2 ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.original_capture2e ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.original_capture3 ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in.
-
.original_system ⇒ Object
Execute a command in the user’s environment Outputs result of the command without capturing it.
-
.reset! ⇒ Object
Resets the faked commands.
-
.split_partial_characters(data) ⇒ Object
Split off trailing partial UTF-8 Characters.
-
.sudo_reason(msg) ⇒ Object
Ask for sudo access with a message explaning the need for it Will make subsequent commands capable of running with sudo for a period of time.
-
.system(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment Outputs result of the command without capturing it.
Class Method Details
.capture2(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture2
#### Returns
-
‘output`: output (STDOUT) of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out, stat = CLI::Kit::System.capture2(’ls’, ‘a_folder’)‘
46 47 48 |
# File 'lib/cli/kit/system.rb', line 46 def capture2(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture2, **kwargs) end |
.capture2e(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture2e
#### Returns
-
‘output`: output (STDOUT merged with STDERR) of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out_and_err, stat = CLI::Kit::System.capture2e(’ls’, ‘a_folder’)‘
67 68 69 |
# File 'lib/cli/kit/system.rb', line 67 def capture2e(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture2e, **kwargs) end |
.capture3(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture3
#### Returns
-
‘output`: STDOUT of the command execution
-
‘error`: STDERR of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out, err, stat = CLI::Kit::System.capture3(’ls’, ‘a_folder’)‘
89 90 91 |
# File 'lib/cli/kit/system.rb', line 89 def capture3(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture3, **kwargs) end |
.error_message ⇒ Object
Returns the errors associated to a test run
#### Returns ‘errors` (String) a string representing errors found on this run, nil if none
150 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 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/cli/kit/support/test_helper.rb', line 150 def errors = { unexpected: [], not_run: [], other: {}, } @delegate_open3.each do |cmd, opts| if opts[:unexpected] errors[:unexpected] << cmd elsif opts[:run] error = [] if opts[:expected][:sudo] != opts[:actual][:sudo] error << "- sudo was supposed to be #{opts[:expected][:sudo]} but was #{opts[:actual][:sudo]}" end if opts[:expected][:env] != opts[:actual][:env] error << "- env was supposed to be #{opts[:expected][:env]} but was #{opts[:actual][:env]}" end errors[:other][cmd] = error.join("\n") unless error.empty? else errors[:not_run] << cmd end end final_error = [] unless errors[:unexpected].empty? final_error << CLI::UI.fmt(<<~EOF) {{bold:Unexpected command invocations:}} {{command:#{errors[:unexpected].join("\n")}}} EOF end unless errors[:not_run].empty? final_error << CLI::UI.fmt(<<~EOF) {{bold:Expected commands were not run:}} {{command:#{errors[:not_run].join("\n")}}} EOF end unless errors[:other].empty? final_error << CLI::UI.fmt(<<~EOF) {{bold:Commands were not run as expected:}} #{errors[:other].map { |cmd, msg| "{{command:#{cmd}}}\n#{msg}" }.join("\n\n")} EOF end return nil if final_error.empty? "\n" + final_error.join("\n") # Initial new line for formatting reasons end |
.fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {}) ⇒ Object
Sets up an expectation for a command and stubs out the call (unless allow is true)
#### Parameters ‘*a` : the command, represented as a splat `stdout` : stdout to stub the command with (defaults to empty string) `stderr` : stderr to stub the command with (defaults to empty string) `allow` : allow determines if the command will be actually run, or stubbed. Defaults to nil (stub) `success` : success status to stub the command with (Defaults to nil) `sudo` : expectation of sudo being set or not (defaults to false) `env` : expectation of env being set or not (defaults to {})
Note: Must set allow or success
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/cli/kit/support/test_helper.rb', line 119 def fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {}) raise ArgumentError, "success or allow must be set" if success.nil? && allow.nil? @delegate_open3 ||= {} @delegate_open3[a.join(' ')] = { expected: { sudo: sudo, env: env, }, actual: { sudo: nil, env: nil, }, stdout: stdout, stderr: stderr, allow: allow, success: success, run: false, } end |
.original_capture2 ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture2
#### Returns
-
‘output`: output (STDOUT) of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out, stat = CLI::Kit::System.capture2(’ls’, ‘a_folder’)‘
51 52 53 |
# File 'lib/cli/kit/support/test_helper.rb', line 51 def capture2(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture2, **kwargs) end |
.original_capture2e ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture2e
#### Returns
-
‘output`: output (STDOUT merged with STDERR) of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out_and_err, stat = CLI::Kit::System.capture2e(’ls’, ‘a_folder’)‘
69 70 71 |
# File 'lib/cli/kit/support/test_helper.rb', line 69 def capture2e(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture2e, **kwargs) end |
.original_capture3 ⇒ Object
Execute a command in the user’s environment This is meant to be largely equivalent to backticks, only with the env passed in. Captures the results of the command without output to the console
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional arguments to pass to Open3.capture3
#### Returns
-
‘output`: STDOUT of the command execution
-
‘error`: STDERR of the command execution
-
‘status`: boolean success status of the command execution
#### Usage ‘out, err, stat = CLI::Kit::System.capture3(’ls’, ‘a_folder’)‘
87 88 89 |
# File 'lib/cli/kit/support/test_helper.rb', line 87 def capture3(*a, sudo: false, env: ENV, **kwargs) delegate_open3(*a, sudo: sudo, env: env, method: :capture3, **kwargs) end |
.original_system ⇒ Object
Execute a command in the user’s environment Outputs result of the command without capturing it
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional keyword arguments to pass to Process.spawn
#### Returns
-
‘status`: boolean success status of the command execution
#### Usage ‘stat = CLI::Kit::System.system(’ls’, ‘a_folder’)‘
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 |
# File 'lib/cli/kit/support/test_helper.rb', line 36 def system(*a, sudo: false, env: ENV, **kwargs) a = apply_sudo(*a, sudo) out_r, out_w = IO.pipe err_r, err_w = IO.pipe in_stream = STDIN.closed? ? :close : STDIN pid = Process.spawn(env, *resolve_path(a, env), 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close handlers = if block_given? { out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) } } else { out_r => ->(data) { STDOUT.write(data) }, err_r => ->(data) { STDOUT.write(data) } } end previous_trailing = Hash.new('') loop do ios = [err_r, out_r].reject(&:closed?) break if ios.empty? readers, = IO.select(ios) readers.each do |io| begin data, trailing = split_partial_characters(io.readpartial(4096)) handlers[io].call(previous_trailing[io] + data) previous_trailing[io] = trailing rescue IOError io.close end end end Process.wait(pid) $CHILD_STATUS end |
.reset! ⇒ Object
Resets the faked commands
142 143 144 |
# File 'lib/cli/kit/support/test_helper.rb', line 142 def reset! @delegate_open3 = {} end |
.split_partial_characters(data) ⇒ Object
Split off trailing partial UTF-8 Characters. UTF-8 Multibyte characters start with a 11xxxxxx byte that tells how many following bytes are part of this character, followed by some number of 10xxxxxx bytes. This simple algorithm will split off a whole trailing multi-byte character.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/cli/kit/system.rb', line 150 def split_partial_characters(data) last_byte = data.getbyte(-1) return [data, ''] if (last_byte & 0b1000_0000).zero? # UTF-8 is up to 6 characters per rune, so we could never want to trim more than that, and we want to avoid # allocating an array for the whole of data with bytes min_bound = -[6, data.bytesize].min final_bytes = data.byteslice(min_bound..-1).bytes partial_character_sub_index = final_bytes.rindex { |byte| byte & 0b1100_0000 == 0b1100_0000 } # Bail out for non UTF-8 return [data, ''] unless partial_character_sub_index partial_character_index = min_bound + partial_character_sub_index [data.byteslice(0...partial_character_index), data.byteslice(partial_character_index..-1)] end |
.sudo_reason(msg) ⇒ Object
Ask for sudo access with a message explaning the need for it Will make subsequent commands capable of running with sudo for a period of time
#### Parameters
-
‘msg`: A message telling the user why sudo is needed
#### Usage ‘ctx.sudo_reason(“We need to do a thing”)`
20 21 22 23 24 25 26 27 |
# File 'lib/cli/kit/system.rb', line 20 def sudo_reason(msg) # See if sudo has a cached password `env SUDO_ASKPASS=/usr/bin/false sudo -A true` return if $CHILD_STATUS.success? CLI::UI.with_frame_color(:blue) do puts(CLI::UI.fmt("{{i}} #{msg}")) end end |
.system(*a, sudo: false, env: {}, **kwargs) ⇒ Object
Execute a command in the user’s environment Outputs result of the command without capturing it
#### Parameters
-
‘*a`: A splat of arguments evaluated as a command. (e.g. `’rm’, folder` is equivalent to ‘rm #folder`)
-
‘sudo`: If truthy, run this command with sudo. If String, pass to `sudo_reason`
-
‘env`: process environment with which to execute this command
-
‘**kwargs`: additional keyword arguments to pass to Process.spawn
#### Returns
-
‘status`: boolean success status of the command execution
#### Usage ‘stat = CLI::Kit::System.system(’ls’, ‘a_folder’)‘
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 |
# File 'lib/cli/kit/system.rb', line 108 def system(*a, sudo: false, env: ENV, **kwargs) a = apply_sudo(*a, sudo) out_r, out_w = IO.pipe err_r, err_w = IO.pipe in_stream = STDIN.closed? ? :close : STDIN pid = Process.spawn(env, *resolve_path(a, env), 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close handlers = if block_given? { out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) } } else { out_r => ->(data) { STDOUT.write(data) }, err_r => ->(data) { STDOUT.write(data) } } end previous_trailing = Hash.new('') loop do ios = [err_r, out_r].reject(&:closed?) break if ios.empty? readers, = IO.select(ios) readers.each do |io| begin data, trailing = split_partial_characters(io.readpartial(4096)) handlers[io].call(previous_trailing[io] + data) previous_trailing[io] = trailing rescue IOError io.close end end end Process.wait(pid) $CHILD_STATUS end |