Parley

Introduction

An expect-like module for Ruby modled after Perl’s Expect.pm

Parley is an implementation of an expect-like API. It is designed to help port away from Perl Expect based applications.

The name “expect” is already well established in ruby and varients of that name are in use by several gems. “parley” was chosen as alternative to yet another expect-varient.

From www.thefreedictionary.com/parley “A discussion or conference, especially one between enemies over terms of truce or other matters.”

See www.nist.gov/el/msid/expect.cfm for references to the original Expect language based on Tcl.

See search.cpan.org/~rgiersig/Expect-1.21/Expect.pod for information on Expect.pm

Duck Type Compatibility

Parley is a module that can be used with any class, like PTY, IO or StringIO that responds to eof(), and either read_nonblock(maxread) or getc().

If the instance is valid for use with Kernel.select(), then Parley will be able to wait for additional input to arrive.

Parley method arguments

The parley() method is called with two arguments:

A call to parley with no arguments should read data until eof? and return :eof.

Each pattern is either:

If an action responds_to?(:call), such as a lambda{|m| code} then the action is called with MatchData as an argument. In the case of :timeout or :eof, MatchData is from matching:

input_buffer =~ /.*/

Examples of Usage

Standard ruby expect vs. equivalent parley usage

In their simplest forms, the two are very similar:

Standard Ruby expect:

require 'expect'
...
input.expect(/pattern/, 10) {|matchdata| code}  # wait up to 10 seconds
input.expect(/pattern/, 0) {|matchdata| code}   # no waiting
input.expect(/pattern/) {|matchdata| code}      # wait for a very long time

Parley:

require 'parley'

...
input.extend Parley # needed if input is not a subclass of IO
...
input.parley(10, [/pattern/, lambda{|matchdata| code}])   # wait up to 10 seconds
input.parley(0, [/pattern/, lambda{|matchdata| code}])    # no waiting
input.parley(nil, [/pattern/, lambda{|matchdata| code}])  # wait forever
input.parley([/pattern/, lambda{|matchdata| code}])       # wait forever

Telnet login using /usr/bin/telnet

See the examples directory for a use of Net::Telnet instead of PTY.spawn(…

require 'parley'
input, output, process_id = PTY.spawn("/usr/bin/telnet localhost")
output.puts '' # hit return to make sure we get some output
result = input.parley(30, [  # allow 30 seconds to login
  [ /ogin:/, lambda{|m| output.puts 'username'; :continue} ],
  [ /ssword:/, lambda{|m| output.puts 'my-secret-password'; :continue} ],
  [ /refused/i, "connection refused" ],
  [ :timeout, "timed out" ],
  [ :eof, "command output closed" ],
  [ /\$/, true ] # some string that only appears in the shell prompt
  ])
if result == true
  puts "Successful login"
  output.puts "date" # This is the important command we had to run
else
  puts "Login failed because: #{result}"
end
# We can keep running commands.
input.close
output.close
id, exit_status = Process.wait2(process_id)

Run your telnet script against canned input

require 'parley'
class StringIO
  include Parley  # or use "input.extend Parley"
end
input = StringIO.new("login: password: prompt$\n", "r")
output = StringIO.new("", "w")
output.puts '' # Note: no effect in this example
result = input.parley(30, [  # Note: timeout has no effect for StringIO
  # XXX check these example patterns against need for anchoring with ^ and/or $
  [ /ogin:/, lambda{|m| output.puts 'username'; :continue} ],
  [ /ssword:/, lambda{|m| output.puts 'my-secret-password'; :continue} ],
  [ :timeout, "timed out" ],
  [ :eof, "command output closed" ],
  [ /\$/, true ] # some string that only appears in the shell prompt
  ])
if result == true
  puts "Successful login"
  output.puts "exit"
else
  puts "Login failed because: #{result}"
end
input.close
output.close
id, exit_status = Process.wait2(process_id)

Handle a timeout condition

require 'parley'
read, write, pid = PTY.spawn("ruby -e 'sleep 20'")
result = read.parley(5, ["timeout, :timeout])
if result == :timeout
  puts "Program timed-out as expected"
else
  puts "Error, timeout did not happen!"
end

Known Issues

## Contributing to parley

Copyright © 2013 Ben Stoltz. See LICENSE.txt for further details.