class Toys::Utils::Exec::Controller

An object that controls a subprocess. This object is returned from an execution running in the background, or is yielded to a control block for an execution running in the foreground. You may use this object to interact with the subcommand's streams, send signals to the process, and get its result.

Attributes

err[R]

The subcommand's standard error stream (which can be read from).

@return [IO] if the command was configured with `err: :controller` @return [nil] if the command was not configured with

`err: :controller`
exception[R]

The exception raised when the process failed to start.

Exactly one of {#exception} and {#pid} will be non-nil.

@return [Exception] if the process failed to start. @return [nil] if the process start was successful.

in[R]

The subcommand's standard input stream (which can be written to).

@return [IO] if the command was configured with `in: :controller` @return [nil] if the command was not configured with

`in: :controller`
name[R]

The subcommand's name. @return [Object]

out[R]

The subcommand's standard output stream (which can be read from).

@return [IO] if the command was configured with `out: :controller` @return [nil] if the command was not configured with

`out: :controller`
pid[R]

The process ID.

Exactly one of {#exception} and {#pid} will be non-nil.

@return [Integer] if the process start was successful @return [nil] if the process could not be started.

Public Class Methods

new(name, controller_streams, captures, pid, join_threads, result_callback, mutex) click to toggle source

@private

# File lib/toys/utils/exec.rb, line 494
def initialize(name, controller_streams, captures, pid, join_threads,
               result_callback, mutex)
  @name = name
  @in = controller_streams[:in]
  @out = controller_streams[:out]
  @err = controller_streams[:err]
  @captures = captures
  @pid = @exception = @wait_thread = nil
  case pid
  when ::Integer
    @pid = pid
    @wait_thread = ::Process.detach(pid)
  when ::Exception
    @exception = pid
  end
  @join_threads = join_threads
  @result_callback = result_callback
  @mutex = mutex
  @result = nil
end

Public Instance Methods

capture(which) click to toggle source

Captures the remaining data in the given stream. After calling this, do not read directly from the stream.

@param which [:out,:err] Which stream to capture @return [self]

# File lib/toys/utils/exec.rb, line 575
def capture(which)
  stream = stream_for(which)
  @join_threads << ::Thread.new do
    begin
      data = stream.read
      @mutex.synchronize do
        @captures[which] = data
      end
    ensure
      stream.close
    end
  end
  self
end
capture_err() click to toggle source

Captures the remaining data in the standard error stream. After calling this, do not read directly from the stream.

@return [self]

# File lib/toys/utils/exec.rb, line 606
def capture_err
  capture(:err)
end
capture_out() click to toggle source

Captures the remaining data in the standard output stream. After calling this, do not read directly from the stream.

@return [self]

# File lib/toys/utils/exec.rb, line 596
def capture_out
  capture(:out)
end
close_streams(which) click to toggle source

Close the controller's streams. @private

# File lib/toys/utils/exec.rb, line 753
def close_streams(which)
  @in.close if which != :out && @in && !@in.closed?
  @out.close if which != :in && @out && !@out.closed?
  @err.close if which != :in && @err && !@err.closed?
  self
end
executing?() click to toggle source

Determine whether the subcommand is still executing

@return [Boolean]

# File lib/toys/utils/exec.rb, line 722
def executing?
  @wait_thread&.status ? true : false
end
kill(sig) click to toggle source

Send the given signal to the process. The signal may be specified by name or number.

@param sig [Integer,String] The signal to send. @return [self]

# File lib/toys/utils/exec.rb, line 711
def kill(sig)
  ::Process.kill(sig, pid) if pid
  self
end
Also aliased as: signal
redirect(which, io, *io_args) click to toggle source

Redirects the remainder of the given stream.

You may specify the stream as an IO or IO-like object, or as a file specified by its path. If specifying a file, you may optionally provide the mode and permissions for the call to `File#open`. You can also specify the value `:null` to indicate the null file.

After calling this, do not interact directly with the stream.

@param which [:in,:out,:err] Which stream to redirect @param io [IO,StringIO,String,:null] Where to redirect the stream @param io_args [Object…] The mode and permissions for opening the

file, if redirecting to/from a file.

@return [self]

# File lib/toys/utils/exec.rb, line 626
def redirect(which, io, *io_args)
  io = ::File::NULL if io == :null
  if io.is_a?(::String)
    io_args = which == :in ? ["r"] : ["w"] if io_args.empty?
    io = ::File.open(io, *io_args)
  end
  stream = stream_for(which, allow_in: true)
  @join_threads << ::Thread.new do
    begin
      if which == :in
        ::IO.copy_stream(io, stream)
      else
        ::IO.copy_stream(stream, io)
      end
    ensure
      stream.close
      io.close
    end
  end
  self
end
redirect_err(io, *io_args) click to toggle source

Redirects the remainder of the standard error stream.

You may specify the stream as an IO or IO-like object, or as a file specified by its path. If specifying a file, you may optionally provide the mode and permissions for the call to `File#open`.

After calling this, do not interact directly with the stream.

@param io [IO,StringIO,String] Where to redirect the stream @param io_args [Object…] The mode and permissions for opening the

file, if redirecting to a file.

@return [self]

# File lib/toys/utils/exec.rb, line 700
def redirect_err(io, *io_args)
  redirect(:err, io, *io_args)
end
redirect_in(io, *io_args) click to toggle source

Redirects the remainder of the standard input stream.

You may specify the stream as an IO or IO-like object, or as a file specified by its path. If specifying a file, you may optionally provide the mode and permissions for the call to `File#open`. You can also specify the value `:null` to indicate the null file.

After calling this, do not interact directly with the stream.

@param io [IO,StringIO,String,:null] Where to redirect the stream @param io_args [Object…] The mode and permissions for opening the

file, if redirecting from a file.

@return [self]

# File lib/toys/utils/exec.rb, line 663
def redirect_in(io, *io_args)
  redirect(:in, io, *io_args)
end
redirect_out(io, *io_args) click to toggle source

Redirects the remainder of the standard output stream.

You may specify the stream as an IO or IO-like object, or as a file specified by its path. If specifying a file, you may optionally provide the mode and permissions for the call to `File#open`. You can also specify the value `:null` to indicate the null file.

After calling this, do not interact directly with the stream.

@param io [IO,StringIO,String,:null] Where to redirect the stream @param io_args [Object…] The mode and permissions for opening the

file, if redirecting to a file.

@return [self]

# File lib/toys/utils/exec.rb, line 682
def redirect_out(io, *io_args)
  redirect(:out, io, *io_args)
end
result(timeout: nil) click to toggle source

Wait for the subcommand to complete, and return a result object.

Closes the control streams if present. The stdin stream is always closed, even if the call times out. The stdout and stderr streams are closed only after the command terminates.

@param timeout [Numeric,nil] The timeout in seconds, or `nil` to

wait indefinitely.

@return [Toys::Utils::Exec::Result] The result object @return [nil] if a timeout occurred.

# File lib/toys/utils/exec.rb, line 738
def result(timeout: nil)
  close_streams(:in)
  return nil if @wait_thread && !@wait_thread.join(timeout)
  @result ||= begin
    close_streams(:out)
    @join_threads.each(&:join)
    Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
          .tap { |result| @result_callback&.call(result) }
  end
end
signal(sig)
Alias for: kill

Private Instance Methods

stream_for(which, allow_in: false) click to toggle source
# File lib/toys/utils/exec.rb, line 762
def stream_for(which, allow_in: false)
  stream = nil
  case which
  when :out
    stream = @out
    @out = nil
  when :err
    stream = @err
    @err = nil
  when :in
    if allow_in
      stream = @in
      @in = nil
    end
  else
    raise ::ArgumentError, "Unknown stream #{which}"
  end
  raise ::ArgumentError, "Stream #{which} not available" unless stream
  stream
end