class StdinResponder

Attributes

rules[R]

Public Class Methods

new(merge_stderr: false, prompt_delay_threshold: 1.0, timeout: 120, verbose: true, debug: false) click to toggle source
# File lib/stdin_responder.rb, line 46
def initialize(merge_stderr: false, prompt_delay_threshold: 1.0, timeout: 120, verbose: true, debug: false)
  @rules = []
  @stdout_buffer = ""
  @stderr_buffer = merge_stderr ? @stdout_buffer : ""
  @stdin_buffer = ""
  @prompt_delay_threshold = prompt_delay_threshold
  @timeout = timeout
  @verbose = verbose
  @debug = debug
end

Public Instance Methods

add_rule(rule) click to toggle source
# File lib/stdin_responder.rb, line 57
def add_rule(rule)
  @rules << {default: "", repeat: 0}.merge(rule)
end
run(command) click to toggle source
# File lib/stdin_responder.rb, line 61
def run(command)
  @session_rules = @rules.dup
  pid, stdin, stdout, stderr = Open4.popen4(command)

  out_buffer = ""
  prompt_threshold = 1

  @stdout_thread = Thread.start do
    monitor_stdout(stdout)
  end

  @stderr_thread = Thread.start do
    monitor_stderr(stderr)
  end

  @stdin_thread = Thread.start do
    generate_responses(stdin)
  end

  master_thread = Thread.start do
    monitor_threads
  end

  @stdout_thread.abort_on_exception = true
  @stderr_thread.abort_on_exception = true
  @stdin_thread.abort_on_exception = true
  master_thread.abort_on_exception = true

  @stdout_thread.join
  @stderr_thread.join
  stdout.close
  stderr.close

  @stdin_thread.join
  stdin.close
end

Private Instance Methods

abort_threads() click to toggle source
# File lib/stdin_responder.rb, line 177
def abort_threads
  @stdout_thread.kill
  @stderr_thread.kill
  @stdin_thread.kill
end
consume_stdout() click to toggle source
# File lib/stdin_responder.rb, line 204
def consume_stdout
  if !@stdout_buffer.empty?
    output = @stdout_buffer.dup
    @stdout_buffer.clear
    return output
  else
    return ""
  end
end
determine_response(rule, read_buffer) click to toggle source
# File lib/stdin_responder.rb, line 183
def determine_response(rule, read_buffer)
  # determine which rule key, if any match the current read_buffer
  return if rule.nil?
  matcher, responder = rule.find do |matcher, responder|
    case matcher
    when Regexp then read_buffer =~ matcher
    when Proc then matcher.call(read_buffer)
    when String then read_buffer.split("\n").last == matcher
    end
  end || [:default, rule[:default]]

  # and generate the response accordingly
  response = case responder
  when Proc then responder.call(read_buffer)
  when :wait, :skip, :abort then responder
  else responder.to_s
  end

  return response
end
generate_responses(instream) click to toggle source
# File lib/stdin_responder.rb, line 118
def generate_responses(instream)
  t0 = Time.now
  read_buffer = ""

  while @stdout_thread.alive? do
    current_output = consume_stdout
    if !current_output.empty?
      # Found new output, put it in our read_buffer and reset the timer
      t0 = Time.now
      print current_output if @verbose
      read_buffer << current_output
    elsif Time.now - t0 > @prompt_delay_threshold
      # No new input and we've been waiting long enough to respond
      rule = next_rule
      puts "dt = #{Time.now - t0}, applying #{rule}" if @debug
      response = determine_response(rule, read_buffer)
      puts "Response: #{response.inspect}" if @debug
      case response
      when nil then nil
      when :skip then next
      when :wait
        t0 = Time.now
        @session_rules.unshift(rule.merge(repeat: 0))
      when :abort
        $stderr.puts "^Abort!" if @verbose
        abort_threads
        break
      else
        t0 = Time.now
        puts "#{response}" if @verbose
        instream.puts response
        read_buffer = ""
      end
    else
      nil # Be patient, wait longer.
    end

    # Abort if we exceed the timeout
    if Time.now - t0 > @timeout
      $stderr.puts "Timeout: abort!" if @verbose
      abort_threads
    end

    # Wait a little
    sleep(0.1)
  end

  # For completion, read the rest of the output
  read_buffer << consume_stdout
  puts read_buffer
end
monitor_stderr(outstream) click to toggle source
# File lib/stdin_responder.rb, line 106
def monitor_stderr(outstream)
  outstream.each_char do |c|
    @stderr_buffer << c
  end
end
monitor_stdout(outstream) click to toggle source
# File lib/stdin_responder.rb, line 100
def monitor_stdout(outstream)
  outstream.each_char do |c|
    @stdout_buffer << c
  end
end
monitor_threads() click to toggle source
# File lib/stdin_responder.rb, line 112
def monitor_threads
  if ![@stdout_thread, @stderr_thread, @stdin_thread].all?(&:alive?)
    abort_threads
  end
end
next_rule() click to toggle source
# File lib/stdin_responder.rb, line 170
def next_rule
  rule = @session_rules.first or return
  rule = rule.dup
  rule[:repeat] <= 0 ? @session_rules.shift : @session_rules.first[:repeat] -= 1
  return rule
end