expectr 2.0.0 → 2.0.1
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.
- 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
|