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 +109 -9
- data/VERSION +1 -1
- data/lib/parley.rb +74 -58
- data/parley.gemspec +4 -6
- data/test/test_parley.rb +150 -36
- metadata +4 -6
- data/README.rdoc +0 -35
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
|
-
|
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
|
-
*
|
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.2.0
|
data/lib/parley.rb
CHANGED
@@ -1,130 +1,143 @@
|
|
1
|
-
|
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
|
-
#
|
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
|
-
|
24
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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 +
|
65
|
+
deadline = Time.now + timeout_seconds
|
51
66
|
end
|
52
67
|
buf = ''
|
53
68
|
unused_buf = '' if not unused_buf
|
54
69
|
|
55
|
-
|
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
|
-
|
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.
|
81
|
-
|
82
|
-
when :timeout
|
90
|
+
result = actions.find do|pattern, action|
|
91
|
+
if pattern == :timeout
|
83
92
|
timeout_handled = true
|
84
|
-
if
|
85
|
-
/.*/.match(buf) #
|
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
|
-
|
96
|
+
r = action
|
89
97
|
end
|
90
|
-
|
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"
|
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
|
-
|
105
|
-
result = nil
|
116
|
+
@pvout.print "parley,#{__LINE__}: EOF Buffer=\"#{buf}\"\n" if parley_verbose
|
106
117
|
eof_handled = false
|
107
|
-
actions.
|
108
|
-
case
|
118
|
+
result = actions.find do |pattern, action|
|
119
|
+
case pattern
|
109
120
|
when :eof
|
110
121
|
eof_handled = true
|
111
|
-
if
|
112
|
-
/.*/m.match(buf)
|
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 =
|
125
|
+
result = action
|
116
126
|
end
|
117
127
|
break result
|
128
|
+
else
|
129
|
+
nil
|
118
130
|
end
|
119
131
|
end
|
120
|
-
|
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
|
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
|
-
#
|
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)
|
159
|
+
act[0].match(buf)
|
147
160
|
when String
|
148
|
-
act[0] = Regexp.new(act[0]) # caching the regexp conversion
|
149
|
-
act[0].match(buf)
|
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
|
-
|
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
|
-
|
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 +
|
182
|
-
|
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
|
data/parley.gemspec
CHANGED
@@ -5,24 +5,22 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "parley"
|
8
|
-
s.version = "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-
|
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.
|
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
|
data/test/test_parley.rb
CHANGED
@@ -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
|
-
|
56
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
99
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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 = #{
|
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
|
-
|
181
|
-
|
194
|
+
# >
|
195
|
+
# dir\n
|
196
|
+
r_f.parley(ftp_to, ["> ", lambda {|m| w_f.print "dir\r"}])
|
182
197
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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.
|
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-
|
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: -
|
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.
|
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
|
data/README.rdoc
DELETED
@@ -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.
|