Module: Zui::Command

Defined in:
lib/zui/command.rb

Constant Summary collapse

DEFAULT_MAX_OUTPUT_BYTES =
1_048_576

Class Method Summary collapse

Class Method Details

.run(argv, env: {}, chdir: nil, input: "", timeout: nil, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/zui/command.rb', line 20

def run(argv, env: {}, chdir: nil, input: "", timeout: nil, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES)
  arguments = normalize_argv(argv)
  output_limit = Integer(max_output_bytes)
  raise ArgumentError, "max_output_bytes must be positive" unless output_limit.positive?
  options = {}
  options[:chdir] = File.expand_path(chdir) if chdir
  stdin = stdout = stderr = wait_thread = nil
  Open3.popen3(normalize_env(env), *arguments, **options) do |child_stdin, child_stdout, child_stderr, child_wait|
    stdin, stdout, stderr, wait_thread = child_stdin, child_stdout, child_stderr, child_wait
    stdin.write(input.to_s)
    stdin.close
    stdout_reader = Thread.new { bounded_read(stdout, output_limit) }
    stderr_reader = Thread.new { bounded_read(stderr, output_limit) }
    status = timeout ? Timeout.timeout(Float(timeout)) { wait_thread.value } : wait_thread.value
    stdout_value, stdout_limited = stdout_reader.value
    stderr_value, stderr_limited = stderr_reader.value
    if stdout_limited || stderr_limited
      raise CommandOutputLimit, "command output exceeded #{output_limit} bytes: #{arguments.first}"
    end
    return CommandResult.new(stdout: stdout_value, stderr: stderr_value, status:)
  rescue Timeout::Error
    terminate(wait_thread)
    stdout_reader&.join(1)
    stderr_reader&.join(1)
    raise CommandTimeout, "command timed out after #{timeout}s: #{arguments.first}"
  end
ensure
  [stdin, stdout, stderr].each { |stream| stream&.close unless stream&.closed? }
end