class Fig::ExternalProgram

Public Class Methods

capture(command_line, input = nil) click to toggle source
# File lib/fig/external_program.rb, line 40
def self.capture(command_line, input = nil)
  output = nil
  errors = nil

  result = popen(*command_line) do
    |stdin, stdout, stderr|

    if ! input.nil?
      stdin.puts input # Potential deadlock if input is bigger than pipe size.
    end
    stdin.close

    output = StringIO.new
    errors = StringIO.new
    input_to_output = {stdout => output, stderr => errors}
    while ! input_to_output.empty?
      ready, * = IO.select input_to_output.keys
      ready.each do
        |handle|

        begin
          # #readpartial() is busted in that it is returning a byte array in
          # a String with a bogus Encoding, even though the bytes may not be
          # valid in the Encoding.
          input_to_output[handle] << handle.readpartial(4096)
        rescue EOFError
          input_to_output.delete handle
        end
      end
    end
  end

  output_string = string_io_byte_array_to_utf8_string output
  errors_string = string_io_byte_array_to_utf8_string errors

  return output_string, errors_string, result
end
popen(*cmd) { |stdin, stdout, stderr| ... } click to toggle source
# File lib/fig/external_program.rb, line 10
def self.popen(*cmd)
  exit_code = nil

  options = {}
  stdin_read, stdin_write = IO.pipe Encoding::UTF_8, Encoding::UTF_8
  options[:in] = stdin_read
  stdin_write.sync = true

  stdout_read, stdout_write = IO.pipe Encoding::UTF_8, Encoding::UTF_8
  options[:out] = stdout_write

  stderr_read, stderr_write = IO.pipe Encoding::UTF_8, Encoding::UTF_8
  options[:err] = stderr_write

  popen_run(
    cmd,
    options,
    [stdin_read, stdout_write, stderr_write],
    [stdin_write, stdout_read, stderr_read],
  ) do
    |stdin, stdout, stderr, wait_thread|

    yield stdin, stdout, stderr

    exit_code = wait_thread.value
  end

  return exit_code
end

Private Class Methods

popen_run(cmd, opts, child_io, parent_io) { || ... } click to toggle source

Stolen from open3.rb

# File lib/fig/external_program.rb, line 81
def self.popen_run(cmd, opts, child_io, parent_io)
  pid = spawn(*cmd, opts)
  wait_thr = Process.detach(pid)
  child_io.each {|io| io.close }
  result = [*parent_io, wait_thr]
  if defined? yield
    begin
      return yield(*result)
    ensure
      parent_io.each{|io| io.close unless io.closed?}
      wait_thr.join
    end
  end
  result
end
string_io_byte_array_to_utf8_string(string_io) click to toggle source

There appears to be no way to convert from a byte array to a UTF-8 string cleanly, with a report of where things are wrong.

# File lib/fig/external_program.rb, line 99
def self.string_io_byte_array_to_utf8_string(string_io)
  string = string_io.string
  string.force_encoding(Encoding::UTF_8)
  if ! string.valid_encoding?
    # Seriously lame, uninformative error message, but, hopefully, this is
    # rare and I don't think I can spend the time to gather the information
    # to make this better right now.
    raise UserInputError.new 'Got invalid UTF-8 input.'
  end

  return string
end