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
|