expectr 1.1.1 → 2.0.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.
- 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