class Shells::ShellBase

Provides a base interface for all shells to build on.

Instantiating this class will raise an error. All shell sessions should inherit this class and override the necessary interface methods.

Attributes

ignore_io_error[RW]

Set to true to ignore IO errors.

input_fifo[RW]
last_exit_code[RW]

Gets the exit code from the last command if it was retrieved.

last_output[RW]

Gets the last time output was received from the shell.

options[RW]

The options provided to this shell.

This hash is read-only.

orig_options[RW]
output[RW]

Gets all of the output contents from the session.

output_stack[RW]

used when the buffer is pushed/popped.

run_flag[RW]
session_exception[RW]

track exceptions raised during session execution.

session_thread[RW]

the thread used to run the session.

stderr[RW]

Gets the STDERR contents from the session.

stdout[RW]

Gets the STDOUT contents from the session.

thread_lock[RW]
wait_for_output[RW]

The character string we are expecting to be echoed back from the shell.

Public Class Methods

new(options = {}, &block) click to toggle source

Initializes the shell with the supplied options.

These options are common to all shells.

prompt

Defaults to “~~#”. Most special characters will be stripped.

retrieve_exit_code

Defaults to false. Can also be true.

on_non_zero_exit_code

Defaults to :ignore. Can also be :raise.

silence_timeout

Defaults to 0. If greater than zero, will raise an error after waiting this many seconds for a prompt.

command_timeout

Defaults to 0. If greater than zero, will raise an error after a command runs for this long without finishing.

unbuffered_input

Defaults to false. If non-false, then input is sent one character at a time, otherwise input is sent in whole strings. If set to :echo, then input is sent one character at a time and the character must be echoed back from the shell before the next character will be sent.

Please check the documentation for each shell class for specific shell options.

# File lib/shells/shell_base/options.rb, line 53
def initialize(options = {}, &block)

  # cannot instantiate a ShellBase
  raise NotImplementedError if self.class == Shells::ShellBase

  raise ArgumentError, '\'options\' must be a hash.' unless options.is_a?(Hash)

  self.options = {
      prompt: '~~#',
      retrieve_exit_code: false,
      on_non_zero_exit_code: :ignore,
      silence_timeout: 0,
      command_timeout: 0,
      unbuffered_input: false
  }.merge( options.inject({}){ |m,(k,v)|  m[k.to_sym] = v; m } )

  self.options[:prompt] = self.options[:prompt]
                          .to_s.strip
                          .gsub('!', '#')
                          .gsub('$', '#')
                          .gsub('\\', '.')
                          .gsub('/', '.')
                          .gsub('"', '-')
                          .gsub('\'', '-')

  self.options[:prompt] = '~~#' if self.options[:prompt] == ''

  raise Shells::InvalidOption, ':on_non_zero_exit_code must be :ignore or :raise.' unless [:ignore, :raise].include?(self.options[:on_non_zero_exit_code])

  validate_options
  self.options.freeze   # no more changes to options now.
  self.orig_options = self.options  # sort of, we might provide helpers (like +change_quit+)

  run_hook :on_init

  # allow for backwards compatibility.
  if block_given?
    run &block
  end

end
on_debug(proc = nil, &block) click to toggle source

Sets the code to be run when debug messages are processed.

The code will receive the debug message as an argument.

on_debug do |msg|
  puts msg
end
# File lib/shells/shell_base/debug.rb, line 13
def self.on_debug(proc = nil, &block)
  add_hook :on_debug, proc, &block
end

Protected Class Methods

add_hook(hook_name, proc = nil, &block) click to toggle source

Adds a hook method to the class.

A hook method should return :break if it wants to cancel executing any other hook methods in the chain.

# File lib/shells/shell_base/hooks.rb, line 24
def self.add_hook(hook_name, proc = nil, &block) #:doc:
  hooks[hook_name] ||= []

  if proc.respond_to?(:call)
    hooks[hook_name] << proc
  elsif proc.is_a?(Symbol) || proc.is_a?(String)
    if self.respond_to?(proc, true)
      hooks[hook_name] << method(proc.to_sym)
    end
  elsif proc
    raise ArgumentError, 'proc must respond to :call method or be the name of a static method in this class'
  end

  if block
    hooks[hook_name] << block
  end

end
debug(msg) click to toggle source

Processes a debug message.

# File lib/shells/shell_base/debug.rb, line 21
def self.debug(msg) #:doc:
  run_static_hook :on_debug, msg
end
on_exception(proc = nil, &block) click to toggle source

Adds code to be run when an exception occurs.

This code will receive the shell as the first argument and the exception as the second. If it handles the exception it should return :break.

on_exception do |shell, ex|
  if ex.is_a?(MyExceptionType)
    ...
    :break
  else
    false
  end
end

You can also pass the name of a static method.

def self.some_exception_handler(shell, ex)
  ...
end

on_exception :some_exception_handler
# File lib/shells/shell_base/run.rb, line 182
def self.on_exception(proc = nil, &block)
  add_hook :on_exception, proc, &block
end
run_static_hook(hook_name, *args) click to toggle source

Runs a hook statically.

The arguments supplied are passed to the hook methods directly.

Return false unless the hook was executed. Returns :break if one of the hook methods returns :break.

# File lib/shells/shell_base/hooks.rb, line 51
def self.run_static_hook(hook_name, *args)
  list = all_hooks(hook_name)
  list.each do |hook|
    result = hook.call(*args)
    return :break if result == :break
  end
  list.any?
end

Private Class Methods

all_hooks(name) click to toggle source
# File lib/shells/shell_base/hooks.rb, line 14
def self.all_hooks(name)
  parent_hooks(name) + (hooks[name] || [])
end
hooks() click to toggle source
# File lib/shells/shell_base/hooks.rb, line 6
def self.hooks
  @hooks ||= {}
end
parent_hooks(name) click to toggle source
# File lib/shells/shell_base/hooks.rb, line 10
def self.parent_hooks(name)
  superclass.respond_to?(:all_hooks, true) ? superclass.send(:all_hooks, name) : []
end

Public Instance Methods

change_quit(quit_command) click to toggle source

Allows you to change the :quit option inside of a session.

This is useful if you need to change the quit command for some reason. e.g. - Changing the command to “reboot”.

Returns the shell instance.

# File lib/shells/shell_base/options.rb, line 103
def change_quit(quit_command)
  raise Shells::NotRunning unless running?
  self.options = options.dup.merge( quit: quit_command ).freeze
  self
end
exec(command, options = {}, &block) click to toggle source

Executes a command during the shell session.

If called outside of the new block, this will raise an error.

The command is the command to execute in the shell.

The options can be used to override the exit code behavior. In all cases, the :default option is the same as not providing the option and will cause exec to inherit the option from the shell's options.

retrieve_exit_code

This can be one of :default, true, or false.

on_non_zero_exit_code

This can be on ot :default, :ignore, or :raise.

silence_timeout

This can be :default or the number of seconds to wait in silence before timing out.

command_timeout

This can be :default or the maximum number of seconds to wait for a command to finish before timing out.

If provided, the block is a chunk of code that will be processed every time the shell receives output from the program. If the block returns a string, the string will be sent to the shell. This can be used to monitor processes or monitor and interact with processes. The block is optional.

shell.exec('sudo -p "password:" nginx restart') do |data,type|
  return 'super-secret' if /password:$/.match(data)
  nil
end
# File lib/shells/shell_base/exec.rb, line 51
def exec(command, options = {}, &block)
  raise Shells::NotRunning unless running?

  options ||= {}
  options = { timeout_error: true, get_output: true }.merge(options)
  options = self.options.merge(options.inject({}) { |m,(k,v)| m[k.to_sym] = v; m })
  options[:retrieve_exit_code] = self.options[:retrieve_exit_code] if options[:retrieve_exit_code] == :default
  options[:on_non_zero_exit_code] = self.options[:on_non_zero_exit_code] unless [:raise, :ignore].include?(options[:on_non_zero_exit_code])
  options[:silence_timeout] = self.options[:silence_timeout] if options[:silence_timeout] == :default
  options[:command_timeout] = self.options[:command_timeout] if options[:command_timeout] == :default
  options[:command_is_echoed] = true if options[:command_is_echoed].nil?
  ret = ''

  merge_local_buffer do
    begin
      # buffer while also passing data to the supplied block.
      if block_given?
        buffer_output(&block)
      end

      command = command.to_s

      # send the command and wait for the prompt to return.
      debug 'Queueing command: ' + command
      queue_input command + line_ending
      if wait_for_prompt(options[:silence_timeout], options[:command_timeout], options[:timeout_error])
        # get the output of the command, minus the trailing prompt.
        ret =
            if options[:get_output]
              debug 'Reading output of command...'
              command_output command, options[:command_is_echoed]
            else
              ''
            end

        if options[:retrieve_exit_code]
          self.last_exit_code = get_exit_code
          if options[:on_non_zero_exit_code] == :raise
            raise NonZeroExitCode.new(last_exit_code) unless last_exit_code == 0 || last_exit_code == :undefined
          end
        else
          self.last_exit_code = nil
        end
      else
        # A timeout occurred and timeout_error was set to false.
        self.last_exit_code = :timeout
        ret = output
      end

    ensure
      # return buffering to normal.
      if block_given?
        buffer_output
      end

    end
  end

  ret
end
exec_for_code(command, options = {}, &block) click to toggle source

Executes a command specifically for the exit code.

Does not return the output of the command, only the exit code.

# File lib/shells/shell_base/exec.rb, line 116
def exec_for_code(command, options = {}, &block)
  options = (options || {}).merge(retrieve_exit_code: true, on_non_zero_exit_code: :ignore)
  exec command, options, &block
  last_exit_code
end
exec_ignore_code(command, options = {}, &block) click to toggle source

Executes a command ignoring any exit code.

Returns the output of the command and does not even retrieve the exit code.

# File lib/shells/shell_base/exec.rb, line 126
def exec_ignore_code(command, options = {}, &block)
  options = (options || {}).merge(retrieve_exit_code: false, on_non_zero_exit_code: :ignore)
  exec command, options, &block
end
inspect() click to toggle source
# File lib/shells/shell_base.rb, line 13
def inspect
  "#<#{self.class}:0x#{object_id.to_s(16).rjust(12,'0')} #{options.reject{|k,v| k == :password}.inspect}>"
end
line_ending() click to toggle source

Defines the line ending used to terminate commands sent to the shell.

The default is “n”. If you need “rn”, “r”, or some other value, simply override this function.

# File lib/shells/shell_base/input.rb, line 23
def line_ending
  "\n"
end
read_file(path) click to toggle source

Reads from a file on the device.

# File lib/shells/shell_base/interface.rb, line 135
def read_file(path)
  nil
end
run(&block) click to toggle source

Runs a shell session.

The block provided will be run asynchronously with the shell.

Returns the shell instance.

# File lib/shells/shell_base/run.rb, line 55
def run(&block)
  sync do
    raise Shells::AlreadyRunning if running?
    self.run_flag = true
  end

  begin
    run_hook :on_before_run

    debug 'Connecting...'
    connect

    debug 'Starting output buffering...'
    buffer_output

    debug 'Starting session thread...'
    self.session_thread = Thread.start(self) do |sh|
      begin
        begin
          debug 'Executing setup...'
          sh.instance_eval { setup }
          debug 'Executing block...'
          block.call sh
        ensure
          debug 'Executing teardown...'
          sh.instance_eval { teardown }
        end
      rescue Shells::QuitNow
        # just exit the session.
      rescue =>e
        # if the exception is handled by the hook no further processing is required, otherwise we store the exception
        # to propagate it in the main thread.
        unless sh.run_hook(:on_exception, e) == :break
          sh.sync { sh.instance_eval { self.session_exception = e } }
        end
      end
    end

    # process the input buffer while the thread is alive and the shell is active.
    debug 'Entering IO loop...'
    io_loop do
      if active?
        begin
          if session_thread.status    # not dead
            # process input from the session.
            unless wait_for_output
              inp = next_input
              if inp
                send_data inp
                self.wait_for_output = (options[:unbuffered_input] == :echo)
              end
            end

            # continue running the IO loop
            true
          elsif session_exception
            # propagate the exception.
            raise session_exception.class, session_exception.message, session_exception.backtrace
          else
            # the thread has exited, but no exception exists.
            # regardless, the IO loop should now exit.
            false
          end
        rescue IOError
          if ignore_io_error
            # we were (sort of) expecting the IO error, so just tell the IO loop to exit.
            false
          else
            raise
          end
        end
      else
        # the shell session is no longer active, tell the IO loop to exit.
        false
      end
    end
  rescue
    # when an error occurs, try to disconnect, but ignore any further errors.
    begin
      debug 'Disconnecting...'
      disconnect
    rescue
      # ignore
    end
    raise
  else
    # when no error occurs, try to disconnect and propagate any errors (unless we are ignoring IO errors).
    begin
      debug 'Disconnecting...'
      disconnect
    rescue IOError
      raise unless ignore_io_error
    end
  ensure
    # cleanup
    run_hook :on_after_run
    self.run_flag = false
  end

  self
end
running?() click to toggle source

Is the shell currently running?

# File lib/shells/shell_base/run.rb, line 15
def running?
  run_flag
end
write_file(path, data) click to toggle source

Writes to a file on the device.

# File lib/shells/shell_base/interface.rb, line 141
def write_file(path, data)
  false
end

Protected Instance Methods

active?() click to toggle source

Determines if the shell is currently active.

You must define this method in your subclass.

# File lib/shells/shell_base/interface.rb, line 65
def active? #:doc:
  raise ::NotImplementedError
end
buffer_output(&block) click to toggle source

Sets the block to call when data is received.

If no block is provided, then the shell will simply log all output from the program. If a block is provided, it will be passed the data as it is received. If the block returns a string, then that string will be sent to the shell.

This method is called internally in the exec method, but there may be legitimate use cases outside of that method as well.

# File lib/shells/shell_base/output.rb, line 122
def buffer_output(&block) #:doc:
  raise Shells::NotRunning unless running?
  block ||= Proc.new { }
  stdout_received do |data|
    self.last_output = Time.now
    append_stdout strip_ansi_escape(data), &block
  end
  stderr_received do |data|
    self.last_output = Time.now
    append_stderr strip_ansi_escape(data), &block
  end
end
command_output(command, expect_command = true) click to toggle source

Gets the output from a command.

# File lib/shells/shell_base/exec.rb, line 135
def command_output(command, expect_command = true)  #:doc:
  # get everything except for the ending prompt.
  ret =
      if (prompt_pos = (output =~ prompt_match))
        output[0...prompt_pos]
      else
        output
      end

  if expect_command
    command_regex = command_match(command)

    # Go until we run out of data or we find one of the possible command starts.
    # Note that we EXPECT the command to the first line of the output from the command because we expect the
    # shell to echo it back to us.
    result_cmd,_,result_data = ret.partition("\n")
    until result_data.to_s.strip == '' || result_cmd.strip =~ command_regex
      result_cmd,_,result_data = result_data.partition("\n")
    end

    if result_cmd.nil? || !(result_cmd =~ command_regex)
      STDERR.puts "SHELL WARNING: Failed to match #{command_regex.inspect}."
    end

    result_data
  else
    ret
  end
end
connect() click to toggle source

Connects to the shell.

You must define this method in your subclass.

# File lib/shells/shell_base/interface.rb, line 48
def connect #:doc:
  raise ::NotImplementedError
end
debug(msg) click to toggle source

Processes a debug message for an instance.

This is processed synchronously.

# File lib/shells/shell_base/debug.rb, line 29
def debug(msg) #:doc:
  if have_hook?(:on_debug)
    sync { self.class.debug msg }
  end
end
discard_local_buffer() { || ... } click to toggle source

Executes the code block with a local output buffer, discarding the local buffer upon completion.

# File lib/shells/shell_base/output.rb, line 148
def discard_local_buffer(&block) #:doc:
  push_buffer
  begin
    yield
  ensure
    pop_discard_buffer
  end
end
disconnect() click to toggle source

Disconnects from the shell.

You must define this method in your subclass. This method will always be called, even if an exception occurs during the session.

# File lib/shells/shell_base/interface.rb, line 57
def disconnect #:doc:
  raise ::NotImplementedError
end
get_exit_code() click to toggle source

Gets the exit code from the last command.

You must define this method in your subclass to utilize exit codes.

# File lib/shells/shell_base/interface.rb, line 127
def get_exit_code #:doc:
  self.last_exit_code = :undefined
end
have_hook?(hook_name) click to toggle source

Returns true if there are any hooks to run.

# File lib/shells/shell_base/hooks.rb, line 78
def have_hook?(hook_name)
  self.class.all_hooks(hook_name).any?
end
io_loop(&block) click to toggle source

Runs the IO loop on the shell while the block returns true.

You must define this method in your subclass. It should block for as little time as necessary before yielding to the block.

# File lib/shells/shell_base/interface.rb, line 74
def io_loop(&block) #:doc:
  raise ::NotImplementedError
end
merge_local_buffer() { || ... } click to toggle source

Executes the code block with a local output buffer, merging the local buffer into the parent buffer upon completion.

# File lib/shells/shell_base/output.rb, line 137
def merge_local_buffer(&block) #:doc:
  push_buffer
  begin
    yield
  ensure
    pop_merge_buffer
  end
end
queue_input(data) click to toggle source

Adds input to be sent to the shell.

# File lib/shells/shell_base/input.rb, line 31
def queue_input(data) #:doc:
  sync do
    if options[:unbuffered_input]
      data = data.chars
      input_fifo.push *data
    else
      input_fifo.push data
    end
  end
end
run_hook(hook_name, *args) click to toggle source

Runs a hook in the current shell instance.

The hook method is passed the shell as the first argument then the arguments passed to this method.

Return false unless the hook was executed. Returns :break if one of the hook methods returns :break.

# File lib/shells/shell_base/hooks.rb, line 66
def run_hook(hook_name, *args)
  list = self.class.all_hooks(hook_name)
  shell = self
  list.each do |hook|
    result = hook.call(shell, *args)
    return :break if result == :break
  end
  list.any?
end
send_data(data) click to toggle source

Sends data to the shell.

You must define this method in your subclass.

It is important that this method not be called directly outside of the run method. Use queue_input to send data to the shell so that it can be handled in a synchronous manner.

# File lib/shells/shell_base/interface.rb, line 85
def send_data(data) #:doc:
  raise ::NotImplementedError
end
setup() click to toggle source

Sets up the shell session.

This method is called after connecting the shell before the session block is run.

By default this method will wait for the prompt to appear in the output.

If you need to set the prompt, you would want to do it here.

# File lib/shells/shell_base/interface.rb, line 14
def setup #:doc:
  setup_prompt
end
setup_prompt() click to toggle source

Sets up the prompt for the shell session.

By default this method will wait for the prompt to appear in the output.

If you need to set the prompt, you would want to do it here.

# File lib/shells/shell_base/interface.rb, line 24
def setup_prompt #:doc:
  wait_for_prompt 30, 30, true
end
stderr_received(&block) click to toggle source

Register a callback to run when stderr data is received.

The block will be passed the data received.

You must define this method in your subclass and it should set a hook to be called when data is received.

def stderr_received
  @conn.on_stderr do |data|
    yield data
  end
end
# File lib/shells/shell_base/interface.rb, line 119
def stderr_received(&block) #:doc:
  raise ::NotImplementedError
end
stdout_received(&block) click to toggle source

Register a callback to run when stdout data is received.

The block will be passed the data received.

You must define this method in your subclass and it should set a hook to be called when data is received.

def stdout_received
  @conn.on_stdout do |data|
    yield data
  end
end
# File lib/shells/shell_base/interface.rb, line 102
def stdout_received(&block) #:doc:
  raise ::NotImplementedError
end
sync(&block) click to toggle source

Synchronizes actions between shell threads.

# File lib/shells/shell_base/sync.rb, line 18
def sync(&block)
  thread_lock.synchronize &block
end
teardown() click to toggle source

Tears down the shell session.

This method is called after the session block is run before disconnecting the shell.

The default implementation simply sends the quit command to the shell and waits up to 1 second for a result.

This method will be called even if an exception is raised during the session.

# File lib/shells/shell_base/interface.rb, line 37
def teardown #:doc:
  unless options[:quit].to_s.strip == ''
    self.ignore_io_error = true
    exec_ignore_code options[:quit], command_timeout: 1, timeout_error: false
  end
end
temporary_prompt(prompt) { || ... } click to toggle source

Sets the prompt to the value temporarily for execution of the code block.

# File lib/shells/shell_base/prompt.rb, line 128
def temporary_prompt(prompt) #:doc:
  raise Shells::NotRunning unless running?
  old_prompt = prompt_match
  begin
    self.prompt_match = prompt
    yield if block_given?
  ensure
    self.prompt_match = old_prompt
  end
end
validate_options() click to toggle source

Validates the options provided to the class.

You should define this method in your subclass.

# File lib/shells/shell_base/options.rb, line 24
def validate_options #:doc:
  warn "The validate_options() method is not defined on the #{self.class} class."
end
wait_for_prompt(silence_timeout = nil, command_timeout = nil, timeout_error = true) click to toggle source

Waits for the prompt to appear at the end of the output.

Once the prompt appears, new input can be sent to the shell. This is automatically called in exec so you would only need to call it directly if you were sending data manually to the shell.

This method is used internally in the exec method, but there may be legitimate use cases outside of that method as well.

# File lib/shells/shell_base/prompt.rb, line 46
def wait_for_prompt(silence_timeout = nil, command_timeout = nil, timeout_error = true) #:doc:
  raise Shells::NotRunning unless running?

  silence_timeout ||= options[:silence_timeout]
  command_timeout ||= options[:command_timeout]

  # when did we send a NL and how many have we sent while waiting for output?
  nudged_at = nil
  nudge_count = 0

  silence_timeout = silence_timeout.to_s.to_f unless silence_timeout.is_a?(Numeric)
  nudge_seconds =
      if silence_timeout > 0
        (silence_timeout / 3.0)  # we want to nudge twice before officially timing out.
      else
        0
      end

  # if there is a limit for the command timeout, then set the absolute timeout for the loop.
  command_timeout = command_timeout.to_s.to_f unless command_timeout.is_a?(Numeric)
  timeout =
      if command_timeout > 0
        Time.now + command_timeout
      else
        nil
      end

  # loop until the output matches the prompt regex.
  # if something gets output async server side, the silence timeout will be handy in getting the shell to reappear.
  # a match while waiting for output is invalid, so by requiring that flag to be false this should work with
  # unbuffered input as well.
  until output =~ prompt_match && !wait_for_output
    # hint that we need to let another thread run.
    Thread.pass

    last_response = last_output

    # Do we need to nudge the shell?
    if nudge_seconds > 0 && (Time.now - last_response) > nudge_seconds
      nudge_count = (nudged_at.nil? || nudged_at < last_response) ? 1 : (nudge_count + 1)

      # Have we previously nudged the shell?
      if nudge_count > 2  # we timeout on the third nudge.
        raise Shells::SilenceTimeout if timeout_error
        debug ' > silence timeout'
        return false
      else
        nudged_at = Time.now

        queue_input line_ending

        # wait a bit longer...
        self.last_output = nudged_at
      end
    end

    # honor the absolute timeout.
    if timeout && Time.now > timeout
      raise Shells::CommandTimeout if timeout_error
      debug ' > command timeout'
      return false
    end
  end

  # make sure there is a newline before the prompt, just to keep everything clean.
  pos = (output =~ prompt_match)
  if output[pos - 1] != "\n"
    # no newline before prompt, fix that.
    self.output = output[0...pos] + "\n" + output[pos..-1]
  end

  # make sure there is a newline at the end of STDOUT content buffer.
  if stdout[-1] != "\n"
    # no newline at end, fix that.
    self.stdout += "\n"
  end

  true
end

Private Instance Methods

append_stderr(data, &block) click to toggle source
# File lib/shells/shell_base/output.rb, line 94
def append_stderr(data, &block)
  data = reduce_newlines data

  sync do
    self.stderr += data
    self.output += data
  end

  if block_given?
    result = block.call(data, :stderr)
    if result && result.is_a?(String)
      queue_input(result + line_ending)
    end
  end
end
append_stdout(data, &block) click to toggle source
# File lib/shells/shell_base/output.rb, line 70
def append_stdout(data, &block)
  # Combined output gets the prompts,
  # but stdout will be without prompts.
  data = reduce_newlines data
  for_stdout = if (pos = (data =~ prompt_match))
                 data[0...pos]
               else
                 data
               end

  sync do
    self.stdout += for_stdout
    self.output += data
    self.wait_for_output = false
  end

  if block_given?
    result = block.call(for_stdout, :stdout)
    if result && result.is_a?(String)
      queue_input(result + line_ending)
    end
  end
end
command_match(command) click to toggle source
# File lib/shells/shell_base/exec.rb, line 167
def command_match(command)
  p = prompt_match.source[0...-7] # trim off [ \t]*$
  c = regex_escape command
  /\A(?:#{p}\s*)?#{c}[ \t]*\z/
end
next_input() click to toggle source
# File lib/shells/shell_base/input.rb, line 44
def next_input
  sync { input_fifo.shift }
end
pop_discard_buffer() click to toggle source

Pops the buffers and discards the captured output.

This method is used internally in the get_exit_code method, but there may be legitimate use cases outside of that method as well.

# File lib/shells/shell_base/output.rb, line 204
def pop_discard_buffer
  raise Shells::NotRunning unless running?
  # a standard pop discarding current data and retrieving the history.
  debug 'Discarding buffer <<'
  sync do
    hist_stdout, hist_stderr, hist_output = (output_stack.pop || [])
    self.stdout = hist_stdout || ''
    self.stderr = hist_stderr || ''
    self.output = hist_output || ''
  end
end
pop_merge_buffer() click to toggle source

Pops the buffers and merges the captured output.

This method is called internally in the exec method, but there may be legitimate use cases outside of that method as well.

# File lib/shells/shell_base/output.rb, line 181
def pop_merge_buffer
  raise Shells::NotRunning unless running?
  # almost a standard pop, however we want to merge history with current.
  debug 'Merging buffer <<'
  sync do
    hist_stdout, hist_stderr, hist_output = (output_stack.pop || [])
    if hist_stdout
      self.stdout = hist_stdout + stdout
    end
    if hist_stderr
      self.stderr = hist_stderr + stderr
    end
    if hist_output
      self.output = hist_output + output
    end
  end
end
prompt_match() click to toggle source
# File lib/shells/shell_base/prompt.rb, line 6
def prompt_match
  @prompt_match
end
prompt_match=(value) click to toggle source
# File lib/shells/shell_base/prompt.rb, line 10
def prompt_match=(value)
  # allow for trailing spaces or tabs, but no other whitespace.
  @prompt_match =
      if value.nil?
        nil
      elsif value.is_a?(::Regexp)
        value
      else
        /#{regex_escape value.to_s}[ \t]*$/
      end
end
push_buffer() click to toggle source

Pushes the buffers for output capture.

This method is called internally in the exec method, but there may be legitimate use cases outside of that method as well.

# File lib/shells/shell_base/output.rb, line 164
def push_buffer
  raise Shells::NotRunning unless running?
  # push the buffer so we can get the output of a command.
  debug 'Pushing buffer >>'
  sync do
    output_stack.push [ stdout, stderr, output ]
    self.stdout = ''
    self.stderr = ''
    self.output = ''
  end
end
reduce_newlines(data) click to toggle source
# File lib/shells/shell_base/output.rb, line 66
def reduce_newlines(data)
  data.gsub("\r\n", "\n").gsub(" \r", "").gsub("\r", "")
end
regex_escape(text) click to toggle source
# File lib/shells/shell_base/regex_escape.rb, line 5
def regex_escape(text)
  text
      .gsub('\\', '\\\\')
      .gsub('[', '\\[')
      .gsub(']', '\\]')
      .gsub('(', '\\(')
      .gsub(')', '\\)')
      .gsub('.', '\\.')
      .gsub('*', '\\*')
      .gsub('+', '\\+')
      .gsub('?', '\\?')
      .gsub('{', '\\{')
      .gsub('}', '\\}')
      .gsub('$', '\\$')
      .gsub('^', '\\^')
end
strip_ansi_escape(data) click to toggle source
# File lib/shells/shell_base/output.rb, line 55
def strip_ansi_escape(data)
  data
      .gsub(/\e\[(\d+;?)*[ABCDEFGHfu]/, "\n")   #   any of the "set cursor position" CSI commands.
      .gsub(/\e\[=?(\d+;?)*[A-Za-z]/,'')        #   \e[#;#;#A or \e[=#;#;#A  basically all the CSI commands except ...
      .gsub(/\e\[(\d+;"[^"]+";?)+p/, '')        #   \e[#;"A"p
      .gsub(/\e[NOc]./,'?')                     #   any of the alternate character set commands.
      .gsub(/\e[P_\]^X][^\e\a]*(\a|(\e\\))/,'') #   any string command
      .gsub(/[\x00\x08\x0B\x0C\x0E-\x1F]/, '')  #   any non-printable characters (notice \x0A (LF) and \x0D (CR) are left as is).
      .gsub("\t", ' ')                          #   turn tabs into spaces.
end