Skip to content

Instantly share code, notes, and snippets.

@codeword
Created October 9, 2017 20:49
Show Gist options
  • Save codeword/6baa35ec4bc26d5fcb89ee970b8c2368 to your computer and use it in GitHub Desktop.
Save codeword/6baa35ec4bc26d5fcb89ee970b8c2368 to your computer and use it in GitHub Desktop.
a useful wrapper of the Open3.popen3 interface to run commands on a shell
require 'open3'
require 'logger'
class CommandRunner
class CommandFailedError < StandardError
def initialize(command, status)
super("Exit status (#{status}) is non-zero for command: #{command}")
end
end
EXIT_STATUS_INTERRUPT = 130
attr_reader :logger, :dry_run
def initialize(logger:, dry_run: false)
@logger = logger
@dry_run = dry_run
end
def run(command, stdin: STDIN, stdout: STDOUT, stderr: STDERR)
if dry_run
logger.warn("dry-running: `#{command}`")
else
logger.info("running: `#{command}`")
status = Open3.popen3(command) do |i, o, e, cmd_status|
redirect(stdin => i, o => stdout, e => stderr)
logger.debug cmd_status.value
cmd_status.value.exitstatus
end
raise CommandFailedError.new(command, status) if status != 0
end
end
private
def redirect(mapping)
inputs = mapping.keys
logger.debug mapping.inspect
until inputs.empty? || (inputs.size == 1 && inputs.first == STDIN) do
logger.debug 'starting `select`'
readable_inputs, = IO.select(inputs, [], [])
ios_ready_for_eof_check = readable_inputs
logger.debug 'finished `select`'
logger.debug 'starting eof check'
ios_ready_for_eof_check.select(&:eof).each do |src|
logger.debug "Stopping redirection from an IO in EOF: " + src.inspect
inputs.delete src
mapping[src].close if src == STDIN
end
break if inputs.empty? || (inputs.size == 1 && inputs.first == STDIN)
readable_inputs.each do |input|
begin
data = input.read_nonblock(1024)
output = mapping[input]
output.write(data)
output.flush
rescue EOFError => e
logger.debug "Reached EOF: #{e}"
inputs.delete input
rescue => e
logger.debug "Handled error: #{e}: io: #{input.inspect}"
inputs.delete input
end
end
end
end
def all_eof?(files)
begin
files.all?(&:eof)
rescue Interrupt => e
logger.warn "You've interrupted while running a command."
EXIT_STATUS_INTERRUPT
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment