Created
October 9, 2017 20:49
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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