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)


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
107
108
109
110
111
112
113
114
115
116
# File 'lib/ruby_expect/expect.rb', line 78

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
  @logger = options[:logger] 
  if @logger.nil?
    @logger = Logger.new(STDERR)
    @logger.level = Logger::FATAL
  end

  @buffer = ''
  @log_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



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

def before
  @before
end

#bufferObject (readonly)

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



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

def buffer
  @buffer
end

#last_matchObject (readonly)

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



40
41
42
# File 'lib/ruby_expect/expect.rb', line 40

def last_match
  @last_match
end

#loggerObject

Any supplied logger will be used both for errors and warnings as well as debug information (I/O when sending and expecting)



48
49
50
# File 'lib/ruby_expect/expect.rb', line 48

def logger
  @logger
end

#matchObject (readonly)

The exact string that matched in the last expect call



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

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



143
144
145
146
147
148
149
150
151
152
# File 'lib/ruby_expect/expect.rb', line 143

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



127
128
129
130
131
# File 'lib/ruby_expect/expect.rb', line 127

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

#debugObject



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

def debug
  warn "`debug` is deprecated.  Use a logger instead"
  @logger.debug?
end

#debug=(debug) ⇒ Object

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



156
157
158
159
160
161
162
163
# File 'lib/ruby_expect/expect.rb', line 156

def debug= debug
  warn "`debug` is deprecated.  Use a logger instead"
  if debug
    @logger.level = Logger::DEBUG
  else
    @logger.level = -1
  end
end

#debug?Boolean

Returns:

  • (Boolean)


170
171
172
173
# File 'lib/ruby_expect/expect.rb', line 170

def debug?
  warn "`debug` is deprecated.  Use a logger instead"
  @logger.debug?
end

#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


260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/ruby_expect/expect.rb', line 260

def expect *patterns, &block
  @logger.debug("Expecting: #{patterns.inspect}") if @logger.debug?
  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)
    raise ClosedError.new("Read filehandle is closed") if (@read_fh.closed?)
    break unless (read_proc)
    @last_match = nil
    patterns.each_index do |i|
      if (match = patterns[i].match(@buffer))
        log_buffer(true)
        @logger.debug("  Matched: #{match}") if @logger.debug?
        @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
  @logger.debug("Timeout")
  return nil
end

#interactObject

Provides the ability to hand control back to the user and allow them to interact with the spawned process.



317
318
319
320
321
322
323
324
325
# File 'lib/ruby_expect/expect.rb', line 317

def interact
  if ($stdin.tty?)
    $stdin.raw do |stdin|
      interact_loop(stdin)
    end
  else
    interact_loop($stdin)
  end
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


196
197
198
# File 'lib/ruby_expect/expect.rb', line 196

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



233
234
235
# File 'lib/ruby_expect/expect.rb', line 233

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



301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/ruby_expect/expect.rb', line 301

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



222
223
224
# File 'lib/ruby_expect/expect.rb', line 222

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



207
208
209
210
211
212
213
214
215
216
217
# File 'lib/ruby_expect/expect.rb', line 207

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