expect-pty 0.6.1 → 0.7.2
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/CHANGELOG.md +34 -3
- data/README.md +103 -120
- data/docs/API.md +76 -51
- data/docs/MIGRATION.md +68 -15
- data/lib/expect/cleanup.rb +4 -3
- data/lib/expect/interaction.rb +57 -56
- data/lib/expect/logging.rb +60 -106
- data/lib/expect/matcher.rb +22 -19
- data/lib/expect/pattern.rb +2 -1
- data/lib/expect/pattern_list.rb +5 -5
- data/lib/expect/redactor.rb +1 -1
- data/lib/expect/relay.rb +20 -19
- data/lib/expect/relay_writer.rb +5 -4
- data/lib/expect/result.rb +1 -1
- data/lib/expect/session.rb +114 -68
- data/lib/expect/session_resources.rb +10 -17
- data/lib/expect/version.rb +2 -2
- data/lib/expect.rb +47 -370
- data/sig/expect.rbs +203 -279
- metadata +5 -21
- data/lib/expect/configuration.rb +0 -143
- data/lib/expect/terminal.rb +0 -102
data/lib/expect/configuration.rb
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Expect
|
|
4
|
-
# 集中校验会话配置。类级默认值以冻结快照发布,每个会话再构造独立副本。
|
|
5
|
-
# 这里只保存策略值,不持有 IO、缓冲或日志对象;修改默认配置不会追溯影响已创建的会话。
|
|
6
|
-
class Configuration
|
|
7
|
-
# 各配置项的初始值;会话仅保存经 setter 验证后的副本。
|
|
8
|
-
DEFAULTS = {
|
|
9
|
-
timeout: nil, write_timeout: nil, buffer_limit: nil, debug_level: 0,
|
|
10
|
-
raw_pty: false, preserve_buffer: false, log_stdout: false,
|
|
11
|
-
log_listeners: true, raw_terminal: true, reset_timeout_on_read: false,
|
|
12
|
-
graceful_close: false
|
|
13
|
-
}.freeze
|
|
14
|
-
# 属性委托和快照导出使用同一配置清单。
|
|
15
|
-
ATTRIBUTES = DEFAULTS.keys.freeze
|
|
16
|
-
# 配置键与读取接口分开:布尔值只提供谓词,导出仍使用原配置键。
|
|
17
|
-
READERS = DEFAULTS.to_h do |name, value|
|
|
18
|
-
[name, [true, false].include?(value) ? :"#{name}?" : name]
|
|
19
|
-
end.freeze
|
|
20
|
-
|
|
21
|
-
attr_reader :timeout, :write_timeout, :buffer_limit, :debug_level
|
|
22
|
-
|
|
23
|
-
# 布尔读写遵循 Ruby 真值规则,仅提供问号查询和 setter。
|
|
24
|
-
def self.boolean_attribute(name)
|
|
25
|
-
define_method(:"#{name}?") { instance_variable_get(:"@#{name}") }
|
|
26
|
-
|
|
27
|
-
define_method(:"#{name}=") { |value| instance_variable_set(:"@#{name}", !!value) }
|
|
28
|
-
end
|
|
29
|
-
private_class_method :boolean_attribute
|
|
30
|
-
|
|
31
|
-
# 先拒绝未知键,再经 setter 校验;发布中的冻结快照不会被部分修改。
|
|
32
|
-
def initialize(**options)
|
|
33
|
-
unknown = options.keys - ATTRIBUTES
|
|
34
|
-
raise ArgumentError, "unknown configuration: #{unknown.join(", ")}" unless unknown.empty?
|
|
35
|
-
|
|
36
|
-
DEFAULTS.merge(options).each { |name, value| public_send(:"#{name}=", value) }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# 设置匹配等待的默认秒数;nil 表示无限等待,0 表示只轮询现有数据。
|
|
40
|
-
def timeout=(value)
|
|
41
|
-
@timeout = Expect.duration(value)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# 设置写入背压的等待期限;先转换和校验,失败时保留原配置。
|
|
45
|
-
def write_timeout=(value)
|
|
46
|
-
@write_timeout = Expect.duration(value)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# 限制接收缓冲保留的尾部字节数;正整数为上限,nil 为无限。
|
|
50
|
-
def buffer_limit=(value)
|
|
51
|
-
unless value.nil? || (value.is_a?(Integer) && value.positive?)
|
|
52
|
-
raise ArgumentError, "buffer_limit must be a positive Integer or nil"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
@buffer_limit = value
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# 设置诊断详细程度:0 关闭,1 生命周期与匹配,2 收发内容,3 缓冲内容。
|
|
59
|
-
def debug_level=(value)
|
|
60
|
-
unless value.is_a?(Integer) && (0..3).cover?(value)
|
|
61
|
-
raise ArgumentError, "debug_level must be an Integer between 0 and 3"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
@debug_level = value
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# 控制 spawn 前是否将子进程终端设为 raw,关闭回显和换行转换。
|
|
68
|
-
# @!method raw_pty?
|
|
69
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
70
|
-
# @return [Boolean] 当前布尔配置。
|
|
71
|
-
# @!method raw_pty=(value)
|
|
72
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
73
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
74
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
75
|
-
boolean_attribute :raw_pty
|
|
76
|
-
|
|
77
|
-
# 控制匹配成功后是否保留完整缓冲;启用时由继续回调自行消费匹配内容。
|
|
78
|
-
# @!method preserve_buffer?
|
|
79
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
80
|
-
# @return [Boolean] 当前布尔配置。
|
|
81
|
-
# @!method preserve_buffer=(value)
|
|
82
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
83
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
84
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
85
|
-
boolean_attribute :preserve_buffer
|
|
86
|
-
|
|
87
|
-
# 控制接收字节是否同步输出到当前 $stdout;默认关闭。
|
|
88
|
-
# @!method log_stdout?
|
|
89
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
90
|
-
# @return [Boolean] 当前布尔配置。
|
|
91
|
-
# @!method log_stdout=(value)
|
|
92
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
93
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
94
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
95
|
-
boolean_attribute :log_stdout
|
|
96
|
-
|
|
97
|
-
# 控制接收字节是否转发给监听器,与 stdout 和日志目标分别管理。
|
|
98
|
-
# @!method log_listeners?
|
|
99
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
100
|
-
# @return [Boolean] 当前布尔配置。
|
|
101
|
-
# @!method log_listeners=(value)
|
|
102
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
103
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
104
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
105
|
-
boolean_attribute :log_listeners
|
|
106
|
-
|
|
107
|
-
# 控制 interact 是否临时设置并恢复本地输入终端;通用 interconnect 不修改终端模式。
|
|
108
|
-
# @!method raw_terminal?
|
|
109
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
110
|
-
# @return [Boolean] 当前布尔配置。
|
|
111
|
-
# @!method raw_terminal=(value)
|
|
112
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
113
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
114
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
115
|
-
boolean_attribute :raw_terminal
|
|
116
|
-
|
|
117
|
-
# 控制收到任何新数据时是否刷新匹配期限,适用于按静默时长判断超时。
|
|
118
|
-
# 只刷新相对 timeout;单次等待显式指定的绝对 deadline 仍是不可延长的上限。
|
|
119
|
-
# @!method reset_timeout_on_read?
|
|
120
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
121
|
-
# @return [Boolean] 当前布尔配置。
|
|
122
|
-
# @!method reset_timeout_on_read=(value)
|
|
123
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
124
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
125
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
126
|
-
boolean_attribute :reset_timeout_on_read
|
|
127
|
-
|
|
128
|
-
# 控制通用 close 是否先软关闭;最终资源清理仍由硬关闭兜底。
|
|
129
|
-
# @!method graceful_close?
|
|
130
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
131
|
-
# @return [Boolean] 当前布尔配置。
|
|
132
|
-
# @!method graceful_close=(value)
|
|
133
|
-
# 查询或设置此会话策略,使用 Ruby 真值规则。
|
|
134
|
-
# @param value [Object] 除 nil/false 外均转换为 true。
|
|
135
|
-
# @return [Boolean] 归一化的布尔配置。
|
|
136
|
-
boolean_attribute :graceful_close
|
|
137
|
-
|
|
138
|
-
# 导出新的属性 Hash,用于构造会话副本或发布下一份默认配置。
|
|
139
|
-
def to_h
|
|
140
|
-
READERS.to_h { |name, reader| [name, public_send(reader)] }
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
data/lib/expect/terminal.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "shellwords"
|
|
4
|
-
|
|
5
|
-
# 会话终端的模式和窗口尺寸接口;人工接管期间的临时恢复由 interaction.rb 负责。
|
|
6
|
-
class Expect
|
|
7
|
-
# @api private
|
|
8
|
-
module Terminal
|
|
9
|
-
# 查询可恢复的终端模式字符串,或通过系统 stty 设置模式;参数按数组传递,不经 shell。
|
|
10
|
-
# 辅助进程与管道独立记账,不能覆盖主会话 PID;失败时保留原异常并有界回收。
|
|
11
|
-
def stty(*modes)
|
|
12
|
-
return "" unless tty?
|
|
13
|
-
|
|
14
|
-
modes = modes.flat_map { |mode| Shellwords.split(mode.to_s) }
|
|
15
|
-
modes = ["-g"] if modes.empty?
|
|
16
|
-
reader = sink = resources = nil
|
|
17
|
-
Cleanup.always(-> { cleanup_stty(resources, reader, sink) }) do
|
|
18
|
-
reader, sink = IO.pipe
|
|
19
|
-
resources = SessionResources.new(reader, writer: sink, own: true)
|
|
20
|
-
resources.pid = Process.spawn("stty", *modes, in: to_io, out: sink, err: sink)
|
|
21
|
-
sink.close
|
|
22
|
-
output = reader.read
|
|
23
|
-
status = wait_stty(resources.pid)
|
|
24
|
-
resources.pid = nil
|
|
25
|
-
raise IOError, "stty failed: #{output.strip}" unless status.success?
|
|
26
|
-
|
|
27
|
-
output.strip
|
|
28
|
-
rescue Errno::ENOENT
|
|
29
|
-
raise IOError, "stty executable not found in PATH; install the system terminal utilities"
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# 读取终端的 [行数, 列数];底层并非终端或句柄已关闭时保留原生 IO 异常。
|
|
34
|
-
def winsize = to_io.winsize
|
|
35
|
-
|
|
36
|
-
# 更新终端尺寸,由内核通知前台进程。
|
|
37
|
-
def winsize=(size)
|
|
38
|
-
to_io.winsize = size
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
# 正常路径阻塞取得真实状态,不额外轮询或固定等待;EINTR 不重新执行 stty。
|
|
44
|
-
def wait_stty(pid)
|
|
45
|
-
Process.waitpid2(pid).last
|
|
46
|
-
rescue Errno::EINTR
|
|
47
|
-
retry
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# 即使账本初始化被中断,局部变量中的管道仍须关闭;单端失败不跳过其他清理。
|
|
51
|
-
# 无主异常时由调用方传播首个常规清理错误。
|
|
52
|
-
def cleanup_stty(resources, reader, sink)
|
|
53
|
-
failure = nil
|
|
54
|
-
[reader, sink].compact.each do |io|
|
|
55
|
-
io.close unless io.closed?
|
|
56
|
-
rescue IOError, SystemCallError => error
|
|
57
|
-
failure ||= error
|
|
58
|
-
end
|
|
59
|
-
begin
|
|
60
|
-
reap_stty(resources) if resources
|
|
61
|
-
rescue IOError, SystemCallError => error
|
|
62
|
-
failure ||= error
|
|
63
|
-
end
|
|
64
|
-
raise failure if failure
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# 每阶段共享固定的 50ms 单调时钟预算:自然退出、TERM、KILL;中断不续期。
|
|
68
|
-
# 仍无法同步回收或系统调用失败时交给 detach 的后台 wait,不伪造退出状态。
|
|
69
|
-
def reap_stty(resources)
|
|
70
|
-
return unless resources.owner == Process.pid
|
|
71
|
-
|
|
72
|
-
[nil, "TERM", "KILL"].each do |signal|
|
|
73
|
-
deadline = Expect.monotonic + 0.05
|
|
74
|
-
loop do
|
|
75
|
-
begin
|
|
76
|
-
resources.reap
|
|
77
|
-
return resources.status unless resources.pid && resources.owner == Process.pid
|
|
78
|
-
|
|
79
|
-
if signal
|
|
80
|
-
begin
|
|
81
|
-
Process.kill(signal, resources.pid)
|
|
82
|
-
rescue Errno::ESRCH
|
|
83
|
-
# 退出可发生在非阻塞 wait 与信号之间,仍需继续 wait。
|
|
84
|
-
nil
|
|
85
|
-
end
|
|
86
|
-
signal = nil
|
|
87
|
-
end
|
|
88
|
-
rescue Errno::EINTR
|
|
89
|
-
# 重试沿用本阶段期限,连续 EINTR 也必须交还控制权。
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
remaining = deadline - Expect.monotonic
|
|
93
|
-
break unless remaining.positive?
|
|
94
|
-
|
|
95
|
-
sleep [remaining, 0.005].min
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
ensure
|
|
99
|
-
Process.detach(resources.pid) if resources.pid && resources.owner == Process.pid
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|