expectr 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/expectr/adopt.rb +30 -20
- data/lib/expectr/child.rb +90 -109
- data/lib/expectr/errstr.rb +3 -3
- data/lib/expectr/interface.rb +16 -34
- data/lib/expectr/interpreter.rb +3 -4
- data/lib/expectr/lambda.rb +62 -71
- data/lib/expectr/version.rb +1 -1
- data/lib/expectr.rb +80 -121
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdb63c98692074edfb85dfd5652de4bb858b98bc
|
4
|
+
data.tar.gz: c0cf8fcfbc6d926bf779884265db4fb1a484839f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb3dac4b059cc9d1453637b020ffa0fa0c094ca04ff5a62acba1dc95d23de265ac4781daf3ce59bd174cc0110a839d4826127a01298e06a4f2650ca5f1047b55
|
7
|
+
data.tar.gz: 47d9d136860390bcb8deb5296b6f3b2eac9406db8ef561770ae6a610a5600eaf07f8ec3acd0c260710278b72585c877106e61d63012d626f58f16684909b4ce6
|
data/lib/expectr/adopt.rb
CHANGED
@@ -3,33 +3,43 @@ require 'expectr/interface'
|
|
3
3
|
require 'expectr/child'
|
4
4
|
|
5
5
|
class Expectr
|
6
|
-
#
|
7
|
-
# with child processes
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# Public: Initialize a new Expectr::Adopt object.
|
6
|
+
# Public: The Expectr::Adopt Module defines the interface for interacting
|
7
|
+
# with child processes without spawning them through Expectr.
|
8
|
+
module Adopt
|
9
|
+
include Expectr::Child
|
10
|
+
|
11
|
+
# Public: Initialize the Expectr interface, adopting IO objects to act on
|
12
|
+
# them as if they were produced by spawning a child process.
|
14
13
|
# IO Objects are named in such a way as to maintain interoperability with
|
15
|
-
# the methods from the Expectr::Child
|
14
|
+
# the methods from the Expectr::Child module.
|
16
15
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
16
|
+
# args - Hash containing IO Objects and optionally a PID to watch.
|
17
|
+
# stdin - IO object open for writing.
|
18
|
+
# stdout - IO object open for reading.
|
19
|
+
# pid - FixNum corresponding to the PID of the process being
|
20
|
+
# adopted. (default: 1)
|
21
21
|
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
22
|
+
# Returns nothing.
|
23
|
+
# Raises TypeError if args[:stdin] or args[:stdout] aren't of type IO.
|
24
|
+
def init_interface(args)
|
25
|
+
unless args[:stdin].kind_of?(IO) && args[:stdout].kind_of?(IO)
|
25
26
|
raise(TypeError, Errstr::IO_EXPECTED)
|
26
27
|
end
|
27
|
-
@stdin = stdin
|
28
|
-
@stdout = stdout
|
28
|
+
@stdin = args[:stdin]
|
29
|
+
@stdout = args[:stdout]
|
29
30
|
@stdout.winsize = $stdout.winsize if $stdout.tty?
|
31
|
+
@pid = args[:pid] || 0
|
32
|
+
|
33
|
+
if @pid > 0
|
34
|
+
Thread.new do
|
35
|
+
Process.wait @pid
|
36
|
+
@pid = 0
|
37
|
+
end
|
38
|
+
end
|
30
39
|
end
|
31
40
|
|
32
|
-
# Public: Present a streamlined interface to create a new Expectr instance
|
41
|
+
# Public: Present a streamlined interface to create a new Expectr instance
|
42
|
+
# using the Adopt interface.
|
33
43
|
#
|
34
44
|
# stdout - IO object open for reading.
|
35
45
|
# stdin - IO object open for writing.
|
@@ -44,7 +54,7 @@ class Expectr
|
|
44
54
|
args[:stdin] = stdin
|
45
55
|
args[:stdout] = stdout
|
46
56
|
args[:pid] = pid
|
47
|
-
Expectr.new(
|
57
|
+
Expectr.new(args)
|
48
58
|
end
|
49
59
|
end
|
50
60
|
end
|
data/lib/expectr/child.rb
CHANGED
@@ -1,28 +1,26 @@
|
|
1
1
|
require 'pty'
|
2
|
+
require 'expectr'
|
2
3
|
require 'expectr/interface'
|
3
4
|
|
4
5
|
class Expectr
|
5
|
-
#
|
6
|
+
# Public: The Expectr::Child Module defines the interface for interacting
|
6
7
|
# with child processes.
|
7
|
-
|
8
|
-
# All methods with the prefix 'interface_' in their name will return a Proc
|
9
|
-
# designed to be defined as an instance method in the primary Expectr object.
|
10
|
-
# These methods will all be documented as if they are the Proc in question.
|
11
|
-
class Child
|
8
|
+
module Child
|
12
9
|
include Expectr::Interface
|
13
|
-
attr_reader :stdin
|
14
|
-
attr_reader :stdout
|
15
|
-
attr_reader :pid
|
16
10
|
|
17
|
-
# Public: Initialize
|
18
|
-
#
|
19
|
-
# process.
|
11
|
+
# Public: Initialize the Expectr interface, spawning a sub-process
|
12
|
+
# attaching to STDIN and STDOUT of the new process.
|
20
13
|
#
|
21
|
-
#
|
14
|
+
# args - A Hash containing arguments to Expectr. Only :cmd is presently
|
15
|
+
# used for this function.
|
16
|
+
# :cmd - A String or File referencing the application to launch.
|
22
17
|
#
|
23
|
-
#
|
24
|
-
|
18
|
+
# Returns nothing.
|
19
|
+
# Raises TypeError if args[:cmd] is anything other than String or File.
|
20
|
+
def init_interface(args)
|
21
|
+
cmd = args[:cmd]
|
25
22
|
cmd = cmd.path if cmd.kind_of?(File)
|
23
|
+
|
26
24
|
unless cmd.kind_of?(String)
|
27
25
|
raise(TypeError, Errstr::STRING_FILE_EXPECTED)
|
28
26
|
end
|
@@ -33,19 +31,17 @@ class Expectr
|
|
33
31
|
|
34
32
|
# Public: Send a signal to the running child process.
|
35
33
|
#
|
36
|
-
# signal - Symbol, String or FixNum
|
37
|
-
#
|
34
|
+
# signal - Symbol, String or FixNum indicating the symbol to be sent to the
|
35
|
+
# running process. (default: :TERM)
|
38
36
|
#
|
39
37
|
# Returns a boolean indicating whether the process was successfully sent
|
40
38
|
# the signal.
|
41
39
|
# Raises ProcessError if the process is not running (@pid = 0).
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Process::kill(signal.to_sym, @pid) == 1
|
48
|
-
}
|
40
|
+
def kill!(signal = :TERM)
|
41
|
+
unless @pid > 0
|
42
|
+
raise(ProcessError, Errstr::PROCESS_NOT_RUNNING)
|
43
|
+
end
|
44
|
+
Process::kill(signal.to_sym, @pid) == 1
|
49
45
|
end
|
50
46
|
|
51
47
|
# Public: Send input to the active child process.
|
@@ -53,113 +49,98 @@ class Expectr
|
|
53
49
|
# str - String to be sent.
|
54
50
|
#
|
55
51
|
# Returns nothing.
|
56
|
-
# Raises Expectr::ProcessError if the process is not running (@pid = 0)
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
}
|
52
|
+
# Raises Expectr::ProcessError if the process is not running (@pid = 0).
|
53
|
+
def send(str)
|
54
|
+
begin
|
55
|
+
@stdin.syswrite str
|
56
|
+
rescue Errno::EIO #Application is not running
|
57
|
+
@pid = 0
|
58
|
+
end
|
59
|
+
unless @pid > 0
|
60
|
+
raise(Expectr::ProcessError, Errstr::PROCESS_GONE)
|
61
|
+
end
|
68
62
|
end
|
69
63
|
|
70
|
-
# Public: Read the child process
|
64
|
+
# Public: Read the output of the child process, force UTF-8 encoding, then
|
71
65
|
# append to the internal buffer and print to $stdout if appropriate.
|
72
66
|
#
|
73
67
|
# Returns nothing.
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return
|
85
|
-
end
|
86
|
-
process_output(buf)
|
68
|
+
def output_loop
|
69
|
+
while @pid > 0
|
70
|
+
unless select([@stdout], nil, nil, @timeout).nil?
|
71
|
+
buf = ''
|
72
|
+
|
73
|
+
begin
|
74
|
+
@stdout.sysread(@buffer_size, buf)
|
75
|
+
rescue Errno::EIO #Application is not running
|
76
|
+
@pid = 0
|
77
|
+
return
|
87
78
|
end
|
79
|
+
process_output(buf)
|
88
80
|
end
|
89
|
-
|
81
|
+
end
|
90
82
|
end
|
91
83
|
|
92
|
-
|
93
|
-
|
94
|
-
|
84
|
+
# Public: Return the PTY's window size.
|
85
|
+
#
|
86
|
+
# Returns a two-element Array (same as IO#winsize)
|
87
|
+
def winsize
|
88
|
+
@stdout.winsize
|
89
|
+
end
|
95
90
|
|
96
|
-
|
97
|
-
env[:tty] = `stty -g`
|
98
|
-
`stty -icanon min 1 time 0 -echo`
|
91
|
+
private
|
99
92
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
93
|
+
# Internal: Set up the execution environment to prepare for entering
|
94
|
+
# interact mode.
|
95
|
+
# Presently assumes a Linux system.
|
96
|
+
#
|
97
|
+
# Returns nothing.
|
98
|
+
def prepare_interact_environment
|
99
|
+
env = {sig: {}}
|
104
100
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
101
|
+
# Save old tty settings and set up the new environment
|
102
|
+
env[:tty] = `stty -g`
|
103
|
+
`stty -icanon min 1 time 0 -echo`
|
109
104
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
105
|
+
# SIGINT should be sent to the child as \C-c
|
106
|
+
env[:sig]['INT'] = trap 'INT' do
|
107
|
+
send "\C-c"
|
108
|
+
end
|
109
|
+
|
110
|
+
# SIGTSTP should be sent to the process as \C-z
|
111
|
+
env[:sig]['TSTP'] = trap 'TSTP' do
|
112
|
+
send "\C-z"
|
113
|
+
end
|
114
114
|
|
115
|
-
|
116
|
-
|
115
|
+
# SIGWINCH should trigger an update to the child processes window size
|
116
|
+
env[:sig]['WINCH'] = trap 'WINCH' do
|
117
|
+
@stdout.winsize = $stdout.winsize
|
118
|
+
end
|
119
|
+
|
120
|
+
env
|
117
121
|
end
|
118
122
|
|
119
|
-
#
|
120
|
-
# handling input from the user in interact mode.
|
123
|
+
# Internal: Create a Thread containing the loop which is responsible for
|
124
|
+
# handling input from the user while in interact mode.
|
121
125
|
#
|
122
126
|
# Returns a Thread containing the running loop.
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
input = ''
|
127
|
+
def interact_thread
|
128
|
+
@interact = true
|
129
|
+
env = prepare_interact_environment
|
130
|
+
Thread.new do
|
131
|
+
begin
|
132
|
+
input = ''
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
134
|
+
while @pid > 0 && @interact
|
135
|
+
if select([$stdin], nil, nil, 1)
|
136
|
+
c = $stdin.getc.chr
|
137
|
+
send c unless c.nil?
|
136
138
|
end
|
137
|
-
ensure
|
138
|
-
restore_environment(env)
|
139
139
|
end
|
140
|
+
ensure
|
141
|
+
restore_environment(env)
|
140
142
|
end
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
# Public: Return the PTY's window size.
|
145
|
-
#
|
146
|
-
# Returns a two-element Array (same as IO#winsize)
|
147
|
-
def interface_winsize
|
148
|
-
-> {
|
149
|
-
@stdout.winsize
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
# Public: Present a streamlined interface to create a new Expectr instance.
|
154
|
-
#
|
155
|
-
# cmd - A String or File referencing the application to launch.
|
156
|
-
# args - A Hash used to specify options for the new object, per
|
157
|
-
# Expectr#initialize.
|
158
|
-
#
|
159
|
-
# Returns a new Expectr object
|
160
|
-
def self.spawn(cmd, args = {})
|
161
|
-
args[:interface] = :child
|
162
|
-
Expectr.new(cmd, args)
|
143
|
+
end
|
163
144
|
end
|
164
145
|
end
|
165
146
|
end
|
data/lib/expectr/errstr.rb
CHANGED
@@ -4,7 +4,7 @@ class Expectr
|
|
4
4
|
ALREADY_INTERACT = "Already in interact mode"
|
5
5
|
end
|
6
6
|
|
7
|
-
|
7
|
+
module Child
|
8
8
|
module Errstr
|
9
9
|
STRING_FILE_EXPECTED = "Command should be of type String or File"
|
10
10
|
PROCESS_NOT_RUNNING = "No process is running"
|
@@ -12,7 +12,7 @@ class Expectr
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
module Adopt
|
16
16
|
module Errstr
|
17
17
|
IO_EXPECTED = "Arguments of type IO expected"
|
18
18
|
end
|
@@ -25,7 +25,7 @@ class Expectr
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
module Lambda
|
29
29
|
module Errstr
|
30
30
|
PROC_EXPECTED = "Proc Objects expected for reader and writer"
|
31
31
|
end
|
data/lib/expectr/interface.rb
CHANGED
@@ -1,53 +1,35 @@
|
|
1
1
|
class Expectr
|
2
2
|
module Interface
|
3
|
-
# Public: Enumerate and expose all interface functions for a given
|
4
|
-
# Interface.
|
5
|
-
#
|
6
|
-
# Returns a two-dimensional Array containing a name for the new method and
|
7
|
-
# a reference to the extant method.
|
8
|
-
def init_instance
|
9
|
-
methods = []
|
10
|
-
public_methods.select { |m| m =~ /^interface_/ }.each do |name|
|
11
|
-
method = public_method(name)
|
12
|
-
name = name.to_s.gsub(/^interface_/, '').to_sym
|
13
|
-
methods << [name, method]
|
14
|
-
end
|
15
|
-
methods
|
16
|
-
end
|
17
|
-
|
18
3
|
# Public: Return a Thread which does nothing, representing an interface
|
19
4
|
# with no functional interact environment available.
|
20
5
|
#
|
21
6
|
# Returns a Thread.
|
22
|
-
def
|
23
|
-
|
24
|
-
Thread.new { }
|
25
|
-
}
|
7
|
+
def interact_thread
|
8
|
+
Thread.new { }
|
26
9
|
end
|
27
10
|
|
28
11
|
# Public: Return an empty Hash representing a case where no action needed
|
29
12
|
# to be taken in order to prepare the environment for interact mode.
|
30
13
|
#
|
31
14
|
# Returns an empty Hash.
|
32
|
-
def
|
33
|
-
|
34
|
-
{}
|
35
|
-
}
|
15
|
+
def prepare_interact_interface
|
16
|
+
{}
|
36
17
|
end
|
37
18
|
|
38
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
# Internal: Restore environment (TTY parameters, signal handlers) after
|
22
|
+
# leaving interact mode.
|
39
23
|
#
|
40
24
|
# Returns nothing.
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@interact = false
|
50
|
-
}
|
25
|
+
def restore_environment(env)
|
26
|
+
env[:sig].each do |signal, handler|
|
27
|
+
trap signal, handler
|
28
|
+
end
|
29
|
+
unless env[:tty].nil?
|
30
|
+
`stty #{env[:tty]}`
|
31
|
+
end
|
32
|
+
@interact = false
|
51
33
|
end
|
52
34
|
end
|
53
35
|
end
|
data/lib/expectr/interpreter.rb
CHANGED
@@ -9,7 +9,7 @@ class Expectr
|
|
9
9
|
# Public: Filename of currently executing script.
|
10
10
|
attr_accessor :filename
|
11
11
|
|
12
|
-
# Public: Initialize a new Expectr interface.
|
12
|
+
# Public: Initialize a new Expectr Interpreter interface.
|
13
13
|
#
|
14
14
|
# source - String containing the source to be executed.
|
15
15
|
def initialize(source)
|
@@ -18,8 +18,7 @@ class Expectr
|
|
18
18
|
@expect = nil
|
19
19
|
end
|
20
20
|
|
21
|
-
# Public: Run the source associated with the Interface object
|
22
|
-
# Object.
|
21
|
+
# Public: Run the source associated with the Interface object.
|
23
22
|
#
|
24
23
|
# Returns nothing.
|
25
24
|
def run
|
@@ -45,7 +44,7 @@ class Expectr
|
|
45
44
|
# true, and 0 or less is false.
|
46
45
|
#
|
47
46
|
# Returns nothing.
|
48
|
-
# Raises
|
47
|
+
# Raises TypeError if enable is not a boolean.
|
49
48
|
def log_user(enable)
|
50
49
|
if enable.is_a?(Numeric)
|
51
50
|
enable = (enable > 0)
|
data/lib/expectr/lambda.rb
CHANGED
@@ -2,33 +2,29 @@ require 'expectr'
|
|
2
2
|
require 'expectr/interface'
|
3
3
|
|
4
4
|
class Expectr
|
5
|
-
#
|
6
|
-
# with
|
7
|
-
#
|
8
|
-
|
9
|
-
# All methods with the prefix 'interface_' in their name will return a Proc
|
10
|
-
# designed to be defined as an instance method in the primary Expectr object.
|
11
|
-
# These methods will all be documented as if they are the Proc in question.
|
12
|
-
class Lambda
|
5
|
+
# Public: The Expectr::Lambda Module defines the interface for interacting
|
6
|
+
# with Proc objects in a manner which is similar to interacting with
|
7
|
+
# processes.
|
8
|
+
module Lambda
|
13
9
|
include Expectr::Interface
|
14
|
-
attr_reader :reader
|
15
|
-
attr_reader :writer
|
16
10
|
|
17
|
-
# Public: Initialize
|
11
|
+
# Public: Initialize the Expectr Lambda interface.
|
18
12
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
13
|
+
# args - Hash containing Proc objects to act as reader and writer:
|
14
|
+
# reader - Lambda which is meant to be interacted with as if it were
|
15
|
+
# analogous to STDIN for a child process.
|
16
|
+
# writer - Lambda which is meant to be interacted with as if it were
|
17
|
+
# analogous to STDOUT for a child process.
|
23
18
|
#
|
24
19
|
# Raises TypeError if arguments aren't of type Proc.
|
25
|
-
def
|
26
|
-
unless reader.kind_of?(Proc) && writer.kind_of?(Proc)
|
20
|
+
def init_interface(args)
|
21
|
+
unless args[:reader].kind_of?(Proc) && args[:writer].kind_of?(Proc)
|
27
22
|
raise(TypeError, Errstr::PROC_EXPECTED)
|
28
23
|
end
|
29
24
|
|
30
|
-
@
|
31
|
-
@
|
25
|
+
@pid = -1
|
26
|
+
@reader = args[:reader]
|
27
|
+
@writer = args[:writer]
|
32
28
|
end
|
33
29
|
|
34
30
|
# Public: Present a streamlined interface to create a new Expectr instance.
|
@@ -45,87 +41,82 @@ class Expectr
|
|
45
41
|
args[:interface] = :lambda
|
46
42
|
args[:reader] = reader
|
47
43
|
args[:writer] = writer
|
48
|
-
Expectr.new(
|
44
|
+
Expectr.new(args)
|
49
45
|
end
|
50
46
|
|
51
|
-
# Public: Send input to the
|
47
|
+
# Public: Send input to the reader Proc.
|
52
48
|
#
|
53
49
|
# args - Arguments to pass to the reader interface.
|
54
50
|
#
|
55
51
|
# Returns nothing.
|
56
|
-
def
|
57
|
-
|
58
|
-
@reader.call(*args)
|
59
|
-
}
|
52
|
+
def send(args)
|
53
|
+
@reader.call(*args)
|
60
54
|
end
|
61
55
|
|
62
56
|
# Public: Prepare the operating environment for interact mode, set the
|
63
57
|
# interact flag to true.
|
64
58
|
#
|
65
59
|
# Returns a Hash containing old signal handlers and tty parameters.
|
66
|
-
def
|
67
|
-
|
68
|
-
env = {sig: {}}
|
60
|
+
def prepare_interact_environment
|
61
|
+
env = {sig: {}}
|
69
62
|
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
# Save old tty settings and set up the new environment
|
64
|
+
env[:tty] = `stty -g`
|
65
|
+
`stty -icanon min 1 time 0 -echo`
|
73
66
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
67
|
+
# SIGINT should be sent to the child as \C-c
|
68
|
+
env[:sig]['INT'] = trap 'INT' do
|
69
|
+
send "\C-c"
|
70
|
+
end
|
78
71
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
72
|
+
# SIGTSTP should be sent to the process as \C-z
|
73
|
+
env[:sig]['TSTP'] = trap 'TSTP' do
|
74
|
+
send "\C-z"
|
75
|
+
end
|
83
76
|
|
84
|
-
|
85
|
-
|
86
|
-
}
|
77
|
+
@interact = true
|
78
|
+
env
|
87
79
|
end
|
88
80
|
|
89
81
|
# Public: Create a Thread containing the loop which is responsible for
|
90
82
|
# handling input from the user in interact mode.
|
91
83
|
#
|
92
84
|
# Returns a Thread containing the running loop.
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
input = ''
|
98
|
-
|
99
|
-
while @interact
|
100
|
-
if select([$stdin], nil, nil, 1)
|
101
|
-
c = $stdin.getc.chr
|
102
|
-
send c unless c.nil?
|
103
|
-
end
|
104
|
-
end
|
85
|
+
def interact_thread
|
86
|
+
Thread.new do
|
87
|
+
env = prepare_interact_environment
|
88
|
+
input = ''
|
105
89
|
|
106
|
-
|
90
|
+
while @interact
|
91
|
+
if select([$stdin], nil, nil, 1)
|
92
|
+
c = $stdin.getc.chr
|
93
|
+
send c unless c.nil?
|
94
|
+
end
|
107
95
|
end
|
108
|
-
|
96
|
+
|
97
|
+
restore_environment(env)
|
98
|
+
end
|
109
99
|
end
|
110
100
|
|
111
|
-
|
112
|
-
|
101
|
+
private
|
102
|
+
|
103
|
+
# Internal: Call the writer lambda, reading the output produced, forcing
|
104
|
+
# UTF-8, appending to the internal buffer and printing to $stdout if
|
105
|
+
# appropriate.
|
113
106
|
#
|
114
107
|
# Returns nothing.
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
return
|
125
|
-
end
|
126
|
-
process_output(buf)
|
108
|
+
def output_loop
|
109
|
+
buf = ''
|
110
|
+
loop do
|
111
|
+
buf.clear
|
112
|
+
|
113
|
+
begin
|
114
|
+
buf << @writer.call.to_s
|
115
|
+
rescue Errno::EIO # Lambda is signaling that execution should end.
|
116
|
+
return
|
127
117
|
end
|
128
|
-
|
118
|
+
process_output(buf)
|
119
|
+
end
|
129
120
|
end
|
130
121
|
end
|
131
122
|
end
|
data/lib/expectr/version.rb
CHANGED
data/lib/expectr.rb
CHANGED
@@ -13,10 +13,9 @@ require 'expectr/lambda'
|
|
13
13
|
# Public: Expectr is an API to the functionality of Expect (see
|
14
14
|
# http://expect.nist.gov) implemented in ruby.
|
15
15
|
#
|
16
|
-
# Expectr contrasts with Ruby's built-in
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# run.
|
16
|
+
# Expectr contrasts with Ruby's built-in expect.rb by avoiding tying in with
|
17
|
+
# the IO class in favor of creating a new object entirely to allow for more
|
18
|
+
# granular control over the execution and display of the program being run.
|
20
19
|
#
|
21
20
|
# Examples
|
22
21
|
#
|
@@ -57,51 +56,57 @@ class Expectr
|
|
57
56
|
# Public: Initialize a new Expectr object.
|
58
57
|
# Spawns a sub-process and attaches to STDIN and STDOUT for the new process.
|
59
58
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
|
59
|
+
# cmd_args - This may be either a Hash containing arguments (described below)
|
60
|
+
# or a String or File Object referencing the application to launch
|
61
|
+
# (assuming Child interface). This argument, if not a Hash, will
|
62
|
+
# be changed into the Hash { cmd: cmd_args }. This argument will
|
63
|
+
# be merged with the args Hash, overriding any arguments
|
64
|
+
# specified there.
|
65
|
+
# This argument is kept around for the sake of backward
|
66
|
+
# compatibility with extant Expectr scripts and may be deprecated
|
67
|
+
# in the future. (default: {})
|
68
|
+
# args - A Hash used to specify options for the instance. (default: {}):
|
69
|
+
# :timeout - Number of seconds that a call to Expectr#expect
|
70
|
+
# has to complete (default: 30)
|
71
|
+
# :flush_buffer - Whether to flush output of the process to the
|
72
|
+
# console (default: true)
|
73
|
+
# :buffer_size - Number of bytes to attempt to read from
|
74
|
+
# sub-process at a time. If :constrain is true,
|
75
|
+
# this will be the maximum size of the internal
|
76
|
+
# buffer as well. (default: 8192)
|
77
|
+
# :constrain - Whether to constrain the internal buffer from
|
78
|
+
# the sub-process to :buffer_size characters.
|
79
|
+
# (default: false)
|
80
|
+
# :interface - Interface Object to use when instantiating the
|
81
|
+
# new Expectr object. (default: Child)
|
82
|
+
def initialize(cmd_args = '', args = {})
|
75
83
|
setup_instance
|
76
84
|
parse_options(args)
|
77
85
|
|
78
|
-
|
79
|
-
|
80
|
-
interface = call_lambda_interface(args)
|
81
|
-
when :adopt
|
82
|
-
interface = call_adopt_interface(args)
|
83
|
-
else
|
84
|
-
interface = call_child_interface(cmd)
|
85
|
-
end
|
86
|
+
cmd_args = { cmd: cmd_args } unless cmd_args.is_a?(Hash)
|
87
|
+
args.merge!(cmd_args)
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
+
unless [:lambda, :adopt, :child].include?(args[:interface])
|
90
|
+
args[:interface] = :child
|
89
91
|
end
|
90
92
|
|
93
|
+
self.extend self.class.const_get(args[:interface].capitalize)
|
94
|
+
init_interface(args)
|
95
|
+
|
91
96
|
Thread.new { output_loop }
|
92
97
|
end
|
93
98
|
|
94
|
-
# Public:
|
99
|
+
# Public: Allow direct control of the running process from the controlling
|
95
100
|
# terminal, acting as a pass-through for the life of the process (or until
|
96
101
|
# the leave! method is called).
|
97
102
|
#
|
98
|
-
# args - A Hash used to specify options to be used for interaction
|
99
|
-
# {}):
|
103
|
+
# args - A Hash used to specify options to be used for interaction.
|
104
|
+
# (default: {}):
|
100
105
|
# :flush_buffer - explicitly set @flush_buffer to the value specified
|
101
106
|
# :blocking - Whether to block on this call or allow code
|
102
107
|
# execution to continue (default: false)
|
103
108
|
#
|
104
|
-
# Returns the interaction Thread
|
109
|
+
# Returns the interaction Thread, calling #join on it if :blocking is true.
|
105
110
|
def interact!(args = {})
|
106
111
|
if @interact
|
107
112
|
raise(ProcessError, Errstr::ALREADY_INTERACT)
|
@@ -111,23 +116,23 @@ class Expectr
|
|
111
116
|
args[:blocking] ? interact_thread.join : interact_thread
|
112
117
|
end
|
113
118
|
|
114
|
-
# Public: Report whether or not current Expectr object is in interact mode
|
119
|
+
# Public: Report whether or not current Expectr object is in interact mode.
|
115
120
|
#
|
116
|
-
# Returns
|
121
|
+
# Returns a boolean.
|
117
122
|
def interact?
|
118
123
|
@interact
|
119
124
|
end
|
120
125
|
|
121
|
-
# Public: Cause the current Expectr object to
|
126
|
+
# Public: Cause the current Expectr object to leave interact mode.
|
122
127
|
#
|
123
128
|
# Returns nothing.
|
124
129
|
def leave!
|
125
130
|
@interact=false
|
126
131
|
end
|
127
132
|
|
128
|
-
# Public: Wraps Expectr#send, appending a newline to the end of the string
|
133
|
+
# Public: Wraps Expectr#send, appending a newline to the end of the string.
|
129
134
|
#
|
130
|
-
# str - String to be sent to the active process (default: '')
|
135
|
+
# str - String to be sent to the active process. (default: '')
|
131
136
|
#
|
132
137
|
# Returns nothing.
|
133
138
|
def puts(str = '')
|
@@ -135,7 +140,8 @@ class Expectr
|
|
135
140
|
end
|
136
141
|
|
137
142
|
# Public: Begin a countdown and search for a given String or Regexp in the
|
138
|
-
# output buffer
|
143
|
+
# output buffer, optionally taking further action based upon which, if any,
|
144
|
+
# match was found.
|
139
145
|
#
|
140
146
|
# pattern - Object String or Regexp representing pattern for which to
|
141
147
|
# search, or a Hash containing pattern -> Proc mappings to be
|
@@ -163,7 +169,7 @@ class Expectr
|
|
163
169
|
# "Second possibility" => -> { puts "option b" },
|
164
170
|
# default: => -> { puts "called on timeout" } }
|
165
171
|
# exp.expect(hash)
|
166
|
-
#
|
172
|
+
#
|
167
173
|
# Returns a MatchData object once a match is found if no block is given
|
168
174
|
# Yields the MatchData object representing the match
|
169
175
|
# Raises TypeError if something other than a String or Regexp is given
|
@@ -196,7 +202,7 @@ class Expectr
|
|
196
202
|
# /option 2/ => -> { puts "action 2" },
|
197
203
|
# :default => -> { puts "default" }
|
198
204
|
# })
|
199
|
-
#
|
205
|
+
#
|
200
206
|
# Calls the procedure associated with the pattern provided.
|
201
207
|
def expect_procmap(pattern_map)
|
202
208
|
pattern_map, pattern, recoverable = process_procmap(pattern_map)
|
@@ -215,7 +221,7 @@ class Expectr
|
|
215
221
|
nil
|
216
222
|
end
|
217
223
|
|
218
|
-
# Public: Clear output buffer
|
224
|
+
# Public: Clear output buffer.
|
219
225
|
#
|
220
226
|
# Returns nothing.
|
221
227
|
def clear_buffer!
|
@@ -226,9 +232,10 @@ class Expectr
|
|
226
232
|
|
227
233
|
private
|
228
234
|
|
229
|
-
# Internal: Print buffer to $stdout if
|
235
|
+
# Internal: Print buffer to $stdout if program output is expected to be
|
236
|
+
# echoed.
|
230
237
|
#
|
231
|
-
# buf - String to be printed to $stdout
|
238
|
+
# buf - String to be printed to $stdout.
|
232
239
|
#
|
233
240
|
# Returns nothing.
|
234
241
|
def print_buffer(buf)
|
@@ -236,19 +243,18 @@ class Expectr
|
|
236
243
|
$stdout.flush unless $stdout.sync
|
237
244
|
end
|
238
245
|
|
239
|
-
# Internal: Encode a String twice to force UTF-8 encoding, dropping
|
240
|
-
# problematic characters in the process.
|
241
|
-
#
|
242
|
-
# buf - String to be encoded.
|
243
|
-
#
|
244
|
-
# Returns the encoded String.
|
245
|
-
def force_utf8(buf)
|
246
|
+
# Internal: Encode a String twice to force UTF-8 encoding, dropping
|
247
|
+
# problematic characters in the process.
|
248
|
+
#
|
249
|
+
# buf - String to be encoded.
|
250
|
+
#
|
251
|
+
# Returns the encoded String.
|
252
|
+
def force_utf8(buf)
|
246
253
|
return buf if buf.valid_encoding?
|
247
|
-
buf.force_encoding('ISO-8859-1').encode('UTF-8', 'UTF-8', replace: nil)
|
248
|
-
end
|
254
|
+
buf.force_encoding('ISO-8859-1').encode('UTF-8', 'UTF-8', replace: nil)
|
255
|
+
end
|
249
256
|
|
250
|
-
# Internal:
|
251
|
-
# appropriately, allowing for default values where nothing is passed.
|
257
|
+
# Internal: Initialize instance variables based upon arguments provided.
|
252
258
|
#
|
253
259
|
# args - A Hash used to specify options for the new object (default: {}):
|
254
260
|
# :timeout - Number of seconds that a call to Expectr#expect has
|
@@ -283,7 +289,7 @@ class Expectr
|
|
283
289
|
|
284
290
|
# Internal: Handle data from the interface, forcing UTF-8 encoding, appending
|
285
291
|
# it to the internal buffer, and printing it to $stdout if appropriate.
|
286
|
-
#
|
292
|
+
#
|
287
293
|
# Returns nothing.
|
288
294
|
def process_output(buf)
|
289
295
|
force_utf8(buf)
|
@@ -302,8 +308,7 @@ class Expectr
|
|
302
308
|
# This method should be wrapped in a Timeout block or otherwise have some
|
303
309
|
# mechanism to break out of the loop.
|
304
310
|
#
|
305
|
-
# pattern
|
306
|
-
# watch.
|
311
|
+
# pattern - String or Regexp containing the pattern for which to watch.
|
307
312
|
#
|
308
313
|
# Returns a MatchData object containing the match found.
|
309
314
|
def check_match(pattern)
|
@@ -320,62 +325,8 @@ class Expectr
|
|
320
325
|
@thread = nil
|
321
326
|
end
|
322
327
|
|
323
|
-
# Internal: Call the Child Interface to instantiate the Expectr object.
|
324
|
-
#
|
325
|
-
# cmd - String or File object referencing the command to be run.
|
326
|
-
#
|
327
|
-
# Returns the Interface object.
|
328
|
-
def call_child_interface(cmd)
|
329
|
-
interface = Expectr::Child.new(cmd)
|
330
|
-
@stdin = interface.stdin
|
331
|
-
@stdout = interface.stdout
|
332
|
-
@pid = interface.pid
|
333
|
-
|
334
|
-
Thread.new do
|
335
|
-
Process.wait @pid
|
336
|
-
@pid = 0
|
337
|
-
end
|
338
|
-
|
339
|
-
interface
|
340
|
-
end
|
341
|
-
|
342
|
-
# Internal: Call the Lambda Interface to instantiate the Expectr object.
|
343
|
-
#
|
344
|
-
# args - Arguments hash passed per #initialize.
|
345
|
-
#
|
346
|
-
# Returns the Interface object.
|
347
|
-
def call_lambda_interface(args)
|
348
|
-
interface = Expectr::Lambda.new(args[:reader], args[:writer])
|
349
|
-
@pid = -1
|
350
|
-
@reader = interface.reader
|
351
|
-
@writer = interface.writer
|
352
|
-
|
353
|
-
interface
|
354
|
-
end
|
355
|
-
|
356
|
-
# Internal: Call the Adopt Interface to instantiate the Expectr object.
|
357
|
-
#
|
358
|
-
# args - Arguments hash passed per #initialize.
|
359
|
-
#
|
360
|
-
# Returns the Interface object.
|
361
|
-
def call_adopt_interface(args)
|
362
|
-
interface = Expectr::Adopt.new(args[:stdin], args[:stdout])
|
363
|
-
@stdin = interface.stdin
|
364
|
-
@stdout = interface.stdout
|
365
|
-
@pid = args[:pid] || 1
|
366
|
-
|
367
|
-
if @pid > 0
|
368
|
-
Thread.new do
|
369
|
-
Process.wait @pid
|
370
|
-
@pid = 0
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
interface
|
375
|
-
end
|
376
|
-
|
377
328
|
# Internal: Watch for a match within the timeout period.
|
378
|
-
#
|
329
|
+
#
|
379
330
|
# pattern - String or Regexp object containing the pattern for which to
|
380
331
|
# watch.
|
381
332
|
# recoverable - Boolean denoting whether a failure to find a match should be
|
@@ -404,23 +355,31 @@ class Expectr
|
|
404
355
|
# Internal: Process a pattern to procedure mapping, producing a sanitized
|
405
356
|
# Hash, a unified Regexp and a boolean denoting whether an Exception should
|
406
357
|
# be raised upon timeout.
|
407
|
-
#
|
358
|
+
#
|
408
359
|
# pattern_map - A Hash containing mappings between patterns designated by
|
409
360
|
# either strings or Regexp objects, to procedures. Optionally,
|
410
361
|
# either :default or :timeout may be mapped to a procedure in
|
411
|
-
# order to designate an action to take upon
|
362
|
+
# order to designate an action to take upon failure to match
|
363
|
+
# any other pattern.
|
412
364
|
#
|
413
365
|
# Returns a Hash, Regexp and boolean object.
|
414
366
|
def process_procmap(pattern_map)
|
367
|
+
# Normalize Hash keys, allowing only Regexps and Symbols for keys.
|
415
368
|
pattern_map = pattern_map.reduce({}) do |c,e|
|
416
|
-
|
369
|
+
unless e[0].is_a?(Symbol) || e[0].is_a?(Regexp)
|
370
|
+
e[0] = Regexp.new(Regexp.escape(e[0].to_s))
|
371
|
+
end
|
372
|
+
c.merge(e[0] => e[1])
|
417
373
|
end
|
418
|
-
|
419
|
-
|
374
|
+
|
375
|
+
# Separate out non-Symbol keys and build a unified Regexp.
|
376
|
+
regex_keys = pattern_map.keys.select { |e| e.is_a?(Regexp) }
|
377
|
+
pattern = regex_keys.reduce("") do |c,e|
|
378
|
+
c += "|" unless c.empty?
|
379
|
+
c + "(#{e.source})"
|
420
380
|
end
|
421
|
-
|
422
|
-
recoverable
|
423
|
-
recoverable ||= pattern_map.keys.include?(:timeout)
|
381
|
+
|
382
|
+
recoverable = regex_keys.include?(:default) || regex_keys.include?(:timeout)
|
424
383
|
|
425
384
|
return pattern_map, pattern, recoverable
|
426
385
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: expectr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Wuest
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Expectr is an interface to the functionality of Expect in Ruby
|
14
14
|
email: chris@chriswuest.com
|
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
47
|
version: '0'
|
48
48
|
requirements: []
|
49
49
|
rubyforge_project:
|
50
|
-
rubygems_version: 2.0.
|
50
|
+
rubygems_version: 2.0.5
|
51
51
|
signing_key:
|
52
52
|
specification_version: 4
|
53
53
|
summary: Expect for Ruby
|