lib/backticks/runner.rb in backticks-0.4.0 vs lib/backticks/runner.rb in backticks-0.5.0
- old
+ new
@@ -1,14 +1,18 @@
require 'pty'
+require 'open3'
module Backticks
# An easy-to-use interface for invoking commands and capturing their output.
# Instances of Runner can be interactive, which prints the command's output
# to the terminal and also allows the user to interact with the command.
- # They can also be unbuffered, which uses a pseudo-tty to capture the
- # command's output with no delay or
+ # By default commands are unbuffered, using a pseudoterminal to capture
+ # the output with no delay.
class Runner
+ # Default streams to buffer if someone calls bufferered= with Boolean.
+ BUFFERED = [:stdin, :stdout, :stderr].freeze
+
# If true, commands will have their stdio streams tied to the parent
# process so the user can view their output and send input to them.
# Commands' output is still captured normally when they are interactive.
#
# Note that interactivity doesn't work very well with unbuffered commands;
@@ -19,34 +23,51 @@
# true, you usually want to set buffered to false!
#
# @return [Boolean]
attr_accessor :interactive
- # If true, commands will be invoked with a pseudo-TTY for stdout in order
- # to capture output as it is generated instead of waiting for pipe buffers
- # to fill.
+ # List of I/O streams that should be captured using a pipe instead of
+ # a pseudoterminal.
#
- # @return [Boolean]
- attr_accessor :buffered
+ # This may be a Boolean, or it may be an Array of stream names from the
+ # set [:stdin, stdout, stderr].
+ #
+ # @return [Array] list of symbolic stream names
+ attr_reader :buffered
# @return [#parameters] the CLI-translation object used by this runner
attr_reader :cli
# Create an instance of Runner.
- # @param [#parameters] cli object used to convert Ruby method parameters into command-line parameters
+ # @option [#include?,Boolean] buffered list of names; true/false for all/none
+ # @option [#parameters] cli command-line parameter translator
+ # @option [Boolean] interactive true to tie parent stdout/stdin to child
+ #
+ # @example buffer stdout
+ # Runner.new(buffered:[:stdout])
def initialize(options={})
options = {
:buffered => false,
:cli => Backticks::CLI::Getopt,
:interactive => false,
}.merge(options)
- @buffered = options[:buffered]
@cli = options[:cli]
- @interactive = options[:interactive]
+ self.buffered = options[:buffered]
+ self.interactive = options[:interactive]
end
+ # @param [Array,Boolean] buffered list of symbolic stream names; true/false for all/none
+ def buffered=(b)
+ @buffered = case b
+ when true then BUFFERED
+ when false, nil then []
+ else
+ b
+ end
+ end
+
# @deprecated
def command(*sugar)
warn 'Backticks::Runner#command is deprecated; please call #run instead'
run(*sugar)
end
@@ -77,52 +98,34 @@
#
# @param [Array] argv command to run; argv[0] is program name and the
# remaining elements are parameters and flags
# @return [Command] the running command
def run_without_sugar(argv)
- if self.buffered
- run_buffered(argv)
+ stdin_r, stdin = if buffered.include?(:stdin) && !interactive
+ IO.pipe
else
- run_unbuffered(argv)
+ PTY.open
end
- end
+ stdout, stdout_w = if buffered.include?(:stdout) && !interactive
+ IO.pipe
+ else
+ PTY.open
+ end
+ stderr, stderr_w = if buffered.include?(:stderr)
+ IO.pipe
+ else
+ PTY.open
+ end
- # Run a command. Use a pty to capture the unbuffered output.
- #
- # @param [Array] argv command to run; argv[0] is program name and the
- # remaining elements are parameters and flags
- # @return [Command] the running command
- private
- def run_unbuffered(argv)
- stdout, stdout_w = PTY.open
- stdin_r, stdin = PTY.open
- stderr, stderr_w = PTY.open
pid = spawn(*argv, in: stdin_r, out: stdout_w, err: stderr_w)
stdin_r.close
stdout_w.close
stderr_w.close
- unless @interactive
+ unless interactive
stdin.close
stdin = nil
end
Command.new(pid, stdin, stdout, stderr)
- end
-
- # Run a command. Perform no translation or substitution. Use a pipe
- # to read the output, which may be buffered by the OS. Return the program's
- # exit status and stdout.
- #
- # @param [Array] argv command to run; argv[0] is program name and the
- # remaining elements are command-line arguments.
- # @return [Command] the running command
- def run_buffered(argv)
- stdin, stdout, stderr, thr = Open3.popen3(*argv)
- unless @interactive
- stdin.close
- stdin = nil
- end
-
- Command.new(thr.pid, stdin, stdout, stderr)
end
end
end