expect-pty 0.7.3 → 0.7.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c57608249970451325b18ded1deb6ecea96a110e3640e198aa947a8dd9a15b98
4
- data.tar.gz: b14a1b19bff3673fa64d89ee6fc052cdb11d8fd7f64be00a74e4d47688b72c28
3
+ metadata.gz: 2b48d4fb81d2c6f7035133387df5641fb420b5c108e7257c2348f4ad119edecf
4
+ data.tar.gz: c6dcfa0a5edc30db418739decdaa262a480e905a234ddf179c67c609edac56a4
5
5
  SHA512:
6
- metadata.gz: d242048c45c5143d7a651bb64ef82c352aee96474c764ce3c168038274dfac2b6aec602548f5f9de497a23c8f90c4e9c46efab58c3e6e13add9cd4351b61f99d
7
- data.tar.gz: e2a86987567a5a0e254d76adf89d744af34f521c60172a4e8a32c373f6a31f020f777a1e983bcb078bd92048533371b0a94bc98564568aa092b3bde4425fadea
6
+ metadata.gz: 3a310e67d4303ebc2d9a2db7231722ab59b52a46bca2cf1aa32a1abd0de63a956867c17f4013df5029d21e4df509c8c6980fcc4193484f1e485b84e0933dff64
7
+ data.tar.gz: 19a972f1e1c19a64ba9b18f2148d0436c2f0a4211261f4a3396447b5863d8e6ac9e8f30322cb28e92ef45a5e8801dc519593c6a82044dacb42fe33b0ed17b1e0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.7.4 - 2026-10-06
6
+
7
+ - **连续匹配**:修复回调中嵌套读取相同内容的新消息后漏触发匹配、最终超时的问题,保留零宽匹配和同值赋回的防空转保护。
8
+ - **大缓冲转接**:降低已积累大量输出时的转接耗时与 GC 压力,保持完整交付、结果快照和失败续发行为。
9
+ - **流式脱敏**:减少长短秘密混合、细粒度分片时的重复扫描和临时对象,保留规则更新、跨块重叠及已有掩码。
10
+
5
11
  ## 0.7.3 - 2026-10-06
6
12
 
7
13
  - **转义连续性**:匹配消费、显式缓冲替换/清空及实际窗口裁剪后清除旧正则历史,避免跨过已消费输入误拼转义并吞字节;内部交接及非消费匹配保留连续历史。
data/README.md CHANGED
@@ -17,7 +17,7 @@ RubyGems/Bundler 解析。推荐入口 **`require "expect/pty"`**;本项目提
17
17
  项目和仓库名为 `expect-ruby`,Gem 名为 `expect-pty`。在应用的 Gemfile 中添加以下内容,然后运行 `bundle install`:
18
18
 
19
19
  ```ruby
20
- gem "expect-pty", "~> 0.7.3", require: "expect/pty"
20
+ gem "expect-pty", "~> 0.7.4", require: "expect/pty"
21
21
  ```
22
22
 
23
23
  也可直接执行 `gem install expect-pty`。需要跟随开发分支时,可从 GitHub 安装:
@@ -30,8 +30,8 @@ gem "expect-pty", git: "https://github.com/gatework/expect-ruby.git", branch: "m
30
30
 
31
31
  ```sh
32
32
  mkdir -p tmp
33
- gem build expect-pty.gemspec --output tmp/expect-pty-0.7.3.gem
34
- gem install ./tmp/expect-pty-0.7.3.gem
33
+ gem build expect-pty.gemspec --output tmp/expect-pty-0.7.4.gem
34
+ gem install ./tmp/expect-pty-0.7.4.gem
35
35
  ```
36
36
 
37
37
  ```ruby
data/docs/API.md CHANGED
@@ -49,6 +49,7 @@ result => { number:, captures: }
49
49
  `reset_timeout: false` 保持原相对期限。全部 EOF 后直接返回 EOF;已处理的来源不再参与超时。
50
50
 
51
51
  `consume: false` 仅在本轮保留完整匹配缓冲;继续但不改变缓冲的模式暂停到输入变化,避免反复触发。
52
+ 回调中的嵌套等待实际读入新字节时,即使缓冲内容与之前相同也会恢复匹配;仅同值赋回缓冲不算新输入。
52
53
  `reset_timeout_on_read: true` 使每次读取重置本轮相对期限。`deadline` 是单调时钟绝对秒数,不被输入或回调延长。
53
54
  IO 期限不打断用户代码或单次正则;不可信正则应使用 Ruby `Regexp` 自身的 timeout。
54
55
 
@@ -111,7 +111,9 @@ module Expect
111
111
  trim_history(history, buffer.byteslice(0, count), limit: session.buffer_limit || REGEXP_ESCAPE_HISTORY_LIMIT,
112
112
  utf8: utf8_regexp)
113
113
  end
114
- buffer.slice!(0, count)
114
+ # 排队前缀与尾部可共享原字节;替换窗口而非删除前缀,避免逐块复制整个余量。
115
+ # 保留缓冲对象身份,嵌套 Matcher 仍把尾部交回 Relay 借用的同一个对象。
116
+ buffer.replace(buffer.byteslice(count..)) if count.positive?
115
117
  return count.positive? ? :queued : :ready
116
118
  end
117
119
  end
@@ -71,7 +71,7 @@ module Expect
71
71
 
72
72
  buffer = buffers ? (buffers[session] ||= session.scan_buffer) : session.scan_buffer
73
73
  stalled = @stalled_matches[session]
74
- if stalled && stalled[:buffer] != buffer
74
+ if stalled && (stalled[:received_bytes] != session.received_bytes || stalled[:buffer] != buffer)
75
75
  @stalled_matches.delete(session)
76
76
  stalled = nil
77
77
  end
@@ -124,13 +124,14 @@ module Expect
124
124
  # 先记录并消费匹配,再执行回调;回调可选择结束、重置期限或保留期限继续。
125
125
  def handle_match(session, pattern, position)
126
126
  previous_buffer = session.buffer
127
+ received_bytes = session.received_bytes
127
128
  result = session.record_match(pattern, position, consume: @consume)
128
129
  action = pattern.call(session)
129
130
  return result unless continuing?(action)
130
131
 
131
- # 回调未改变缓冲时暂停当前模式,等待缓冲变化后再匹配,避免原地空转。
132
- if session.buffer == previous_buffer
133
- stalled = (@stalled_matches[session] ||= { buffer: previous_buffer, patterns: [] })
132
+ # 同值赋回仍须防止空转;嵌套读取到的新字节即使内容相同,也能恢复匹配。
133
+ if session.received_bytes == received_bytes && session.buffer == previous_buffer
134
+ stalled = (@stalled_matches[session] ||= { buffer: previous_buffer, received_bytes:, patterns: [] })
134
135
  stalled[:patterns] << pattern
135
136
  end
136
137
  @deadline = next_deadline if CONTINUE.equal?(action)
@@ -32,6 +32,7 @@ module Expect
32
32
 
33
33
  @patterns = patterns.map { |pattern| pattern.b.freeze }.uniq.freeze
34
34
  @lookbehind = [(@patterns.map(&:bytesize).max || 0) - 1, 0].max
35
+ @scanned_bytes = 0
35
36
  end
36
37
 
37
38
  # 追加一个原始字节块,返回已经可以确定的安全前缀;新秘密可能跨越此前保留的尾部。
@@ -94,14 +95,18 @@ module Expect
94
95
  end
95
96
  end
96
97
 
97
- # 仍逐字节推进重叠命中,但同一模式的相交/相邻区间只写一次掩码。
98
- # 只向旧掩码取并集,不能清除其他模式或旧规则已经隐藏的 pending 字节。
98
+ # 每个模式只扫描可能跨越新增字节的起点;规则更新后从头重扫保留窗口。
99
+ # 重叠命中取并集,同一模式的相交/相邻区间只写一次,不清除旧掩码。
99
100
  def mark_secrets
101
+ size = @pending.bytesize
102
+ return if @scanned_bytes == size
103
+
100
104
  @patterns.each do |pattern|
101
- starting = @pending.index(pattern)
105
+ length = pattern.bytesize
106
+ offset = @scanned_bytes >= length ? @scanned_bytes - length + 1 : 0
107
+ starting = @pending.index(pattern, offset)
102
108
  next unless starting
103
109
 
104
- length = pattern.bytesize
105
110
  ending = starting + length
106
111
  offset = starting
107
112
  while (offset = @pending.index(pattern, offset + 1))
@@ -113,12 +118,15 @@ module Expect
113
118
  end
114
119
  @hidden[starting, ending - starting] = "\1" * (ending - starting)
115
120
  end
121
+ @scanned_bytes = size
116
122
  end
117
123
 
118
124
  # 按连续区间输出,避免逐字节构造字符串;只保存尚可能与下一块组成秘密的后缀。
119
125
  # masking 跨 append 保留,使被分成多个块的同一隐藏区间只输出一次替换标记。
120
126
  def release(length)
121
127
  output = "".b
128
+ return output if length.zero?
129
+
122
130
  cursor = 0
123
131
  while cursor < length
124
132
  hidden = @hidden.getbyte(cursor) == 1
@@ -135,9 +143,11 @@ module Expect
135
143
  end
136
144
  cursor = ending
137
145
  end
138
- @pending = @pending.byteslice(length..)
139
- @hidden = @hidden.byteslice(length..)
140
- @suppressed = @suppressed.byteslice(length..) if @suppressed
146
+ remaining = @pending.bytesize - length
147
+ @pending = @pending.byteslice(length, remaining)
148
+ @hidden = @hidden.byteslice(length, remaining)
149
+ @suppressed = @suppressed.byteslice(length, remaining) if @suppressed
150
+ @scanned_bytes = remaining
141
151
  output
142
152
  end
143
153
 
@@ -145,6 +145,10 @@ module Expect
145
145
  # @api private
146
146
  def scan_buffer = @buffer
147
147
 
148
+ # 成功读取的累计字节数;消费、替换和移交缓冲不算新的接收进展。
149
+ # @api private
150
+ attr_reader :received_bytes
151
+
148
152
  # 复制并替换原始字节缓冲,应用当前上限;调用方后续修改原字符串不会影响会话。
149
153
  def buffer=(value)
150
154
  raise ArgumentError, "buffer must be a String" unless value.is_a?(String)
@@ -359,6 +363,7 @@ module Expect
359
363
 
360
364
  data = data.b
361
365
  buffer << data
366
+ @received_bytes += data.bytesize
362
367
  trim_buffer if trim
363
368
  # 本块完成全部交付前不能递归读取,否则后块会抢先进入其他日志及输出目标。
364
369
  # 回调仍可匹配已有缓冲或驱动其他会话;拒绝嵌套读取不能释放外层的保护。
@@ -388,6 +393,7 @@ module Expect
388
393
  @tty_name = slave.path if slave
389
394
  @buffer = "".b
390
395
  @buffer_generation = 0
396
+ @received_bytes = 0
391
397
  @buffer_discarded_bytes = 0
392
398
  @outputs = []
393
399
  @sequences = {}
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Expect
4
4
  # Gem 与库共用的版本号;独立文件使 gemspec 无需加载完整会话实现。
5
- VERSION = "0.7.3"
5
+ VERSION = "0.7.4"
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expect-pty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - expect-pty contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-10-06 00:00:00.000000000 Z
10
+ date: 2026-10-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: io-console