expectr 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/expectr +22 -0
- data/lib/expectr/adopt.rb +50 -0
- data/lib/expectr/child.rb +165 -0
- data/lib/expectr/error.rb +13 -2
- data/lib/expectr/errstr.rb +33 -0
- data/lib/expectr/interface.rb +53 -0
- data/lib/expectr/interpreter.rb +119 -0
- data/lib/expectr/lambda.rb +131 -0
- data/lib/expectr/version.rb +1 -1
- data/lib/expectr.rb +209 -175
- metadata +17 -25
- data/Gemfile +0 -2
- data/LICENSE +0 -20
- data/README.rdoc +0 -51
- data/Rakefile +0 -18
- data/expectr.gemspec +0 -18
- data/test/helper.rb +0 -4
- data/test/test_core.rb +0 -50
- data/test/test_initialization.rb +0 -17
- data/test/test_interaction.rb +0 -112
- data/test/test_signals.rb +0 -36
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eb60ae1f64e5710fe3e5cfdff6e49bf938a9aaa3
|
4
|
+
data.tar.gz: 98fd24b1cadf73f5c88a313dd48cd1edb0e6f64d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1748339aa16a902c05c7b19a17e6469a94eaf3a2e21d3143ac92a2291d932cd52a3ecad280607efaa409ec7fb1cd017e5945c09d5a9bafda4a2906800a86a64c
|
7
|
+
data.tar.gz: 7d21a1bc05f4067f823d0f0292f1242dcee4bb4952d7df7ca520f8287e5aa4280de8e5b281f4f0b60c9321ae53bf45a76171abc35c7b48ea139067a2e00bc0b2
|
data/bin/expectr
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'expectr'
|
4
|
+
require 'expectr/interpreter'
|
5
|
+
|
6
|
+
class Expectr
|
7
|
+
module Main extend self
|
8
|
+
# Public: Read and run source passed in to the interpreter.
|
9
|
+
#
|
10
|
+
# Returns nothing.
|
11
|
+
def run
|
12
|
+
src = ARGF.read
|
13
|
+
exit 2 unless src
|
14
|
+
|
15
|
+
interpreter = Expectr::Interpreter.new(src.untaint)
|
16
|
+
interpreter.filename = $FILENAME
|
17
|
+
interpreter.run
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Expectr::Main.run
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'expectr'
|
2
|
+
require 'expectr/interface'
|
3
|
+
require 'expectr/child'
|
4
|
+
|
5
|
+
class Expectr
|
6
|
+
# Internal: The Expectr::Adopt Class contains the interface to interacting
|
7
|
+
# with child processes not spawned by Expectr.
|
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 Adopt < Expectr::Child
|
13
|
+
# Public: Initialize a new Expectr::Adopt object.
|
14
|
+
# IO Objects are named in such a way as to maintain interoperability with
|
15
|
+
# the methods from the Expectr::Child class.
|
16
|
+
#
|
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 adopted
|
20
|
+
# (default: 1)
|
21
|
+
#
|
22
|
+
# Raises TypeError if arguments are of type other than IO.
|
23
|
+
def initialize(stdin, stdout)
|
24
|
+
unless stdin.kind_of?(IO) && stdout.kind_of?(IO)
|
25
|
+
raise(TypeError, Errstr::IO_EXPECTED)
|
26
|
+
end
|
27
|
+
@stdin = stdin
|
28
|
+
@stdout = stdout
|
29
|
+
@stdout.winsize = $stdout.winsize if $stdout.tty?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Present a streamlined interface to create a new Expectr instance.
|
33
|
+
#
|
34
|
+
# stdout - IO object open for reading.
|
35
|
+
# stdin - IO object open for writing.
|
36
|
+
# pid - FixNum corresponding to the PID of the process being adopted
|
37
|
+
# (default: 1)
|
38
|
+
# args - A Hash used to specify options for the new object, per
|
39
|
+
# Expectr#initialize.
|
40
|
+
#
|
41
|
+
# Returns a new Expectr object
|
42
|
+
def self.spawn(stdout, stdin, pid = 1, args = {})
|
43
|
+
args[:interface] = :adopt
|
44
|
+
args[:stdin] = stdin
|
45
|
+
args[:stdout] = stdout
|
46
|
+
args[:pid] = pid
|
47
|
+
Expectr.new('', args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'pty'
|
2
|
+
require 'expectr/interface'
|
3
|
+
|
4
|
+
class Expectr
|
5
|
+
# Internal: The Expectr::Child class contains the interface to interacting
|
6
|
+
# 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
|
12
|
+
include Expectr::Interface
|
13
|
+
attr_reader :stdin
|
14
|
+
attr_reader :stdout
|
15
|
+
attr_reader :pid
|
16
|
+
|
17
|
+
# Public: Initialize a new Expectr::Child object.
|
18
|
+
# Spawns a sub-process and attaches to STDIN and STDOUT for the new
|
19
|
+
# process.
|
20
|
+
#
|
21
|
+
# cmd - A String or File referencing the application to launch.
|
22
|
+
#
|
23
|
+
# Raises TypeError if argument is anything other than String or File.
|
24
|
+
def initialize(cmd)
|
25
|
+
cmd = cmd.path if cmd.kind_of?(File)
|
26
|
+
unless cmd.kind_of?(String)
|
27
|
+
raise(TypeError, Errstr::STRING_FILE_EXPECTED)
|
28
|
+
end
|
29
|
+
|
30
|
+
@stdout,@stdin,@pid = PTY.spawn(cmd)
|
31
|
+
@stdout.winsize = $stdout.winsize if $stdout.tty?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Send a signal to the running child process.
|
35
|
+
#
|
36
|
+
# signal - Symbol, String or FixNum corresponding to the symbol to be sent
|
37
|
+
# to the running process. (default: :TERM)
|
38
|
+
#
|
39
|
+
# Returns a boolean indicating whether the process was successfully sent
|
40
|
+
# the signal.
|
41
|
+
# Raises ProcessError if the process is not running (@pid = 0).
|
42
|
+
def interface_kill!
|
43
|
+
->(signal = :TERM) {
|
44
|
+
unless @pid > 0
|
45
|
+
raise(ProcessError, Errstr::PROCESS_NOT_RUNNING)
|
46
|
+
end
|
47
|
+
Process::kill(signal.to_sym, @pid) == 1
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: Send input to the active child process.
|
52
|
+
#
|
53
|
+
# str - String to be sent.
|
54
|
+
#
|
55
|
+
# Returns nothing.
|
56
|
+
# Raises Expectr::ProcessError if the process is not running (@pid = 0)
|
57
|
+
def interface_send
|
58
|
+
->(str) {
|
59
|
+
begin
|
60
|
+
@stdin.syswrite str
|
61
|
+
rescue Errno::EIO #Application is not running
|
62
|
+
@pid = 0
|
63
|
+
end
|
64
|
+
unless @pid > 0
|
65
|
+
raise(Expectr::ProcessError, Errstr::PROCESS_GONE)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: Read the child process's output, force UTF-8 encoding, then
|
71
|
+
# append to the internal buffer and print to $stdout if appropriate.
|
72
|
+
#
|
73
|
+
# Returns nothing.
|
74
|
+
def interface_output_loop
|
75
|
+
-> {
|
76
|
+
while @pid > 0
|
77
|
+
unless select([@stdout], nil, nil, @timeout).nil?
|
78
|
+
buf = ''
|
79
|
+
|
80
|
+
begin
|
81
|
+
@stdout.sysread(@buffer_size, buf)
|
82
|
+
rescue Errno::EIO #Application is not running
|
83
|
+
@pid = 0
|
84
|
+
return
|
85
|
+
end
|
86
|
+
process_output(buf)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def interface_prepare_interact_environment
|
93
|
+
-> {
|
94
|
+
env = {sig: {}}
|
95
|
+
|
96
|
+
# Save old tty settings and set up the new environment
|
97
|
+
env[:tty] = `stty -g`
|
98
|
+
`stty -icanon min 1 time 0 -echo`
|
99
|
+
|
100
|
+
# SIGINT should be sent to the child as \C-c
|
101
|
+
env[:sig]['INT'] = trap 'INT' do
|
102
|
+
send "\C-c"
|
103
|
+
end
|
104
|
+
|
105
|
+
# SIGTSTP should be sent to the process as \C-z
|
106
|
+
env[:sig]['TSTP'] = trap 'TSTP' do
|
107
|
+
send "\C-z"
|
108
|
+
end
|
109
|
+
|
110
|
+
# SIGWINCH should trigger an update to the child processes window size
|
111
|
+
env[:sig]['WINCH'] = trap 'WINCH' do
|
112
|
+
@stdout.winsize = $stdout.winsize
|
113
|
+
end
|
114
|
+
|
115
|
+
env
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
# Public: Create a Thread containing the loop which is responsible for
|
120
|
+
# handling input from the user in interact mode.
|
121
|
+
#
|
122
|
+
# Returns a Thread containing the running loop.
|
123
|
+
def interface_interact_thread
|
124
|
+
-> {
|
125
|
+
@interact = true
|
126
|
+
env = prepare_interact_environment
|
127
|
+
Thread.new do
|
128
|
+
begin
|
129
|
+
input = ''
|
130
|
+
|
131
|
+
while @pid > 0 && @interact
|
132
|
+
if select([$stdin], nil, nil, 1)
|
133
|
+
c = $stdin.getc.chr
|
134
|
+
send c unless c.nil?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
ensure
|
138
|
+
restore_environment(env)
|
139
|
+
end
|
140
|
+
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)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/expectr/error.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
class Expectr
|
2
|
-
# Public:
|
3
|
-
#
|
2
|
+
# Public: Denotes a problem with the running Process associated with a
|
3
|
+
# Child Interface
|
4
4
|
class ProcessError < StandardError; end
|
5
|
+
|
6
|
+
module Interface
|
7
|
+
# Public: Denotes an interface which cannot be killed (e.g. Lambda Interface)
|
8
|
+
class NotKillableError < StandardError; end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Interpreter
|
12
|
+
# Public: Denotes that input was attempted to be sent to a non-existant
|
13
|
+
# process.
|
14
|
+
class NotRunningError < StandardError; end
|
15
|
+
end
|
5
16
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Expectr
|
2
|
+
module Errstr
|
3
|
+
EXPECT_WRONG_TYPE = "Pattern should be of class String, Regexp, or Hash"
|
4
|
+
ALREADY_INTERACT = "Already in interact mode"
|
5
|
+
end
|
6
|
+
|
7
|
+
class Child
|
8
|
+
module Errstr
|
9
|
+
STRING_FILE_EXPECTED = "Command should be of type String or File"
|
10
|
+
PROCESS_NOT_RUNNING = "No process is running"
|
11
|
+
PROCESS_GONE = "Child process no longer exists"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Adopt < Child
|
16
|
+
module Errstr
|
17
|
+
IO_EXPECTED = "Arguments of type IO expected"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Interpreter
|
22
|
+
module Errstr
|
23
|
+
BOOLEAN_OR_FIXNUM = "Boolean or Fixnum expected, received a %s"
|
24
|
+
PROCESS_NOT_RUNNING = "No process is running"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Lambda
|
29
|
+
module Errstr
|
30
|
+
PROC_EXPECTED = "Proc Objects expected for reader and writer"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Expectr
|
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
|
+
# Public: Return a Thread which does nothing, representing an interface
|
19
|
+
# with no functional interact environment available.
|
20
|
+
#
|
21
|
+
# Returns a Thread.
|
22
|
+
def interface_interact_thread
|
23
|
+
-> {
|
24
|
+
Thread.new { }
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Return an empty Hash representing a case where no action needed
|
29
|
+
# to be taken in order to prepare the environment for interact mode.
|
30
|
+
#
|
31
|
+
# Returns an empty Hash.
|
32
|
+
def interface_prepare_interact_interface
|
33
|
+
-> {
|
34
|
+
{}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Internal: Restore environment after interact mode has been left.
|
39
|
+
#
|
40
|
+
# Returns nothing.
|
41
|
+
def interface_restore_environment
|
42
|
+
->(env) {
|
43
|
+
env[:sig].each do |signal, handler|
|
44
|
+
trap signal, handler
|
45
|
+
end
|
46
|
+
unless env[:tty].nil?
|
47
|
+
`stty #{env[:tty]}`
|
48
|
+
end
|
49
|
+
@interact = false
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'expectr'
|
2
|
+
|
3
|
+
class Expectr
|
4
|
+
# Internal: Provide an interface to Expectr in a manner designed to mimic
|
5
|
+
# Expect's original functionality.
|
6
|
+
class Interpreter
|
7
|
+
DEFAULT_TIMEOUT = 10
|
8
|
+
|
9
|
+
# Public: Filename of currently executing script.
|
10
|
+
attr_accessor :filename
|
11
|
+
|
12
|
+
# Public: Initialize a new Expectr interface.
|
13
|
+
#
|
14
|
+
# source - String containing the source to be executed.
|
15
|
+
def initialize(source)
|
16
|
+
@source = source
|
17
|
+
@variables = { timeout: DEFAULT_TIMEOUT }
|
18
|
+
@expect = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Run the source associated with the Interface object, bound to the
|
22
|
+
# Object.
|
23
|
+
#
|
24
|
+
# Returns nothing.
|
25
|
+
def run
|
26
|
+
eval(@source, binding, (@filename || "(expectr)"), 0)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Print one or more messages to $stdout.
|
30
|
+
#
|
31
|
+
# str - String or Array of Strings to print to $stdout.
|
32
|
+
#
|
33
|
+
# Returns nothing.
|
34
|
+
def send_user(*str)
|
35
|
+
str.each { |line| $stdout.print line }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Set whether output from the active Expectr object should be
|
39
|
+
# echoed to the user.
|
40
|
+
#
|
41
|
+
# enable - Boolean value denoting whether output to the screen should be
|
42
|
+
# enabled.
|
43
|
+
# In order to keep compatibility with Expect, a Fixnum can be
|
44
|
+
# passed, such that any number greater than 0 is evaluated as
|
45
|
+
# true, and 0 or less is false.
|
46
|
+
#
|
47
|
+
# Returns nothing.
|
48
|
+
# Raises Argument
|
49
|
+
def log_user(enable)
|
50
|
+
if enable.is_a?(Numeric)
|
51
|
+
enable = (enable > 0)
|
52
|
+
end
|
53
|
+
unless enable.is_a?(TrueClass) || enable.is_a?(FalseClass)
|
54
|
+
raise(TypeError, Errstr::BOOLEAN_OR_FIXNUM % enable.class.name)
|
55
|
+
end
|
56
|
+
|
57
|
+
set(:flush_buffer, enable)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Spawn an instance of a command via Expectr.
|
61
|
+
#
|
62
|
+
# cmd - String referencing the application to spawn.
|
63
|
+
#
|
64
|
+
# Returns nothing.
|
65
|
+
def spawn(cmd)
|
66
|
+
@expect = Expectr.new(cmd, @variables)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Provide an interface to Expectr#expect.
|
70
|
+
#
|
71
|
+
# args - Arguments to be passed through to the Expectr object.
|
72
|
+
#
|
73
|
+
# Returns per Expectr#expect.
|
74
|
+
def expect(*args)
|
75
|
+
@expect.expect(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Send a String to the process referenced by the active Expectr
|
79
|
+
# object.
|
80
|
+
#
|
81
|
+
# str - String to send to the process.
|
82
|
+
#
|
83
|
+
# Returns nothing.
|
84
|
+
# Raises NotRunningError if no Expectr object is active.
|
85
|
+
def send(str)
|
86
|
+
if @expect.nil?
|
87
|
+
raise(NotRunningError, Errstr::PROCESS_NOT_RUNNING)
|
88
|
+
end
|
89
|
+
@expect.send(str)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: Terminate any process associated with the Interpreter.
|
93
|
+
#
|
94
|
+
# Returns nothing.
|
95
|
+
def close
|
96
|
+
@expect.kill!(:KILL) if @expect.respond_to?(:kill!)
|
97
|
+
rescue Expectr::ProcessError
|
98
|
+
ensure
|
99
|
+
@expect = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Internal: Set value in internal Hash and update attr_accessor value in
|
105
|
+
# the active Expectr object if applicable.
|
106
|
+
#
|
107
|
+
# variable_name - Name of key to set in the internal Hash.
|
108
|
+
# value - Value to associate with the key.
|
109
|
+
#
|
110
|
+
# Returns nothing.
|
111
|
+
def set(variable_name, value)
|
112
|
+
@variables[variable_name] = value
|
113
|
+
method_name = (variable_name.to_s + "=").to_sym
|
114
|
+
if @expect.methods.include?(method_name)
|
115
|
+
@expect.method(method_name).call(value)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'expectr'
|
2
|
+
require 'expectr/interface'
|
3
|
+
|
4
|
+
class Expectr
|
5
|
+
# Internal: The Expectr::Lambda Class contains the interface to interacting
|
6
|
+
# with ruby objects transparently via lambdas in a manner consistent with
|
7
|
+
# other Expectr interfaces.
|
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
|
13
|
+
include Expectr::Interface
|
14
|
+
attr_reader :reader
|
15
|
+
attr_reader :writer
|
16
|
+
|
17
|
+
# Public: Initialize a new Expectr::Lambda object.
|
18
|
+
#
|
19
|
+
# reader - Lambda which is meant to be interacted with as if it were
|
20
|
+
# analogous to STDIN for a child process.
|
21
|
+
# writer - Lambda which is meant to be interacted with as if it were
|
22
|
+
# analogous to STDOUT for a child process.
|
23
|
+
#
|
24
|
+
# Raises TypeError if arguments aren't of type Proc.
|
25
|
+
def initialize(reader, writer)
|
26
|
+
unless reader.kind_of?(Proc) && writer.kind_of?(Proc)
|
27
|
+
raise(TypeError, Errstr::PROC_EXPECTED)
|
28
|
+
end
|
29
|
+
|
30
|
+
@reader = reader
|
31
|
+
@writer = writer
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Present a streamlined interface to create a new Expectr instance.
|
35
|
+
#
|
36
|
+
# reader - Lambda which is meant to be interacted with as if it were
|
37
|
+
# analogous to STDIN for a child process.
|
38
|
+
# writer - Lambda which is meant to be interacted with as if it were
|
39
|
+
# analogous to STDOUT for a child process.
|
40
|
+
# args - A Hash used to specify options for the new object, per
|
41
|
+
# Expectr#initialize.
|
42
|
+
#
|
43
|
+
# Returns a new Expectr object
|
44
|
+
def self.spawn(reader, writer, args = {})
|
45
|
+
args[:interface] = :lambda
|
46
|
+
args[:reader] = reader
|
47
|
+
args[:writer] = writer
|
48
|
+
Expectr.new('', args)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: Send input to the active child process.
|
52
|
+
#
|
53
|
+
# args - Arguments to pass to the reader interface.
|
54
|
+
#
|
55
|
+
# Returns nothing.
|
56
|
+
def interface_send
|
57
|
+
->(args) {
|
58
|
+
@reader.call(*args)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Prepare the operating environment for interact mode, set the
|
63
|
+
# interact flag to true.
|
64
|
+
#
|
65
|
+
# Returns a Hash containing old signal handlers and tty parameters.
|
66
|
+
def interface_prepare_interact_environment
|
67
|
+
-> {
|
68
|
+
env = {sig: {}}
|
69
|
+
|
70
|
+
# Save old tty settings and set up the new environment
|
71
|
+
env[:tty] = `stty -g`
|
72
|
+
`stty -icanon min 1 time 0 -echo`
|
73
|
+
|
74
|
+
# SIGINT should be sent to the child as \C-c
|
75
|
+
env[:sig]['INT'] = trap 'INT' do
|
76
|
+
send "\C-c"
|
77
|
+
end
|
78
|
+
|
79
|
+
# SIGTSTP should be sent to the process as \C-z
|
80
|
+
env[:sig]['TSTP'] = trap 'TSTP' do
|
81
|
+
send "\C-z"
|
82
|
+
end
|
83
|
+
|
84
|
+
@interact = true
|
85
|
+
env
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Create a Thread containing the loop which is responsible for
|
90
|
+
# handling input from the user in interact mode.
|
91
|
+
#
|
92
|
+
# Returns a Thread containing the running loop.
|
93
|
+
def interface_interact_thread
|
94
|
+
-> {
|
95
|
+
Thread.new do
|
96
|
+
env = prepare_interact_environment
|
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
|
105
|
+
|
106
|
+
restore_environment(env)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
# Internal: Call the writer lambda, reading the output, forcing UTF-8 and
|
112
|
+
# appending to the internal buffer and printing to $stdout if appropriate.
|
113
|
+
#
|
114
|
+
# Returns nothing.
|
115
|
+
def interface_output_loop
|
116
|
+
-> {
|
117
|
+
buf = ''
|
118
|
+
loop do
|
119
|
+
buf.clear
|
120
|
+
|
121
|
+
begin
|
122
|
+
buf << @writer.call.to_s
|
123
|
+
rescue Errno::EIO # Lambda is signaling that execution should end.
|
124
|
+
return
|
125
|
+
end
|
126
|
+
process_output(buf)
|
127
|
+
end
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/expectr/version.rb
CHANGED