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.
data/lib/expect.rb CHANGED
@@ -3,9 +3,7 @@
3
3
  require "pty"
4
4
  require "io/console"
5
5
  require "stringio"
6
- require "forwardable"
7
6
  require_relative "expect/version"
8
- require_relative "expect/configuration"
9
7
  require_relative "expect/result"
10
8
  require_relative "expect/session_resources"
11
9
  require_relative "expect/pattern"
@@ -14,30 +12,28 @@ require_relative "expect/matcher"
14
12
  require_relative "expect/cleanup"
15
13
  require_relative "expect/session"
16
14
 
17
- # 自动化交互会话:可以拥有一个 PTY 子进程,也可以适配已有可 select 的 IO。
18
- # 缓冲和匹配统一保留原始字节,配置、匹配结果与资源生命周期分别管理。
19
- class Expect
20
- # 回调控制符:分别表示重置期限后继续,或保留原期限继续。
15
+ # PTY 会话的创建、匹配与转接入口;可变状态只属于返回的 Session。
16
+ module Expect
17
+ # 重置相对期限后继续等待。
21
18
  CONTINUE = :continue
22
- # 继续等待但不重置相对期限。
19
+ # 保留原相对期限继续等待。
23
20
  CONTINUE_WITHOUT_RESET = :continue_without_reset
24
21
  # 单轮非阻塞读写的最大字节数。
25
22
  # @api private
26
23
  READ_SIZE = 16_384
27
- CONFIGURATION_MUTEX = Mutex.new
28
- private_constant :CONFIGURATION_MUTEX
29
24
 
30
25
  # PTY 创建后命令启动失败。
31
26
  class SpawnError < StandardError; end
27
+
32
28
  # 同一个来源不能同时交给两个 Relay。
33
29
  class ReentrancyError < StandardError; end
34
30
 
35
- # 已被底层接受的字节不可撤回;调用方可据此只处理尚未写出的后缀。
31
+ # 本次写入已经确认交付的进度;其他嵌套写入的异常保留在 cause。
36
32
  class WriteTimeout < IOError
33
+ # 本次 write 已被底层接受的字节数。
37
34
  attr_reader :bytes_written
38
35
 
39
- # 记录本次已经确认交付的字节数,不推测异常写入是否产生副作用。
40
- # @param bytes_written [Integer] 已交付字节数。
36
+ # 构造包含已交付字节数的背压超时。
41
37
  def initialize(message = "write timed out", bytes_written: 0)
42
38
  @bytes_written = bytes_written
43
39
  super(message)
@@ -45,103 +41,63 @@ class Expect
45
41
  end
46
42
 
47
43
  class << self
48
- # 读取冻结的默认配置;子类未单独配置时继承父类快照。
49
- # @return [Expect::Configuration] 冻结的类级默认快照。
50
- def configuration
51
- return @configuration if defined?(@configuration)
52
- return superclass.configuration unless self == Expect
53
-
54
- CONFIGURATION_MUTEX.synchronize { @configuration ||= Configuration.new.freeze }
55
- end
56
-
57
- # 基于旧快照构造可修改副本,全部赋值与配置块成功后才发布,异常时保留原配置。
58
- # 配置关键字覆盖默认值,未知键抛出 ArgumentError。
59
- # @yieldparam configuration [Expect::Configuration] 发布前可修改的副本。
60
- # @return [Expect::Configuration] 新发布的冻结快照。
61
- def configure(**)
62
- raise ThreadError, "nested configure is not supported" if CONFIGURATION_MUTEX.owned?
63
-
64
- # 初始化默认快照后,将整个读改写过程串行化,避免并发配置丢失更新。
65
- configuration
66
- CONFIGURATION_MUTEX.synchronize do
67
- updated = Configuration.new(**configuration.to_h, **)
68
- yield updated if block_given?
69
- @configuration = updated.freeze
70
- end
71
- end
72
-
73
- # 创建并启动会话;有块时返回块结果并确保关闭,无块时由调用方负责生命周期。
74
- # @param command [Array<String>] 命令及参数;单字符串遵循 Ruby 自动 shell 语义,多参数按 argv 执行。
75
- # @param env [Hash<String, String, nil>] 子进程环境覆盖。
76
- # @param chdir [String, nil] 子进程工作目录。
77
- # @yieldparam connection [Expect] 自动关闭的用户会话。
78
- # @return [Expect, Object] 无块返回会话,有块返回块结果。
79
- def spawn(*command, env: {}, chdir: nil, **)
80
- connection = nil
44
+ # 创建并启动 Session;有块时返回块结果,并按 graceful 策略关闭所属资源。
45
+ # raw 只控制本次子进程 PTY;日志与输出对象始终借用。
46
+ def spawn(*command, env: {}, chdir: nil, raw: false, graceful: false,
47
+ timeout: nil, write_timeout: nil, buffer_limit: nil, logger: nil, transcript: nil, outputs: [])
48
+ session = nil
81
49
  spawned = false
82
- cleanup = -> { connection&.close(graceful: spawned && connection.graceful_close?) if block_given? || !spawned }
50
+ cleanup = -> { session&.close(graceful: spawned && graceful) if block_given? || !spawned }
83
51
  Cleanup.always(cleanup) do
84
- connection = new(**)
85
- connection.spawn(*command, env:, chdir:)
52
+ session = Session.new(timeout:, write_timeout:, buffer_limit:, logger:, transcript:, outputs:)
53
+ session.spawn(*command, env:, chdir:, raw:)
86
54
  spawned = true
87
- block_given? ? yield(connection) : connection
55
+ block_given? ? yield(session) : session
88
56
  end
89
57
  end
90
58
 
91
- # 适配已有 IO;own: true 接管关闭责任,初始化失败也释放所属端点。
92
- # @param io [IO] 可 select 的真实读端。
93
- # @param writer [IO] 写端,默认与读端相同。
94
- # @param own [Boolean] 是否取得 IO 关闭责任。
95
- # @yieldparam connection [Expect] 自动关闭的用户会话。
96
- # @return [Expect, Object] 无块返回会话,有块返回块结果。
97
- def open(io, writer: io, own: false, **)
98
- connection = nil
59
+ # 适配真实 IO;own 只决定读写端点的关闭责任,日志与输出目标仍由调用者管理。
60
+ # 有块时返回块结果;初始化失败也清理已取得的所属端点。
61
+ def open(io, writer: io, own: false, graceful: false,
62
+ timeout: nil, write_timeout: nil, buffer_limit: nil, logger: nil, transcript: nil, outputs: [])
63
+ session = nil
99
64
  initialized = false
100
65
  cleanup = lambda do
101
- if connection && (block_given? || !initialized)
102
- connection.__send__(:cleanup_session, io, writer:, own:,
103
- graceful: initialized && connection.graceful_close?)
66
+ if session && (block_given? || !initialized)
67
+ session.cleanup_session(io, writer:, own:, graceful: initialized && graceful)
68
+ elsif !session && own
69
+ SessionResources.close_handles(io, writer)
104
70
  end
105
71
  end
106
72
  Cleanup.always(cleanup) do
107
- connection = allocate
108
- connection.__send__(:initialize_session, io, writer:, own:, **)
73
+ session = Session.allocate
74
+ session.__send__(:initialize_io, io, writer:, own:, timeout:, write_timeout:, buffer_limit:,
75
+ logger:, transcript:, outputs:)
109
76
  initialized = true
110
- block_given? ? yield(connection) : connection
77
+ block_given? ? yield(session) : session
111
78
  end
112
79
  end
113
80
 
114
- # 按 listeners 建立转发图,返回引发停止的用户会话或 nil。
115
- # @param connections [Array<Expect>] 转接来源,不能为空。
116
- # @param timeout [Numeric, nil] 总等待秒数,nil 无限。
117
- # @return [Expect, nil] 停止来源;总期限到达返回 nil。
118
- def interconnect(*connections, timeout: nil)
119
- raise ArgumentError, "interconnect requires Expect sessions" unless connections.any? && connections.all?(Expect)
81
+ # 按 outputs 建立转发图,返回引发停止的 Session;总期限到达返回 nil。
82
+ def interconnect(*sessions, timeout: nil)
83
+ raise ArgumentError, "interconnect requires Session objects" unless sessions.any? && sessions.all?(Session)
120
84
 
121
- Relay.new(connections.map { |connection| Session.for(connection) }, timeout).run&.connection
85
+ Relay.new(sessions, timeout).run
122
86
  end
123
87
 
124
- # 多会话等待的完整结果入口;from: 提供默认来源,块内可分别指定每个模式的来源。
125
- # @param patterns [Array<String, Regexp, Symbol>] 文本模式、:eof 或 :timeout。
126
- # @param timeout [Numeric, nil] 相对秒数,nil 无限,0 非阻塞轮询。
127
- # @param deadline [Numeric, nil] 单调时钟绝对期限,不被回调延长。
128
- # @yieldparam patterns [Expect::PatternList] 有参数块接收构建器;无参数块以 DSL 执行。
129
- # @return [Expect::Result] 本次等待的不可变快照。
130
- def expect(*patterns, from: [], timeout: configuration.timeout, deadline: nil, &)
131
- run_expect(from, patterns, timeout, deadline:, &)
88
+ # 共同等待指定来源,返回不可变 Result。consume 控制本轮是否消费匹配文本。
89
+ # reset_timeout_on_read 仅重置相对期限,deadline 始终是不可延长的总期限。
90
+ def expect(*patterns, from: [], timeout: nil, deadline: nil, consume: true, reset_timeout_on_read: false, &)
91
+ run_expect(from, patterns, timeout, deadline:, consume:, reset_timeout_on_read:, &)
132
92
  end
133
93
 
134
- # 返回继续等待的控制符,reset_timeout 决定是否重新计算匹配期限。
135
- # @return [Symbol] 重置期限或保持期限的继续控制符。
94
+ # 返回回调继续控制符,reset_timeout 为 false 时保留原相对期限。
136
95
  def continue(reset_timeout: true) = reset_timeout ? CONTINUE : CONTINUE_WITHOUT_RESET
137
96
 
138
- # 读取不受系统时间调整影响的单调时钟,所有相对超时共用此计时基准。
139
- # @return [Float] 当前单调时钟秒数。
97
+ # 当前单调时钟秒数;供跨多次等待共享 deadline。
140
98
  def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
141
99
 
142
- # 将秒数转换为有限的非负数,nil 表示无限;供配置和单次操作共用校验。
143
- # @param value [Numeric, String, nil] 可转换为有限非负秒数的值。
144
- # @return [Float, nil] 校验后的秒数。
100
+ # 转换有限非负秒数,nil 表示无相对期限。
145
101
  def duration(value)
146
102
  return nil if value.nil?
147
103
 
@@ -151,12 +107,10 @@ class Expect
151
107
  number
152
108
  end
153
109
 
154
- # 等待并返回可读会话,不消费输入;去重并忽略已关闭会话,默认非阻塞。
155
- # @param sessions [Array<Expect>] 待读取来源。
156
- # @return [Array<Expect>] 就绪来源,保持声明顺序。
110
+ # 返回可读 Session,不消费输入;去重、排除已关闭来源,并保留声明顺序。
157
111
  def readable_sessions(*sessions, timeout: 0)
158
112
  timeout = duration(timeout)
159
- raise ArgumentError, "readable_sessions requires Expect sessions" unless sessions.all?(Expect)
113
+ raise ArgumentError, "readable_sessions requires Session objects" unless sessions.all?(Session)
160
114
 
161
115
  active = sessions.uniq(&:object_id).reject(&:closed?)
162
116
  return [] if active.empty?
@@ -182,14 +136,14 @@ class Expect
182
136
 
183
137
  private
184
138
 
185
- # 就绪描述符按对象身份归属会话,不能由 IO 子类的值相等规则替代。
139
+ # 按对象身份归属就绪描述符,不能用 IO 的值相等规则合并来源。
186
140
  def select_ready_sessions(sessions, readable)
187
141
  by_io = readable.each_with_object({}.compare_by_identity) { |io, index| index[io] = true }
188
142
  sessions.select { |session| by_io.key?(session.to_io) }
189
143
  end
190
144
 
191
- # 先完成模式声明再启动引擎;无参数块支持简洁 DSL,有参数块保留调用方 self。
192
- def run_expect(sessions, patterns, timeout, deadline: nil, &block)
145
+ # 声明完成后固定模式;有参数块保留调用方 self,无参数块使用模式 DSL。
146
+ def run_expect(sessions, patterns, timeout, deadline:, consume:, reset_timeout_on_read:, &block)
193
147
  timeout = duration(timeout)
194
148
  deadline = Float(deadline) unless deadline.nil?
195
149
  raise ArgumentError, "deadline must be finite" if deadline && !deadline.finite?
@@ -199,284 +153,7 @@ class Expect
199
153
  if block
200
154
  block.parameters.empty? ? pattern_list.instance_exec(&block) : block.call(pattern_list)
201
155
  end
202
- Matcher.new(pattern_list, timeout, deadline:).run
203
- end
204
- end
205
-
206
- extend Forwardable
207
-
208
- # @!method <<(object)
209
- # 执行操作并返回当前用户会话,支持链式使用。
210
- # @return [Expect]
211
- # @!method after
212
- # 读取最近结果中的字节快照;尚无结果时为 nil。
213
- # @return [String, nil]
214
- # @!method alive?
215
- # 查询当前会话状态;输入结束、进程退出和关闭分别记录。
216
- # @return [Boolean]
217
- # @!method before
218
- # 读取最近结果中的字节快照;尚无结果时为 nil。
219
- # @return [String, nil]
220
- # @!method buffer
221
- # 取得接收字节;buffer 返回副本,clear_buffer 移交并清空现有内容。
222
- # @return [String]
223
- # @!method buffer=(value)
224
- # 校验并替换当前设置;缓冲和集合采用副本,IO 与回调只借用。
225
- # @return [void]
226
- # @!method buffer_discarded_bytes
227
- # 读取因匹配窗口上限裁剪而丢弃的累计字节数。
228
- # @return [Integer]
229
- # @!method buffer_limit
230
- # 读取会话独立配置;更改类级默认值不会追溯影响已建立会话。
231
- # @return [Integer, nil]
232
- # @!method buffer_limit=(value)
233
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
234
- # @return [void]
235
- # @!method captures
236
- # 读取最近捕获组;未参与的组为 nil,尚无结果时为空数组。
237
- # @return [Array<String, nil>]
238
- # @!method clear_buffer
239
- # 取得接收字节;buffer 返回副本,clear_buffer 移交并清空现有内容。
240
- # @return [String]
241
- # @!method close(graceful: graceful_close?)
242
- # 关闭所属资源并回收子进程;graceful 启用先收尾输出,硬关闭始终兜底。
243
- # @return [nil]
244
- # @!method closed?
245
- # 查询当前会话状态;输入结束、进程退出和关闭分别记录。
246
- # @return [Boolean]
247
- # @!method command
248
- # 读取已启动命令的冻结参数快照,未启动时为 nil。
249
- # @return [Array<String>, nil]
250
- # @!method debug_level
251
- # 读取会话独立配置;更改类级默认值不会追溯影响已建立会话。
252
- # @return [Integer]
253
- # @!method debug_level=(value)
254
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
255
- # @return [void]
256
- # @!method diagnostic_output
257
- # 读取借用的诊断目标;nil 使用 stderr。
258
- # @return [#info, #write, Proc, nil]
259
- # @!method diagnostic_output=(value)
260
- # 校验并替换当前设置;缓冲和集合采用副本,IO 与回调只借用。
261
- # @return [void]
262
- # @!method eof?
263
- # 查询当前会话状态;输入结束、进程退出和关闭分别记录。
264
- # @return [Boolean]
265
- # @!method error
266
- # 读取最近 EOF、超时或原始 IO 错误,匹配成功时为 nil。
267
- # @return [Symbol, Exception, nil]
268
- # @!method exit_code
269
- # 读取对应进程、IO 或匹配属性;尚无可用值时为 nil。
270
- # @return [Integer, nil]
271
- # @!method fileno
272
- # 读取对应进程、IO 或匹配属性;尚无可用值时为 nil。
273
- # @return [Integer, nil]
274
- # @!method graceful_close=(value)
275
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
276
- # @return [void]
277
- # @!method graceful_close?
278
- # 按 Ruby 真值规则查询此布尔配置。
279
- # @return [Boolean]
280
- # @!method hard_close(timeout: 0.2)
281
- # 等待或回收子进程,未取得真实状态时返回 nil;期限以秒计。
282
- # @return [Process::Status, nil]
283
- # @!method interact(input: $stdin, escape: nil, output: nil, timeout: nil)
284
- # 临时转接输入和输出,退出时恢复终端与监听设置;超时返回 nil。
285
- # @return [Expect, nil]
286
- # @!method last_result
287
- # 读取最近一次等待的不可变结果。
288
- # @return [Expect::Result, nil]
289
- # @!method listeners
290
- # 读取监听目标数组的副本;监听器只借用,不随会话关闭。
291
- # @return [Array<#write>]
292
- # @!method listeners=(value)
293
- # 校验并替换当前设置;缓冲和集合采用副本,IO 与回调只借用。
294
- # @return [void]
295
- # @!method log_listeners=(value)
296
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
297
- # @return [void]
298
- # @!method log_listeners?
299
- # 按 Ruby 真值规则查询此布尔配置。
300
- # @return [Boolean]
301
- # @!method log_output
302
- # 读取当前接收日志目标,不包含发送数据。
303
- # @return [#write, Proc, nil]
304
- # @!method log_output=(value)
305
- # 校验并替换当前设置;缓冲和集合采用副本,IO 与回调只借用。
306
- # @return [void]
307
- # @!method log_stdout=(value)
308
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
309
- # @return [void]
310
- # @!method log_stdout?
311
- # 按 Ruby 真值规则查询此布尔配置。
312
- # @return [Boolean]
313
- # @!method log_to(target = nil, mode: "a", &block)
314
- # 设置借用日志目标或打开所属日志文件;返回目标。
315
- # @return [#write, Proc]
316
- # @yieldparam bytes [String] 实际读取的字节。
317
- # @yieldreturn [Object] 回调返回值不影响匹配。
318
- # @!method match
319
- # 读取最近结果中的字节快照;尚无结果时为 nil。
320
- # @return [String, nil]
321
- # @!method match_number
322
- # 读取对应进程、IO 或匹配属性;尚无可用值时为 nil。
323
- # @return [Integer, nil]
324
- # @!method on_sequence(sequence, &block)
325
- # 执行操作并返回当前用户会话,支持链式使用。
326
- # @return [Expect]
327
- # @yield 转义匹配完成后运行,nil/false 停止,其余返回值继续。
328
- # @yieldreturn [Object] 是否继续转接。
329
- # @!method pending_output?
330
- # 查询当前会话状态;输入结束、进程退出和关闭分别记录。
331
- # @return [Boolean]
332
- # @!method pid
333
- # 读取对应进程、IO 或匹配属性;尚无可用值时为 nil。
334
- # @return [Integer, nil]
335
- # @!method preserve_buffer=(value)
336
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
337
- # @return [void]
338
- # @!method preserve_buffer?
339
- # 按 Ruby 真值规则查询此布尔配置。
340
- # @return [Boolean]
341
- # @!method process_status
342
- # 非阻塞回收直属子进程并读取真实状态;未知时保留 nil。
343
- # @return [Process::Status, nil]
344
- # @!method puts(*objects)
345
- # 按 Ruby puts 语义转换换行、nil 和数组后写入。
346
- # @return [nil]
347
- # @!method raw_pty=(value)
348
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
349
- # @return [void]
350
- # @!method raw_pty?
351
- # 按 Ruby 真值规则查询此布尔配置。
352
- # @return [Boolean]
353
- # @!method raw_terminal=(value)
354
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
355
- # @return [void]
356
- # @!method raw_terminal?
357
- # 按 Ruby 真值规则查询此布尔配置。
358
- # @return [Boolean]
359
- # @!method redact(*secrets)
360
- # 执行操作并返回当前用户会话,支持链式使用。
361
- # @return [Expect]
362
- # @!method reset_timeout_on_read=(value)
363
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
364
- # @return [void]
365
- # @!method reset_timeout_on_read?
366
- # 按 Ruby 真值规则查询此布尔配置。
367
- # @return [Boolean]
368
- # @!method send_slow(*objects, delay:)
369
- # 返回已写字节数;背压超时抛出 WriteTimeout 并保留 bytes_written。
370
- # @return [Integer]
371
- # @!method slave
372
- # 读取 PTY slave;适配已有 IO 时为 nil,启动后句柄已关闭。
373
- # @return [IO, nil]
374
- # @!method soft_close(timeout: 15, term_timeout: 1)
375
- # 等待或回收子进程,未取得真实状态时返回 nil;期限以秒计。
376
- # @return [Process::Status, nil]
377
- # @!method spawn(*command, env: {}, chdir: nil)
378
- # 执行操作并返回当前用户会话,支持链式使用。
379
- # @return [Expect]
380
- # @!method stty(*modes)
381
- # 查询或设置终端模式;非 TTY 返回空字符串,失败抛出 IO 错误。
382
- # @return [String]
383
- # @!method timeout
384
- # 读取会话独立配置;更改类级默认值不会追溯影响已建立会话。
385
- # @return [Numeric, nil]
386
- # @!method timeout=(value)
387
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
388
- # @return [void]
389
- # @!method to_io
390
- # 取得底层读端或写端,不复制描述符。
391
- # @return [IO]
392
- # @!method tty?
393
- # 查询当前会话状态;输入结束、进程退出和关闭分别记录。
394
- # @return [Boolean]
395
- # @!method tty_name
396
- # 读取 PTY 终端路径;适配已有 IO 时为 nil。
397
- # @return [String, nil]
398
- # @!method wait(timeout: nil)
399
- # 等待或回收子进程,未取得真实状态时返回 nil;期限以秒计。
400
- # @return [Process::Status, nil]
401
- # @!method winsize
402
- # 读取终端行列数,保留原生 IO 错误。
403
- # @return [Array<Integer>]
404
- # @!method winsize=(value)
405
- # 校验并替换当前设置;缓冲和集合采用副本,IO 与回调只借用。
406
- # @return [void]
407
- # @!method write(*objects)
408
- # 返回已写字节数;背压超时抛出 WriteTimeout 并保留 bytes_written。
409
- # @return [Integer]
410
- # @!method write_log(*objects)
411
- # 向当前接收日志补写数据;返回目标调用结果,过滤暂存时可为 nil。
412
- # @return [Object]
413
- # @!method write_timeout
414
- # 读取会话独立配置;更改类级默认值不会追溯影响已建立会话。
415
- # @return [Numeric, nil]
416
- # @!method write_timeout=(value)
417
- # 校验并设置会话配置;非法数值不改变当前值,布尔值遵循 Ruby 真值。
418
- # @return [void]
419
- # @!method writer
420
- # 取得底层读端或写端,不复制描述符。
421
- # @return [IO]
422
- # @!method inspect
423
- # 返回不含缓冲、命令或秘密内容的安全诊断摘要。
424
- # @return [String]
425
- # 用户会话只委托稳定接口,内核读写、转接标记和资源账本不出现在公开方法中。
426
- def_delegators :@session,
427
- :<<, :after, :alive?, :before, :buffer, :buffer=, :buffer_discarded_bytes, :buffer_limit,
428
- :buffer_limit=, :captures, :clear_buffer, :close, :closed?, :command, :debug_level,
429
- :debug_level=, :diagnostic_output, :diagnostic_output=, :eof?, :error, :exit_code, :fileno,
430
- :graceful_close=, :graceful_close?, :hard_close, :interact, :last_result,
431
- :listeners, :listeners=, :log_listeners=, :log_listeners?, :log_output,
432
- :log_output=, :log_stdout=, :log_stdout?, :log_to, :match, :match_number,
433
- :on_sequence, :pending_output?, :pid, :preserve_buffer=, :preserve_buffer?,
434
- :process_status, :puts, :raw_pty=, :raw_pty?, :raw_terminal=,
435
- :raw_terminal?, :redact, :reset_timeout_on_read=,
436
- :reset_timeout_on_read?, :send_slow, :slave, :soft_close, :spawn, :stty, :timeout, :timeout=,
437
- :to_io, :tty?, :tty_name, :wait, :winsize, :winsize=, :write, :write_log, :write_timeout,
438
- :write_timeout=, :writer, :inspect
439
-
440
- # 创建 PTY,可立即执行命令;未完成初始化时按局部所有权释放全部句柄。
441
- # @param command [Array<String>] 可选启动命令及参数。
442
- # @return [Expect] 已创建的用户会话。
443
- def initialize(*command, env: {}, chdir: nil, **)
444
- master = slave = nil
445
- Cleanup.on_failure(-> { cleanup_session(master, writer: master, slave:, own: true) }) do
446
- master, slave = PTY.open
447
- initialize_session(master, writer: master, slave:, own: true, **)
448
- spawn(*command, env:, chdir:) unless command.empty?
449
- end
450
- end
451
-
452
- # 返回不可变结果;模式块无参数时使用 DSL,有参数时保留调用者 self。
453
- # @param patterns [Array<String, Regexp, Symbol>] 文本模式、:eof 或 :timeout。
454
- # @param timeout [Numeric, nil] 相对秒数,nil 无限,0 非阻塞轮询。
455
- # @param deadline [Numeric, nil] 单调时钟绝对期限,不被回调延长。
456
- # @yieldparam patterns [Expect::PatternList] 有参数块接收构建器;无参数块以 DSL 执行。
457
- # @return [Expect::Result] 本次等待的不可变快照。
458
- def expect(*patterns, timeout: self.timeout, deadline: nil, &)
459
- self.class.__send__(:run_expect, [self], patterns, timeout, deadline:, &)
460
- end
461
-
462
- # 返回回调继续控制符,可选择保持原期限。
463
- # @return [Symbol] 重置期限或保持期限的继续控制符。
464
- def continue(reset_timeout: true) = Expect.continue(reset_timeout:)
465
-
466
- private
467
-
468
- attr_reader :session
469
-
470
- def initialize_session(reader, **)
471
- @session = Session.allocate
472
- @session.initialize_connection(self, reader, **)
473
- end
474
-
475
- def cleanup_session(reader, writer:, own:, slave: nil, graceful: false)
476
- if @session
477
- @session.cleanup_session(reader, writer:, own:, slave:, graceful:)
478
- elsif own
479
- SessionResources.close_handles(reader, writer, slave)
156
+ Matcher.new(pattern_list, timeout, deadline:, consume:, reset_timeout_on_read:).run
480
157
  end
481
158
  end
482
159
  end