Class: RubyExpect::Expect

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_expect/expect.rb

Overview

This is the main class used to interact with IO objects An Expect object can be used to send and receive data on any read/write IO object.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, &block) ⇒ Expect

Create a new Expect object for the given IO object

There are two ways to create a new Expect object. The first is to supply a single IO object with a read/write mode. The second method is to supply a read file handle as the first argument and a write file handle as the second argument.

args

at most 3 arguments, 1 or 2 IO objects (read/write or read + write and an optional options hash. The only currently supported option is :debug (default false) which, if enabled, will send data received on the input filehandle to STDOUT

block

An optional block called upon initialization. See procedure

Examples

# expect with a read/write filehandle
exp = Expect.new(rwfh)

# expect with separate read and write filehandles
exp = Expect.new(rfh, wfh)

# turning on debugging
exp = Expect.new(rfh, wfh, :debug => true)

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ruby_expect/expect.rb', line 75

def initialize *args, &block
  options = {}
  if (args.last.is_a?(Hash))
    options = args.pop
  end

  raise ArgumentError("First argument must be an IO object") unless (args[0].is_a?(IO))
  if (args.size == 1)
    @write_fh = args.shift
    @read_fh = @write_fh
  elsif (args.size == 2)
    raise ArgumentError("Second argument must be an IO object") unless (args[1].is_a?(IO))
    @write_fh = args.shift
    @read_fh = args.shift
  else
    raise ArgumentError.new("either specify a read/write IO object, or a read IO object and a write IO object")
  end
  
  raise "Input file handle is not readable!" unless (@read_fh.stat.readable?)
  raise "Output file handle is not writable!" unless (@write_fh.stat.writable?)

  @child_pid = options[:child_pid]
  @debug = options[:debug] || false
  @buffer = ''
  @before = ''
  @match = ''
  @timeout = 0

  unless (block.nil?)
    procedure(&block)
  end
end

Instance Attribute Details

#beforeObject (readonly)

Any data that was in the accumulator buffer before match in the last expect call if the last call to expect resulted in a timeout, then before is an empty string


32
33
34
# File 'lib/ruby_expect/expect.rb', line 32

def before
  @before
end

#bufferObject (readonly)

The accumulator buffer populated by read_loop. Only access this if you really know what you are doing!


42
43
44
# File 'lib/ruby_expect/expect.rb', line 42

def buffer
  @buffer
end

#debugObject

Set debug in order to see the output being read from the spawned process


45
46
47
# File 'lib/ruby_expect/expect.rb', line 45

def debug
  @debug
end

#last_matchObject (readonly)

The MatchData object from the last expect call or nil upon a timeout


38
39
40
# File 'lib/ruby_expect/expect.rb', line 38

def last_match
  @last_match
end

#matchObject (readonly)

The exact string that matched in the last expect call


35
36
37
# File 'lib/ruby_expect/expect.rb', line 35

def match
  @match
end

Class Method Details

.connect(socket, options = {}, &block) ⇒ Object

Connect to a socket

command

The socket or file to connect to

block

Optional block to call and run a procedure in


133
134
135
136
137
138
139
140
141
142
# File 'lib/ruby_expect/expect.rb', line 133

def self.connect socket, options = {}, &block
  require 'socket'
  client = nil
  if (socket.is_a?(UNIXSocket))
   client = socket
  else
   client = UNIXSocket.new(socket)
  end
  return RubyExpect::Expect.new(client, options, &block)
end

.spawn(command, options = {}, &block) ⇒ Object

Spawn a command and interact with it

command

The command to execute

block

Optional block to call and run a procedure in


117
118
119
120
121
# File 'lib/ruby_expect/expect.rb', line 117

def self.spawn command, options = {}, &block
  shell_in, shell_out, pid = PTY.spawn(command)
  options[:child_pid] = pid
  return RubyExpect::Expect.new(shell_out, shell_in, options, &block)
end

Instance Method Details

#expect(*patterns, &block) ⇒ Object

Wait until either the timeout occurs or one of the given patterns is seen in the input. Upon a match, the property before is assigned all input in the accumulator before the match, the matched string itself is assigned to the match property and an optional block is called

The method will return the index of the matched pattern or nil if no match has occurred during the timeout period

patterns

list of patterns to look for. These can be either literal strings or Regexp objects

block

An optional block to be called if one of the patterns matches

Example

exp = Expect.new(io)
exp.expect('Password:') do
  send("12345")
end

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/ruby_expect/expect.rb', line 229

def expect *patterns, &block
  patterns = pattern_escape(*patterns)
  @end_time = 0
  if (@timeout != 0)
    @end_time = Time.now + @timeout
  end

  @before = ''
  matched_index = nil
  while (@end_time == 0 || Time.now < @end_time)
    return nil if (@read_fh.closed?)
    break unless (read_proc)
    @last_match = nil
    patterns.each_index do |i|
      if (match = patterns[i].match(@buffer))
        @last_match = match
        @before = @buffer.slice!(0...match.begin(0))
        @match = @buffer.slice!(0...match.to_s.length)
        matched_index = i
        break
      end
    end
    unless (@last_match.nil?)
      unless (block.nil?)
        instance_eval(&block)
      end
      return matched_index
    end
  end
  return nil
end

#procedure(&block) ⇒ Object

Perform a series of 'expects' using the DSL defined in Procedure

block

The block will be called in the context of a new Procedure object

Example

exp = Expect.new(io)
exp.procedure do
 each do
   expect /first expected line/ do
     send "some text to send"
   end

   expect /second expected line/ do
     send "some more text to send"
   end
 end
end

165
166
167
# File 'lib/ruby_expect/expect.rb', line 165

def procedure &block
  RubyExpect::Procedure.new(self, &block)
end

#send(command) ⇒ Object

Convenience method that will send a string followed by a newline to the write handle of the IO object

command

String to send down the pipe


202
203
204
# File 'lib/ruby_expect/expect.rb', line 202

def send command
  @write_fh.write("#{command}\n")
end

#soft_closeObject

Wait for the process to complete or the read handle to be closed and then clean everything up. This method call will block until the spawned process or connected filehandle/socket is closed


266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/ruby_expect/expect.rb', line 266

def soft_close
  while (! @read_fh.closed?)
    read_proc
  end
  @read_fh.close unless (@read_fh.closed?)
  @write_fh.close unless (@write_fh.closed?)
  if (@child_pid)
    Process.wait(@child_pid)
    return $?
  end
  return true
end

#timeoutObject

Get the current timeout value


191
192
193
# File 'lib/ruby_expect/expect.rb', line 191

def timeout
  @timeout
end

#timeout=(timeout) ⇒ Object

Set the time to wait for an expected pattern

timeout

number of seconds to wait before giving up. A value of zero means wait forever


176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ruby_expect/expect.rb', line 176

def timeout= timeout
  unless (timeout.is_a?(Integer))
    raise "Timeout must be an integer"
  end
  unless (timeout >= 0)
    raise "Timeout must be greater than or equal to zero"
  end

  @timeout = timeout
  @end_time = 0
end