parley 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -15,15 +15,116 @@ See http://www.nist.gov/el/msid/expect.cfm for references to the original Expect
15
15
 
16
16
  See http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod for information on Expect.pm
17
17
 
18
- Known Issues
19
- ------------
18
+ = Parley
19
+ An expect-like module for Ruby modled after Perl's Expect.pm
20
20
 
21
+ Parley is a module that can be used with any class, like PTY, IO or
22
+ StringIO that responds_to?() :eof?, and either :read_nonblock(maxread)
23
+ or :getc.
24
+
25
+ If the class also responds to :select, then Parley will be able to wait
26
+ for additional input to arrive.
27
+
28
+ == parley method arguments
29
+
30
+ The parley() method is called with two arguments:
31
+
32
+ * a timeout in seconds, which may be zero to indicate no timeout
33
+ * an array of arrays, each array contains a pattern and an action.
34
+
35
+ Each pattern is either:
36
+
37
+ * a RegExp to match input data
38
+ * the symbol :timeout to match the timeout condition from select()
39
+ * the symbol :eof to match the eof?() condition
40
+
41
+ If an action responds_to?(:call), such as a lambda{|m| code}
42
+ then the action is called with MatchData as an argument.
43
+ In the case of :timeout or :eof, MatchData is from matching:
44
+ input_buffer =~ /.*/
45
+
46
+ == Examples of Usage
47
+
48
+ === Standard ruby expect vs. equivalent parley usage
49
+ Standard Ruby expect:
50
+ require 'expect'
51
+
52
+ ...
53
+ input.expect(/pattern/, 10) {|matchdata| code}
54
+
55
+ Parley:
56
+ require 'parley'
57
+
58
+ ...
59
+ input.parley(10, [[/pattern/, lambda{|matchdata| code}]])
60
+
61
+ === Telnet login using /usr/bin/telnet
62
+ require 'parley'
63
+ input, output, process_id = PTY.spawn("/usr/bin/telnet localhost")
64
+ output.puts '' # hit return to make sure we get some output
65
+ result = input.parley(30, [ # allow 30 seconds to login
66
+ [ /ogin:/, lambda{|m| output.puts 'username'; :continue} ],
67
+ [ /ssword:/, lambda{|m| output.puts 'my-secret-password'; :continue} ],
68
+ [ /refused/i, "connection refused" ],
69
+ [ :timeout, "timed out" ],
70
+ [ :eof, "command output closed" ],
71
+ [ /\$/, true ] # some string that only appears in the shell prompt
72
+ ])
73
+ if result == true
74
+ puts "Successful login"
75
+ output.puts "date" # This is the important command we had to run
76
+ else
77
+ puts "Login failed because: #{result}"
78
+ end
79
+ # We can keep running commands.
80
+ input.close
81
+ output.close
82
+ id, exit_status = Process.wait2(process_id)
83
+
84
+ === Run your telnet script against canned input
85
+ require 'parley'
86
+ class StringIO
87
+ include Parley
88
+ end
89
+ input = StringIO.new("login: password: prompt$\n", "r")
90
+ output = StringIO.new("", "w")
91
+ output.puts '' # Note: no effect in this example
92
+ result = input.parley(30, [ # Note: timeout has no effect for StringIO
93
+ # XXX check these example patterns against need for anchoring with ^ and/or $
94
+ [ /ogin:/, lambda{|m| output.puts 'username'; :continue} ],
95
+ [ /ssword:/, lambda{|m| output.puts 'my-secret-password'; :continue} ],
96
+ [ :timeout, "timed out" ],
97
+ [ :eof, "command output closed" ],
98
+ [ /\$/, true ] # some string that only appears in the shell prompt
99
+ ])
100
+ if result == true
101
+ puts "Successful login"
102
+ output.puts "exit"
103
+ else
104
+ puts "Login failed because: #{result}"
105
+ end
106
+ input.close
107
+ output.close
108
+ id, exit_status = Process.wait2(process_id)
109
+
110
+ === Handle a timeout condition
111
+ require 'parley'
112
+ read, write, pid = PTY.spawn("ruby -e 'sleep 20'")
113
+ result = read.parley(5, ["timeout, :timeout])
114
+ if result == :timeout
115
+ puts "Program timed-out as expected"
116
+ else
117
+ puts "Error, timeout did not happen!"
118
+ end
119
+
120
+ == Known Issues
121
+
122
+ * :reset_timeout from IO::parley() doesn't have the desired effect, it isn't re-establishing the timeout.
21
123
  * need to generatate adequte documentation. See test/test_parley.rb for now
22
- * :restart_timeout from IO::parley() doesn't have the desired effect, it isn't re-establishing the timeout.
124
+ * line oriented reading option
125
+ * Finer grain greediness control beyond read_nonblock(maxlen)
23
126
 
24
- Contributing to parley
25
- ----------------------
26
-
127
+ == Contributing to parley
27
128
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
28
129
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
29
130
  * Fork the project.
@@ -32,8 +133,7 @@ Contributing to parley
32
133
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
33
134
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
34
135
 
35
- Copyright
36
- ---------
136
+ == Copyright
37
137
 
38
138
  Copyright (c) 2013 Ben Stoltz.
39
- See LICENSE.txt for further details.
139
+ See LICENSE.txt for further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -1,130 +1,143 @@
1
- #!/usr/bin/ruby
2
- #
3
- # = parley.rb - An expect library for Ruby modled after Perl's Expect.pm
4
- #
1
+
5
2
  require 'pty'
6
3
 
7
- # TODO: line oriented reading option
8
- # Greediness control
9
4
  module Parley
10
- # VERSION = '0.1.0'
11
-
5
+ # Internal: used to set input data that has been received, but not yet matched
6
+ #--
12
7
  # Initialize() is usually not called or doesn't call super(), so
13
8
  # do implicit instance variable initialization
9
+ #++
14
10
  def unused_buf= (v)
15
11
  @unused_buf = v
16
12
  end
17
13
 
14
+ # holds the remaining input read from +read_nonblock()+ or +getc()+ but not yet used
18
15
  def unused_buf
19
16
  @unused_buf = nil unless defined? @unused_buf
20
17
  @unused_buf
21
18
  end
22
19
 
23
- def parley_verbose= (v)
24
- @parley_verbose = (v ? true : false)
20
+ # Sets/clears verbose mode to aid debugging.
21
+ #
22
+ # Debug output is sent to +STDOUT+ unless overridden by +pvout+
23
+ def parley_verbose= (truth, pvout = STDOUT)
24
+ @pvout = pvout
25
+ @parley_verbose = (truth ? true : false)
25
26
  end
26
27
 
28
+ # returns +true+ if debug output is enabled, else +false+
27
29
  def parley_verbose
28
30
  @parley_verbose = false unless defined? @parley_verbose
29
31
  @parley_verbose
30
32
  end
31
33
 
32
- def parley_maxread= (v)
33
- @parley_maxread = (v > 0) ? v : 1
34
+ # sets the maximum number of characters to read from +read_nonblock()+
35
+ def parley_maxread= (max_characters = 1)
36
+ @parley_maxread = (max_characters > 0) ? max_characters : 1
34
37
  end
35
38
 
39
+ # return the maximum number of characters to read from +read_nonblock()+
36
40
  def parley_maxread
37
41
  @parley_maxread = 1 unless defined? @parley_maxread
38
42
  @parley_maxread
39
43
  end
40
44
 
41
- def parley (t_out, *actions)
42
- # t_out = nil; # wait forever for next data
43
- # t_out = 0; # timeout immediately if no data available
44
- # t_out > 0; # timeout after t_out seconds
45
- # t_out < 0; # deadline is in the past, same as t_out = 0
46
- STDOUT.print "\n------\nparley t_out=#{t_out}\n" if parley_verbose
47
- if (t_out == nil)
45
+ # Match patterns and conditions and take corresponding actions until an action
46
+ # returns a value not equal to +:continue+ or +:reset_timeout+
47
+ #
48
+ # +timeout_seconds+ specifies the amount of time before the +:timeout+ condition
49
+ # is presented to the pattern/action list.
50
+ #
51
+ # If +timeout_seconds+ is less than or equal to zero, then +:timeout+
52
+ # immediately as soon as there is no more data available.
53
+ #
54
+ # XXX bad. output could spew forever and we want to stop by deadline.
55
+ #
56
+ # If +timeout_seconds+ is nil, then no +:timeout+ condition will be generated.
57
+ #
58
+ # A action returning the value +:reset_timeout+ will +:continue+ and reset
59
+ # the timeout deadline to a value of +Time.now+ + +timeout_seconds+
60
+ def parley (timeout_seconds, *actions)
61
+ @pvout.print "parley,#{__LINE__}: timeout_seconds=#{timeout_seconds}\n" if parley_verbose
62
+ if (timeout_seconds == nil)
48
63
  deadline = nil
49
64
  else
50
- deadline = Time.now + t_out
65
+ deadline = Time.now + timeout_seconds
51
66
  end
52
67
  buf = ''
53
68
  unused_buf = '' if not unused_buf
54
69
 
55
- loop_count = 0
56
-
57
- # STDOUT.puts "class=#{self.class}"
58
-
59
- # Compatible hack. There are changes coming w.r.t. respond_to? for
70
+ # XXX Compatible hack. There are changes coming w.r.t. respond_to? for
60
71
  # protected methods. Just do a simple poll, and see if it works.
61
72
  begin
62
73
  result = IO.select([self], [], [], 0)
63
74
  has_select = true;
64
75
  rescue Exception
65
76
  has_select = false;
66
- # STDOUT.puts "Exception: #{Exception}"
67
77
  end
68
- # STDOUT.puts "has_select=#{has_select}"
69
78
 
70
79
  begin
71
- loop_count = loop_count + 1
72
80
  # If it is possible to wait for data, then wait for data
73
81
  t = (deadline ? (deadline - Time.now) : nil)
74
82
  t = (t.nil? || t >= 0) ? t : 0
83
+ # XXX If maxlen > unused_buf.length, then try to get more input?
84
+ # Think about above. don't want to use up all of timeout.
75
85
  if unused_buf.length == 0 && has_select && !IO.select([self], nil, nil, t)
76
- # Timeout condition returns nil
77
- STDOUT.print "TIMEOUT=\"#{buf}\"\n" if parley_verbose
86
+ # Timeout condition from IO.select() returns nil
87
+ @pvout.print "parley,#{__LINE__}: TIMEOUT buf=\"#{buf}\"\n" if parley_verbose
78
88
  timeout_handled = nil
79
89
  result = nil
80
- result = actions.each do|act|
81
- case act[0]
82
- when :timeout
90
+ result = actions.find do|pattern, action|
91
+ if pattern == :timeout
83
92
  timeout_handled = true
84
- if act[1].respond_to?(:call)
85
- /.*/.match(buf) # get the buffer contents into a Regexp.last_match
86
- result = act[1].call(Regexp.last_match)
93
+ if action.respond_to?(:call)
94
+ r = action.call(/.*/.match(buf)) # call with entire buffer as a MatchData
87
95
  else
88
- result = act[1]
96
+ r = action
89
97
  end
90
- break result
98
+ @pvout.print "parley,#{__LINE__}: TIMEOUT Handled=\"#{r}\"\n" if parley_verbose
99
+ break r
100
+ else
101
+ nil
91
102
  end
92
103
  end
104
+ @pvout.print "parley,#{__LINE__}: TIMEOUT RESULT=\"#{result}\"\n" if parley_verbose
93
105
  if (!timeout_handled)
94
106
  # XXX need to prepend buf to @unusedbuf
95
107
  unused_buf = buf # save data for next time
96
- raise "timeout" # XXX use TimeoutException
108
+ raise "timeout"
97
109
  end
98
- STDOUT.print "TIMEOUT RESULT=\"#{result}\"\n" if parley_verbose
99
110
  return result unless result == :reset_timeout
111
+ matched = true
100
112
  else
101
113
 
102
114
  # We've waited, if that was possible, check for data present
103
115
  if unused_buf.length == 0 && eof?
104
- STDOUT.print "EOF Buffer=\"#{buf}\"\n" if parley_verbose
105
- result = nil
116
+ @pvout.print "parley,#{__LINE__}: EOF Buffer=\"#{buf}\"\n" if parley_verbose
106
117
  eof_handled = false
107
- actions.each do |act|
108
- case act[0]
118
+ result = actions.find do |pattern, action|
119
+ case pattern
109
120
  when :eof
110
121
  eof_handled = true
111
- if act[1].respond_to?(:call)
112
- /.*/m.match(buf) # Game end, match everything remaining
113
- result = act[1].call(Regexp.last_match)
122
+ if action.respond_to?(:call)
123
+ result = action.call(/.*/m.match(buf))
114
124
  else
115
- result = act[1]
125
+ result = action
116
126
  end
117
127
  break result
128
+ else
129
+ nil
118
130
  end
119
131
  end
120
- if not eof_handled
132
+ unless eof_handled
121
133
  # XXX need to prepend buf to @unusedbuf
122
134
  unused_buf = buf # save data for next time
123
- raise :eof if not eof_handled
135
+ raise "End of file"
124
136
  end
125
137
  return result
126
138
  end
127
139
 
140
+ # No timeout and no EOF. There is some input data to look at
128
141
  # Greedy read:
129
142
  # buf << self.read_nonblock(maxlen)
130
143
  if (unused_buf.length > 0)
@@ -140,18 +153,18 @@ module Parley
140
153
  result = :continue
141
154
  matched = false
142
155
  result = actions.each_with_index do |act,i|
143
- # STDOUT.print "buf=\"#{buf}\"\tact=#{act[0]}\n" if parley_verbose
156
+ # @pvout.print "parley,#{__LINE__}: buf=\"#{buf}\"\tact=#{act[0]}\n" if parley_verbose
144
157
  m = case act[0]
145
158
  when Regexp
146
- act[0].match(buf) and Regexp.last_match
159
+ act[0].match(buf)
147
160
  when String
148
- act[0] = Regexp.new(act[0]) # caching the regexp conversion, XXX any problem?
149
- act[0].match(buf) and Regexp.last_match
161
+ act[0] = Regexp.new(act[0]) # caching the regexp conversion
162
+ act[0].match(buf)
150
163
  else
151
164
  nil
152
165
  end
153
166
  if m
154
- STDOUT.print "match[#{i}]=\"#{buf}\"\n" if parley_verbose
167
+ @pvout.print "parley,#{__LINE__}: match[#{i}]=\"#{buf}\"\n" if parley_verbose
155
168
  matched = true
156
169
  if act[1]
157
170
  if act[1].respond_to?(:call)
@@ -164,7 +177,7 @@ module Parley
164
177
  end
165
178
  buf = '' # consume the buffer (XXX only the part that matched?)
166
179
  # XXX if the regex had post context, don't consume that.
167
- STDOUT.puts "result=#{result}" if parley_verbose
180
+ @pvout.puts "parley,#{__LINE__}: result=#{result}" if parley_verbose
168
181
  break result
169
182
  end
170
183
  result
@@ -172,25 +185,28 @@ module Parley
172
185
  end
173
186
 
174
187
  if matched == true
188
+ @pvout.puts "parley,#{__LINE__}: MATCH, result=#{result}" if parley_verbose
175
189
  result = case result
176
190
  when :continue
177
191
  :continue
178
192
  when nil # explicit end
179
193
  break nil
180
194
  when :reset_timeout
181
- deadline = Time.now + t_out # XXX vs deadline in lambda closure?
182
- STDOUT.puts "deadline=#{deadline.to_s}, continue" if parley_verbose
195
+ deadline = Time.now + timeout_seconds # XXX vs deadline in lambda closure?
196
+ @pvout.puts "parley,#{__LINE__}: deadline=#{deadline.to_s}, continue" if parley_verbose
183
197
  :continue
184
198
  else # return with result
185
199
  break result
186
200
  end
187
201
  else
202
+ @pvout.puts "parley,#{__LINE__}: no match, implicit :continue, buf=#{buf}" if parley_verbose
188
203
  result = :continue
189
204
  end
190
205
  end while result == :continue
191
206
  end
192
207
  end
193
208
 
209
+ # Including the Parley module will monkey-patch the IO class
194
210
  class IO
195
211
  include Parley
196
212
  end
@@ -5,24 +5,22 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "parley"
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ben Stoltz"]
12
- s.date = "2013-02-15"
12
+ s.date = "2013-02-28"
13
13
  s.description = "An expect-like gem, modeled after Perl's Expect.pm"
14
14
  s.email = "stoltz@lzrd.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
17
- "README.md",
18
- "README.rdoc"
17
+ "README.md"
19
18
  ]
20
19
  s.files = [
21
20
  ".document",
22
21
  "Gemfile",
23
22
  "LICENSE.txt",
24
23
  "README.md",
25
- "README.rdoc",
26
24
  "Rakefile",
27
25
  "VERSION",
28
26
  "lib/parley.rb",
@@ -33,7 +31,7 @@ Gem::Specification.new do |s|
33
31
  s.homepage = "http://github.com/lzrd/parley"
34
32
  s.licenses = ["MIT"]
35
33
  s.require_paths = ["lib"]
36
- s.rubygems_version = "1.8.24"
34
+ s.rubygems_version = "1.8.25"
37
35
  s.summary = "An expect-like gem, modeled after Perl's Expect.pm"
38
36
 
39
37
  if s.respond_to? :specification_version then
@@ -1,7 +1,12 @@
1
+
2
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0
3
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}") if __FILE__ == $0
4
+
1
5
  require 'helper'
2
6
  require "test/unit"
3
7
  require 'parley'
4
8
  require 'stringio'
9
+ require 'open3'
5
10
 
6
11
  #class TestParley < Test::Unit::TestCase
7
12
  # should "probably rename this file and start testing for real" do
@@ -30,6 +35,16 @@ class TestIO < Test::Unit::TestCase
30
35
  assert(result == :timeout, "** ERROR result = #{result}")
31
36
  end
32
37
 
38
+ def test_single_match
39
+ sin = StringIO.new(@text)
40
+ result = sin.parley(0,
41
+ [/apple/, "just apple"],
42
+ [/white/, "1 too many matches"],
43
+ [/dogs/, "2 too many matches"],
44
+ [:eof, "very bad"])
45
+ assert(result == "just apple", "Invalid result(#{result})")
46
+ end
47
+
33
48
  def test_eof_constant
34
49
  io = File.new("/dev/null", "r")
35
50
  result = io.parley(20, [:eof, :eof])
@@ -52,8 +67,8 @@ class TestIO < Test::Unit::TestCase
52
67
  ['three dogs', lambda{|m| count += 1; :continue }],
53
68
  [:eof, lambda{|m| count}] # XXX need to verify unused portion of buffer
54
69
  )
55
- assert(count == 2, "** ERROR count = #{count}")
56
- assert(result == 2, "** ERROR result = #{result.inspect}")
70
+ assert(count == 2, "** ERROR count = #{count}")
71
+ assert(result == 2, "** ERROR result = #{result.inspect}")
57
72
  end
58
73
 
59
74
  def test_strings_maxread
@@ -65,8 +80,8 @@ class TestIO < Test::Unit::TestCase
65
80
  ['three dogs', lambda{|m| count += 1; :continue }],
66
81
  [:eof, lambda{|m| count}] # XXX need to verify unused portion of buffer
67
82
  )
68
- assert(count == 2, "** ERROR count = #{count}")
69
- assert(result == 2, "** ERROR result = #{result.inspect}")
83
+ assert(count == 2, "** ERROR count = #{count}")
84
+ assert(result == 2, "** ERROR result = #{result.inspect}")
70
85
  end
71
86
 
72
87
  def test_patterns
@@ -77,8 +92,8 @@ class TestIO < Test::Unit::TestCase
77
92
  [Regexp.new(colors.join('|')), lambda{|m| count += 1; :continue}],
78
93
  [:eof, lambda{|m| count}] # XXX need to verify unused portion of buffer
79
94
  )
80
- assert(count == 2, "** ERROR count = #{count}")
81
- assert(result == 2, "** ERROR result = #{result.inspect}")
95
+ assert(count == 2, "** ERROR count = #{count}")
96
+ assert(result == 2, "** ERROR result = #{result.inspect}")
82
97
  end
83
98
 
84
99
  #
@@ -95,8 +110,8 @@ class TestIO < Test::Unit::TestCase
95
110
  [Regexp.new(colors.join('|')), lambda{|m| count += 1; :continue}],
96
111
  [:eof, lambda{|m| count}] # XXX need to verify unused portion of buffer
97
112
  )
98
- assert(count == 1, "** ERROR count = #{count}")
99
- assert(result == 1, "** ERROR result = #{result.inspect}")
113
+ assert(count == 1, "** ERROR count = #{count}")
114
+ assert(result == 1, "** ERROR result = #{result.inspect}")
100
115
  end
101
116
 
102
117
  #
@@ -133,9 +148,9 @@ class TestIO < Test::Unit::TestCase
133
148
  }
134
149
  ] # XXX need to verify unused portion of buffer
135
150
  )
136
- assert(count == 3, "** ERROR count = #{count}")
137
- assert(result == 3, "** ERROR result = #{result.inspect}")
138
- # assert(buf_end.length > 0, "** Error buf_end.length=#{buf_end.length}, buf=#{buf_end}")
151
+ assert(count == 3, "** ERROR count = #{count}")
152
+ assert(result == 3, "** ERROR result = #{result.inspect}")
153
+ # assert(buf_end.length > 0, "** Error buf_end.length=#{buf_end.length}, buf=#{buf_end}")
139
154
  end
140
155
 
141
156
  def test_empty_string
@@ -145,7 +160,7 @@ class TestIO < Test::Unit::TestCase
145
160
  result = io.parley(0,
146
161
  [Regexp.new(colors.join('|')), lambda{|m| count += 1; :continue}],
147
162
  [:eof, lambda{|m| count}])
148
- assert(count == 0, "** ERROR count = #{result.inspect}")
163
+ assert(count == 0, "** ERROR count = #{count.inspect}")
149
164
  assert(result == 0, "** ERROR result = #{result.inspect}")
150
165
  end
151
166
 
@@ -176,25 +191,25 @@ class TestIO < Test::Unit::TestCase
176
191
  [/> /, lambda {|m| w_f.puts("cd pub/ruby"); nil }]
177
192
  )
178
193
 
179
- # >
180
- # dir\n
181
- r_f.parley(ftp_to, ["> ", lambda {|m| w_f.print "dir\r"}])
194
+ # >
195
+ # dir\n
196
+ r_f.parley(ftp_to, ["> ", lambda {|m| w_f.print "dir\r"}])
182
197
 
183
- # lrwxrwxrwx 1 1014 100 27 Feb 18 12:52 ruby-1.8.7-p334.tar.bz2 -> 1.8/ruby-1.8.7-p334.tar.bz2
184
- r_f.parley(ftp_to,
185
- [/^ftp> /, lambda {|m|
186
- for x in m.pre_match.split("\n")
187
- if x =~ /(ruby.*?\.tar\.gz)/ then
188
- fnames.push $1
189
- end
190
- end
191
- begin
192
- w_f.print "quit\n"
193
- rescue
194
- end
195
- :nil
196
- }],
197
- [:eof, nil])
198
+ # lrwxrwxrwx 1 1014 100 27 Feb 18 12:52 ruby-1.8.7-p334.tar.bz2 -> 1.8/ruby-1.8.7-p334.tar.bz2
199
+ r_f.parley(ftp_to,
200
+ [/^ftp> /, lambda {|m|
201
+ for x in m.pre_match.split("\n")
202
+ if x =~ /(ruby.*?\.tar\.gz)/ then
203
+ fnames.push $1
204
+ end
205
+ end
206
+ begin
207
+ w_f.print "quit\n"
208
+ rescue
209
+ end
210
+ :nil
211
+ }],
212
+ [:eof, nil])
198
213
  end
199
214
  puts "The latest ruby interpreter is #{fnames.sort.pop}"
200
215
  end
@@ -203,11 +218,110 @@ class TestIO < Test::Unit::TestCase
203
218
  read, write, pid = PTY.spawn(
204
219
  # XXX fire off a ruby program, not bash, to get the test behavoir we want
205
220
  '/bin/bash -x -c "for x in wait done too_late; do sleep 3; echo $x; done"')
206
- read.parley_verbose = true if @test_verbose
207
- result = read.parley(5,
208
- [/wait/, :reset_timeout],
209
- [/done/, :pass ],
210
- [:timeout, :timeout])
211
- assert(result == :pass, "** ERROR result = #{result}")
221
+ read.parley_verbose = true if @test_verbose
222
+ result = read.parley(5,
223
+ [/wait/, :reset_timeout],
224
+ [/done/, :pass ],
225
+ [:timeout, :timeout])
226
+ assert(result == :pass, "** ERROR result = #{result}")
227
+ end
228
+
229
+ def test_guessing_game
230
+ @NGAMES = 2
231
+ @UPPER_LIMIT = 100
232
+ @UPPER_LIMIT_LOG2 = 8
233
+
234
+ game = <<-GUESSING_GAME_EOT
235
+ BEGIN {puts "0>"; @n = 0}
236
+ END { puts "Goodbye!"; }
237
+ @secret ||= rand 100
238
+ g = $F[0] ? $F[0].strip : ""
239
+ z = "?"
240
+ if g =~ /\\d+$/m
241
+ z = (g.to_i <=> @secret)
242
+ case g.to_i <=> @secret
243
+ when -1
244
+ puts "too low"
245
+ when 1
246
+ puts "too high"
247
+ when 0
248
+ puts "correct!"
249
+ @secret = rand 100
250
+ puts "Ready";
251
+ end
252
+ else
253
+ exit 0 if $F[0] == "exit"
254
+ end
255
+ @n += 1
256
+ puts "\#{@n}>"
257
+ GUESSING_GAME_EOT
258
+
259
+ # DRY: common code for sending a guess
260
+ def sendguess resend = false
261
+ @myguess = ((@min + @max) / 2).to_i
262
+ puts "Resending guess" if resend
263
+ puts "Guessing #{@myguess}"
264
+ @sin.puts @myguess
265
+ @guesses += 1 unless resend
266
+ if @guesses > @UPPER_LIMIT_LOG2
267
+ "I Lost" # Bug in program if we haven't guessed it already
268
+ else
269
+ :continue
270
+ end
271
+ end
272
+
273
+ def newgame
274
+ @min = 0
275
+ @max = @UPPER_LIMIT
276
+ @guesses = 0
277
+ end
278
+
279
+ def winner
280
+ puts "I win!"
281
+ @wins += 1
282
+ newgame
283
+ (@games -= 1) > 0 ? sendguess : "finished" # :continue or "finished"
284
+ end
285
+
286
+ @games = @NGAMES
287
+ @wins = 0
288
+ # puts "Running: ruby -n -a -e #{game}"
289
+ result = nil
290
+ pty_return = PTY.spawn('ruby', '-n', '-a', '-e', game) do |sout, sin, pid|
291
+ @sin = sin
292
+ puts "Sending <CR>"
293
+ sin.puts '' # Elicit a prompt from the game
294
+ r = select([sout], [], [], 2) # Wait up to 15 seconds for output from guessing game
295
+ puts "Wokeup from select with <<#{r.inspect}>>"
296
+
297
+ newgame
298
+ @n_to_reset = 0
299
+ sout.parley_maxread = 100
300
+ # sout.parley_verbose = true
301
+ result = sout.parley(
302
+ 2,
303
+ [/too low/, lambda{|m| @min = @myguess + 1; sendguess}],
304
+ [/too high/, lambda{|m| @max = @myguess - 1; sendguess}],
305
+ [/correct/, lambda{|m| winner}],
306
+ [/>/, lambda{|m| sendguess true}],
307
+ [
308
+ :timeout,
309
+ lambda do |m|
310
+ sin.puts ""
311
+ @n_to_reset += 1
312
+ puts "Resetting timeout #{@n_to_reset}"
313
+ if @n_to_reset > 2
314
+ sin.puts "exit"
315
+ "Timeout"
316
+ else
317
+ :reset_timeout
318
+ end
319
+ end
320
+ ])
321
+ end
322
+ puts "Script finished, pty_return=#{pty_return}"
323
+ assert(result == "finished", "didn't win last game")
324
+ # assert(ec == 0, "Bad exit from guessing game")
325
+ assert(@wins == @NGAMES, "Didn't win exactly #{@NGAMES} games")
212
326
  end
213
327
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parley
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-15 00:00:00.000000000 Z
12
+ date: 2013-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rdoc
@@ -66,13 +66,11 @@ extensions: []
66
66
  extra_rdoc_files:
67
67
  - LICENSE.txt
68
68
  - README.md
69
- - README.rdoc
70
69
  files:
71
70
  - .document
72
71
  - Gemfile
73
72
  - LICENSE.txt
74
73
  - README.md
75
- - README.rdoc
76
74
  - Rakefile
77
75
  - VERSION
78
76
  - lib/parley.rb
@@ -94,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
92
  version: '0'
95
93
  segments:
96
94
  - 0
97
- hash: -1353724432284273081
95
+ hash: -3112653461861631076
98
96
  required_rubygems_version: !ruby/object:Gem::Requirement
99
97
  none: false
100
98
  requirements:
@@ -103,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
101
  version: '0'
104
102
  requirements: []
105
103
  rubyforge_project:
106
- rubygems_version: 1.8.24
104
+ rubygems_version: 1.8.25
107
105
  signing_key:
108
106
  specification_version: 3
109
107
  summary: An expect-like gem, modeled after Perl's Expect.pm
@@ -1,35 +0,0 @@
1
- = parley
2
-
3
- An Expect-like gem for Ruby
4
-
5
- Parley is an implementation of an expect-like API. It is designed to
6
- help port away from Perl Expect based applications. The name "expect"
7
- is already well established in ruby. Those of you who have wrestled
8
- with the interactive, text-mode, APIs of the world will appreciate the
9
- meaning of the word:
10
-
11
- From http://www.thefreedictionary.com/parley "A discussion or conference, especially one between enemies over terms of truce or other matters."
12
-
13
- See http://www.nist.gov/el/msid/expect.cfm for references to the original Expect language based on Tcl.
14
-
15
- See http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod for information on Expect.pm
16
-
17
- == Known Issues
18
-
19
- * need to generatate adequte documentation. See test/test_parley.rb for now
20
- * :restart_timeout from IO::parley() doesn't have the desired effect
21
-
22
- == Contributing to parley
23
-
24
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
25
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
26
- * Fork the project.
27
- * Start a feature/bugfix branch.
28
- * Commit and push until you are happy with your contribution.
29
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
30
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
31
-
32
- == Copyright
33
-
34
- Copyright (c) 2013 Ben Stoltz.
35
- See LICENSE.txt for further details.