class Expectr
Public: Expectr
is an API to the functionality of Expect (see expect.nist.gov) implemented in ruby.
Expectr
contrasts with Ruby’s built-in expect.rb by avoiding tying in with the IO class in favor of creating a new object entirely to allow for more granular control over the execution and display of the program being run.
Examples
# SSH Login to another machine exp = Expectr.new('ssh user@example.com') exp.expect("Password:") exp.send('password') exp.interact!(blocking: true) # See if a web server is running on the local host, react accordingly exp = Expectr.new('netstat -ntl|grep ":80 " && echo "WEB"', timeout: 1) if exp.expeect("WEB") # Do stuff if we see 'WEB' in the output else # Do other stuff end
Constants
- DEFAULT_BUFFER_SIZE
- DEFAULT_CONSTRAIN
- DEFAULT_FLUSH_BUFFER
- DEFAULT_TIMEOUT
- VERSION
Attributes
Public: Returns the active buffer to match against
Public: Gets/sets the number of bytes to use for the internal buffer
Public: Gets/sets whether to constrain the buffer to the buffer size
Public: Returns the buffer discarded by the latest call to Expectr#expect
Public: Gets/sets whether to flush program output to $stdout
Public: Returns the PID of the running process
Public: Gets/sets the number of seconds a call to Expectr#expect
may last
Public Class Methods
Public: Initialize a new Expectr
object. Spawns a sub-process and attaches to STDIN and STDOUT for the new process.
cmd_args - This may be either a Hash containing arguments (described below)
or a String or File Object referencing the application to launch (assuming Child interface). This argument, if not a Hash, will be changed into the Hash { cmd: cmd_args }. This argument will be merged with the args Hash, overriding any arguments specified there. This argument is kept around for the sake of backward compatibility with extant Expectr scripts and may be deprecated in the future. (default: {})
args - A Hash used to specify options for the instance. (default: {}):
:timeout - Number of seconds that a call to Expectr#expect has to complete (default: 30) :flush_buffer - Whether to flush output of the process to the console (default: true) :buffer_size - Number of bytes to attempt to read from sub-process at a time. If :constrain is true, this will be the maximum size of the internal buffer as well. (default: 8192) :constrain - Whether to constrain the internal buffer from the sub-process to :buffer_size characters. (default: false) :interface - Interface Object to use when instantiating the new Expectr object. (default: Child)
# File lib/expectr.rb, line 82 def initialize(cmd_args = '', args = {}) setup_instance parse_options(args) cmd_args = { cmd: cmd_args } unless cmd_args.is_a?(Hash) args.merge!(cmd_args) unless [:lambda, :adopt, :child].include?(args[:interface]) args[:interface] = :child end self.extend self.class.const_get(args[:interface].capitalize) init_interface(args) Thread.new { output_loop } end
Public Instance Methods
Public: Clear output buffer.
Returns nothing.
# File lib/expectr.rb, line 227 def clear_buffer! @out_mutex.synchronize do @buffer.clear end end
Public: Begin a countdown and search for a given String or Regexp in the output buffer, optionally taking further action based upon which, if any, match was found.
pattern - Object String or Regexp representing pattern for which to
search, or a Hash containing pattern -> Proc mappings to be used in cases where multiple potential patterns should map to distinct actions.
recoverable - Denotes whether failing to match the pattern should cause the
method to raise an exception (default: false)
Examples
exp.expect("this should exist") # => MatchData exp.expect("this should exist") do # ... end exp.expect(/not there/) # Raises Timeout::Error exp.expect(/not there/, true) # => nil hash = { "First possibility" => -> { puts "option a" }, "Second possibility" => -> { puts "option b" }, default: => -> { puts "called on timeout" } } exp.expect(hash)
Returns a MatchData object once a match is found if no block is given Yields the MatchData object representing the match Raises TypeError if something other than a String or Regexp is given Raises Timeout::Error if a match isn’t found in time, unless recoverable
# File lib/expectr.rb, line 177 def expect(pattern, recoverable = false) return expect_procmap(pattern) if pattern.is_a?(Hash) match = nil pattern = Regexp.new(Regexp.quote(pattern)) if pattern.is_a?(String) unless pattern.is_a?(Regexp) raise(TypeError, Errstr::EXPECT_WRONG_TYPE) end match = watch_match(pattern, recoverable) block_given? ? yield(match) : match end
Public: Begin a countdown and search for any of multiple possible patterns, performing designated actions upon success/failure.
pattern_map - Hash containing mappings between Strings or Regexps and
procedure objects. Additionally, an optional action, designated by :default or :timeout may be provided to specify an action to take upon failure.
Examples
exp.expect_procmap({ "option 1" => -> { puts "action 1" }, /option 2/ => -> { puts "action 2" }, :default => -> { puts "default" } })
Calls the procedure associated with the pattern provided.
# File lib/expectr.rb, line 207 def expect_procmap(pattern_map) pattern_map, pattern, recoverable = process_procmap(pattern_map) match = nil match = watch_match(pattern, recoverable) pattern_map.each do |s,p| if s.is_a?(Regexp) return p.call if s.match(match.to_s) end end pattern_map[:default].call unless pattern_map[:default].nil? pattern_map[:timeout].call unless pattern_map[:timeout].nil? nil end
Public: Allow direct control of the running process from the controlling terminal, acting as a pass-through for the life of the process (or until the leave! method is called).
args - A Hash used to specify options to be used for interaction.
(default: {}): :flush_buffer - explicitly set @flush_buffer to the value specified :blocking - Whether to block on this call or allow code execution to continue (default: false)
Returns the interaction Thread, calling join on it if :blocking is true.
# File lib/expectr.rb, line 110 def interact!(args = {}) if @interact raise(ProcessError, Errstr::ALREADY_INTERACT) end @flush_buffer = args[:flush_buffer].nil? ? true : args[:flush_buffer] args[:blocking] ? interact_thread.join : interact_thread end
Public: Report whether or not current Expectr
object is in interact mode.
Returns a boolean.
# File lib/expectr.rb, line 122 def interact? @interact end
Public: Cause the current Expectr
object to leave interact mode.
Returns nothing.
# File lib/expectr.rb, line 129 def leave! @interact=false end
Public: Wraps Expectr#send, appending a newline to the end of the string.
str - String to be sent to the active process. (default: ”)
Returns nothing.
# File lib/expectr.rb, line 138 def puts(str = '') send str + "\n" end
Private Instance Methods
Internal: Check for a match against a given pattern until a match is found. This method should be wrapped in a Timeout block or otherwise have some mechanism to break out of the loop.
pattern - String or Regexp containing the pattern for which to watch.
Returns a MatchData object containing the match found.
# File lib/expectr.rb, line 314 def check_match(pattern) match = nil @thread = Thread.current while match.nil? @out_mutex.synchronize do match = pattern.match(@buffer) if match.nil? raise Timeout::Error if @pid.zero? @out_mutex.sleep end end end match ensure @thread = nil end
Internal: Encode a String twice to force UTF-8 encoding, dropping problematic characters in the process.
buf - String to be encoded.
Returns the encoded String.
# File lib/expectr.rb, line 252 def force_utf8(buf) return buf if buf.valid_encoding? buf.force_encoding('ISO-8859-1').encode('UTF-8', 'UTF-8', replace: nil) end
Internal: Initialize instance variables based upon arguments provided.
args - A Hash used to specify options for the new object (default: {}):
:timeout - Number of seconds that a call to Expectr#expect has to complete. :flush_buffer - Whether to flush output of the process to the console. :buffer_size - Number of bytes to attempt to read from sub-process at a time. If :constrain is true, this will be the maximum size of the internal buffer as well. :constrain - Whether to constrain the internal buffer from the sub-process to :buffer_size.
Returns nothing.
# File lib/expectr.rb, line 271 def parse_options(args) @timeout = args[:timeout] || DEFAULT_TIMEOUT @buffer_size = args[:buffer_size] || DEFAULT_BUFFER_SIZE @constrain = args[:constrain] || DEFAULT_CONSTRAIN @flush_buffer = args[:flush_buffer] @flush_buffer = DEFAULT_FLUSH_BUFFER if @flush_buffer.nil? end
Internal: Print buffer to $stdout if program output is expected to be echoed.
buf - String to be printed to $stdout.
Returns nothing.
# File lib/expectr.rb, line 241 def print_buffer(buf) $stdout.print buf if @flush_buffer $stdout.flush unless $stdout.sync end
Internal: Handle data from the interface, forcing UTF-8 encoding, appending it to the internal buffer, and printing it to $stdout if appropriate.
Returns nothing.
# File lib/expectr.rb, line 294 def process_output(buf) force_utf8(buf) print_buffer(buf) @out_mutex.synchronize do @buffer << buf if @constrain && @buffer.length > @buffer_size @buffer = @buffer[-@buffer_size..-1] end @thread.wakeup if @thread end end
Internal: Process a pattern to procedure mapping, producing a sanitized Hash, a unified Regexp and a boolean denoting whether an Exception should be raised upon timeout.
pattern_map - A Hash containing mappings between patterns designated by
either strings or Regexp objects, to procedures. Optionally, either :default or :timeout may be mapped to a procedure in order to designate an action to take upon failure to match any other pattern.
Returns a Hash, Regexp and boolean object.
# File lib/expectr.rb, line 369 def process_procmap(pattern_map) # Normalize Hash keys, allowing only Regexps and Symbols for keys. pattern_map = pattern_map.reduce({}) do |c,e| unless e[0].is_a?(Symbol) || e[0].is_a?(Regexp) e[0] = Regexp.new(Regexp.escape(e[0].to_s)) end c.merge(e[0] => e[1]) end # Separate out non-Symbol keys and build a unified Regexp. regex_keys = pattern_map.keys.select { |e| e.is_a?(Regexp) } pattern = regex_keys.reduce("") do |c,e| c += "|" unless c.empty? c + "(#{e.source})" end recoverable = regex_keys.include?(:default) || regex_keys.include?(:timeout) return pattern_map, pattern, recoverable end
Internal: Initialize instance variables to their default values.
Returns nothing.
# File lib/expectr.rb, line 282 def setup_instance @buffer = '' @discard = '' @thread = nil @out_mutex = Mutex.new @interact = false end
Internal: Watch for a match within the timeout period.
pattern - String or Regexp object containing the pattern for which to
watch.
recoverable - Boolean denoting whether a failure to find a match should be
considered fatal.
Returns a MatchData object if a match was found, or else nil. Raises Timeout::Error if no match is found and recoverable is false.
# File lib/expectr.rb, line 340 def watch_match(pattern, recoverable) match = nil Timeout::timeout(@timeout) do match = check_match(pattern) end @out_mutex.synchronize do @discard = @buffer[0..match.begin(0)-1] @buffer = @buffer[match.end(0)..-1] end match rescue Timeout::Error => details raise(Timeout::Error, details) unless recoverable nil end