expect-pty 0.8.1 → 0.8.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 +6 -0
- data/docs/API.md +3 -1
- data/lib/expect/matcher.rb +7 -6
- data/lib/expect/redactor.rb +10 -2
- data/lib/expect/relay.rb +2 -0
- data/lib/expect/session.rb +11 -1
- data/lib/expect/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbcc16796d1be757cc45df5ed803aa8d3518601923eaa3765d512cd3000baaf7
|
|
4
|
+
data.tar.gz: 47943a69f86cfc7ba7564eea3f2ec7e0f71a30910e398535fbe734cff8cba54f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9d0688944e25cd879072059ecdf8556676bc32c0b83634dbac773f002dad67485e9dad95034a52b306dbf0e1a3e38eff1e67efb0fcd382cd45c8b0c06bf79bd
|
|
7
|
+
data.tar.gz: 62aed043158e01a8023111d441fb9c484d697763157e18db97c6c01e9bbb7c55af7fb55ea3e311a4bcae2e191209a1401a42b139523d0e7d4c0873be78c2800c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.8.2 - 2026-10-11
|
|
6
|
+
|
|
7
|
+
- 修复对象转换、日志回调或背压读取关闭会话后仍继续写入借用 IO 的问题,空写入也会检查关闭状态。
|
|
8
|
+
- 修复转接接收回调耗尽总期限后仍读取后续来源的问题,保留零超时的首次轮询行为。
|
|
9
|
+
- 加快多来源 EOF 连续派发,并减少诊断重新启用后持续脱敏的临时分配,保留来源顺序与秘密过滤。
|
|
10
|
+
|
|
5
11
|
## 0.8.1 - 2026-10-11
|
|
6
12
|
|
|
7
13
|
- 修复自动应答已在缓冲开头确定获胜模式,却被后续无关正则的编码错误阻断的问题,保留未消费尾部。
|
data/docs/API.md
CHANGED
|
@@ -126,6 +126,7 @@ end
|
|
|
126
126
|
`write(*objects)` 按 to_s 写字节并返回字节数,`<<` 返回 Session,`puts` 遵循 Ruby IO 换行与数组规则并返回 nil。
|
|
127
127
|
`send_slow(*objects, delay:)` 按字符延迟发送并收取回复。背压超时抛出 `Expect::WriteTimeout`,`bytes_written` 仅标识本次调用已确认进度,
|
|
128
128
|
嵌套写入异常放在 cause。成功短写不受 write_timeout 的总耗时限制。
|
|
129
|
+
对象转换、发送诊断或背压读取的回调若关闭会话,后续写入抛出 IOError;借用 IO 仍归调用方所有,空写入同样检查关闭状态。
|
|
129
130
|
`send_slow` 的进度累计本次调用中此前完整字符与失败字符实际接受的字节;转换对象和记录回复的内层写入进度不计入。
|
|
130
131
|
首字符 write 的异常无需修正进度时保留原对象;需要累计或排除内层进度时包装并保留 cause。
|
|
131
132
|
`to_io`、`writer`、`slave` 暴露句柄;`pid`、`command`、`tty_name`、`fileno`、`tty?`、`alive?`、`exit_code` 查询会话属性。
|
|
@@ -154,7 +155,8 @@ DEBUG 关闭期间仍推进分片脱敏,但这些字节不会在重新启用
|
|
|
154
155
|
`pending_output?` 表示尚未交付数据;再次转接同源会话继续发送,不重放成功前缀;更换 outputs 不改变已排队字节的目标。
|
|
155
156
|
同源递归转接抛出 ReentrancyError;转义回调可嵌套匹配,所有来源状态按对象身份隔离。
|
|
156
157
|
接收字节交付期间,logger/transcript/outputs 的回调不能递归读取同一会话的新输入,否则抛出 ReentrancyError;
|
|
157
|
-
|
|
158
|
+
匹配已有缓冲及读取独立会话仍可用。正数转接总期限会在扫描、继续回调返回后及读取每个就绪来源前检查;接收回调耗尽预算后不再读取后续来源。
|
|
159
|
+
已读分片的交付仍完整完成,零超时仍处理缓冲并首次轮询。
|
|
158
160
|
|
|
159
161
|
`interact(input: $stdin, escape: nil, output: nil, timeout: nil, raw: true)` 临时建立双向转接,退出后恢复
|
|
160
162
|
outputs、转义和本地终端模式。
|
data/lib/expect/matcher.rb
CHANGED
|
@@ -18,7 +18,8 @@ module Expect
|
|
|
18
18
|
# 相对期限可因接收或 continue 重算,总期限始终固定;两者共用单调时钟。
|
|
19
19
|
@hard_deadline = deadline
|
|
20
20
|
@deadline = next_deadline
|
|
21
|
-
|
|
21
|
+
# 按声明顺序维护仍参与本轮等待的来源;EOF 删除后不再从完整列表重复跳过已处理项。
|
|
22
|
+
@active_sessions = @sessions.each_with_object({}.compare_by_identity) { |session, active| active[session] = true }
|
|
22
23
|
@stalled_matches = {}.compare_by_identity
|
|
23
24
|
@polled = false
|
|
24
25
|
@expired_eof_continuation = false
|
|
@@ -84,7 +85,7 @@ module Expect
|
|
|
84
85
|
earliest = nil
|
|
85
86
|
groups.each do |sessions, patterns|
|
|
86
87
|
sessions.each do |session|
|
|
87
|
-
next
|
|
88
|
+
next unless @active_sessions.key?(session)
|
|
88
89
|
|
|
89
90
|
buffer = buffers ? (buffers[session] ||= session.scan_buffer) : session.scan_buffer
|
|
90
91
|
stalled = @stalled_matches[session]
|
|
@@ -169,19 +170,19 @@ module Expect
|
|
|
169
170
|
|
|
170
171
|
# 找出尚未派发 EOF 事件的会话,保证每个源只处理一次结束事件。
|
|
171
172
|
def unhandled_eof
|
|
172
|
-
@
|
|
173
|
+
@active_sessions.each_key.find(&:eof?)
|
|
173
174
|
end
|
|
174
175
|
|
|
175
176
|
# 将剩余字节交给 EOF 回调;需要继续时等待其他源,全部结束则立即返回。
|
|
176
177
|
def handle_eof(session)
|
|
177
178
|
result = session.record_eof
|
|
178
|
-
@
|
|
179
|
+
@active_sessions.delete(session)
|
|
179
180
|
actions = @patterns.eof_patterns_for(session).map { |pattern| pattern.call(session) }
|
|
180
181
|
return result unless actions.any? { |action| continuing?(action) }
|
|
181
182
|
|
|
182
183
|
reset_timeout = actions.any? { |action| CONTINUE.equal?(action) }
|
|
183
184
|
@deadline = next_deadline if reset_timeout
|
|
184
|
-
return result if @
|
|
185
|
+
return result if @active_sessions.empty?
|
|
185
186
|
|
|
186
187
|
# 期限已过时不再扫描文本,但先派发已知 EOF;最后一个源结束不能被误报为超时。
|
|
187
188
|
@expired_eof_continuation = !reset_timeout && expired?
|
|
@@ -234,7 +235,7 @@ module Expect
|
|
|
234
235
|
end
|
|
235
236
|
|
|
236
237
|
# 返回本次仍需监听的会话,供读取选择和超时回调使用。
|
|
237
|
-
def active_sessions = @
|
|
238
|
+
def active_sessions = @active_sessions.keys
|
|
238
239
|
|
|
239
240
|
# 只有约定的继续符号会驱动下一轮,普通回调返回值不会改变等待流程。
|
|
240
241
|
def continuing?(action) = CONTINUE.equal?(action) || CONTINUE_WITHOUT_RESET.equal?(action)
|
data/lib/expect/redactor.rb
CHANGED
|
@@ -59,6 +59,7 @@ module Expect
|
|
|
59
59
|
output = release(@pending.bytesize)
|
|
60
60
|
# 已结束的流没有尚待抑制的字节,后续可见分片恢复普通路径;再次 suppress 时重新建立字节图。
|
|
61
61
|
@suppressed = nil
|
|
62
|
+
@suppressed_until = nil
|
|
62
63
|
@masking = false
|
|
63
64
|
output
|
|
64
65
|
end
|
|
@@ -73,7 +74,10 @@ module Expect
|
|
|
73
74
|
def append_chunk(data, visible:)
|
|
74
75
|
raise ArgumentError, "data must be a String" unless data.is_a?(String)
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
unless visible || data.empty?
|
|
78
|
+
@suppressed ||= "\0" * @pending.bytesize
|
|
79
|
+
@suppressed_until = @pending.bytesize + data.bytesize
|
|
80
|
+
end
|
|
77
81
|
@suppressed << ((visible ? "\0" : "\1") * data.bytesize) if @suppressed
|
|
78
82
|
@pending << data.b
|
|
79
83
|
@hidden << ("\0" * data.bytesize)
|
|
@@ -144,7 +148,11 @@ module Expect
|
|
|
144
148
|
remaining = @pending.bytesize - length
|
|
145
149
|
@pending = @pending.byteslice(length, remaining)
|
|
146
150
|
@hidden = @hidden.byteslice(length, remaining)
|
|
147
|
-
|
|
151
|
+
if @suppressed
|
|
152
|
+
# 只追踪最后一个抑制字节的排空边界,避免每轮扫描字节图;跨块秘密掩码不受影响。
|
|
153
|
+
@suppressed_until -= length
|
|
154
|
+
@suppressed = @suppressed_until.positive? ? @suppressed.byteslice(length, remaining) : nil
|
|
155
|
+
end
|
|
148
156
|
@scanned_bytes = remaining
|
|
149
157
|
output
|
|
150
158
|
end
|
data/lib/expect/relay.rb
CHANGED
|
@@ -122,6 +122,8 @@ module Expect
|
|
|
122
122
|
end
|
|
123
123
|
readers.each do |session|
|
|
124
124
|
next unless ready ? ready.key?(session.to_io) : readable.first.equal?(session.to_io)
|
|
125
|
+
# 前一个接收回调可耗尽总预算;本块交付完成后暂停新的读取,零超时仍允许首次批量轮询。
|
|
126
|
+
break if dispatch_expired?
|
|
125
127
|
|
|
126
128
|
begin
|
|
127
129
|
if @buffers.key?(session)
|
data/lib/expect/session.rb
CHANGED
|
@@ -178,16 +178,19 @@ module Expect
|
|
|
178
178
|
|
|
179
179
|
# 按 Ruby to_s 规则原样写入所有字节,返回字节数;背压等待受 write_timeout 限制。
|
|
180
180
|
def write(*objects)
|
|
181
|
-
|
|
181
|
+
check_writable!
|
|
182
182
|
|
|
183
183
|
begin
|
|
184
184
|
# 单对象不需要中间数组与 join 副本;to_s、诊断和部分写入仍经过同一条路径。
|
|
185
185
|
data = objects.size == 1 ? objects.first.to_s.b : objects.map { |object| object.to_s.b }.join
|
|
186
|
+
check_writable!
|
|
186
187
|
trace_data(:sending, data)
|
|
187
188
|
rescue WriteTimeout
|
|
188
189
|
# 转换或诊断中的嵌套写入不属于当前命令;此时尚未向 writer 发送任何字节。
|
|
189
190
|
raise WriteTimeout.new("write interrupted before sending data", bytes_written: 0)
|
|
190
191
|
end
|
|
192
|
+
# 转换与诊断均可关闭借用会话;即使底层 IO 仍打开,也不能把关闭后的发送报告为成功。
|
|
193
|
+
check_writable!
|
|
191
194
|
deadline = write_timeout && (Expect.monotonic + write_timeout)
|
|
192
195
|
offset = 0
|
|
193
196
|
while offset < data.bytesize
|
|
@@ -467,6 +470,8 @@ module Expect
|
|
|
467
470
|
|
|
468
471
|
# EINTR 未确认交付时不移动游标;其他写入计数必须落在当前块范围内。
|
|
469
472
|
def write_chunk(data, offset, deadline)
|
|
473
|
+
# 背压读取会执行接收回调;每次重试重新检查,关闭会话不等于关闭调用方拥有的 writer。
|
|
474
|
+
check_writable!
|
|
470
475
|
chunk = data.byteslice(offset, READ_SIZE)
|
|
471
476
|
count = writer.write_nonblock(chunk, exception: false)
|
|
472
477
|
return count if count == :wait_writable
|
|
@@ -481,6 +486,11 @@ module Expect
|
|
|
481
486
|
nil
|
|
482
487
|
end
|
|
483
488
|
|
|
489
|
+
# 同一状态检查覆盖入口、用户转换/诊断之后和背压恢复;只检查,不取得借用 IO 所有权。
|
|
490
|
+
def check_writable!
|
|
491
|
+
raise IOError, "closed Expect session" if closed? || writer.closed?
|
|
492
|
+
end
|
|
493
|
+
|
|
484
494
|
# 背压等待同时排空读端,期限不因 EINTR 重算,异常始终报告外层写入进度。
|
|
485
495
|
def wait_writable(offset, deadline)
|
|
486
496
|
remaining = deadline && [deadline - Expect.monotonic, 0].max
|
data/lib/expect/version.rb
CHANGED