在Dart中实现类Bash Expect功能:重写Stdin遇阻及方案问询
实现类Bash 'expect'的Dart交互功能
问题背景
我想实现一个等效于Bash expect命令的功能,能够捕获stdout输出并向stdin注入数据。Dart的IOOverrides类支持重写stdout、stdin和stderr,但重写stdin需要一个StdIn实例,而StdIn只有私有构造函数,没法自行实例化,导致无法通过ZoneSpecification重写StdIn。想知道是不是遗漏了什么?既然stdin的getter代码已经支持重写,有没有其他实现方式?
期望最终效果
Interact(spawn: () { final age = ask( 'How old are you', defaultValue: '5', customPrompt: (prompt, defaultValue, {hidden = false}) => 'AAA$prompt:$defaultValue', ); print('You are $age years old'); }) ..expect('AAAHow old are you:5') ..send('6') ..expect('You are 6 years old');
可行实现方案
不用纠结实例化StdIn,可以通过以下方式实现:
- 自定义Mock IO类:创建继承自
Stdin和Stdout的子类(两者都是抽象类),重写核心方法:MockStdin:维护一个输入队列,重写readLineSync等方法,从队列中取出预注入的数据返回。MockStdout:维护一个字符串缓冲区,重写write/writeln等方法,把输出内容写入缓冲区。
- 用IOOverrides包裹执行逻辑:通过
IOOverrides.runZoned方法,将目标代码(spawn回调)包裹在自定义的IO环境中,替换默认的stdin和stdout为Mock实现。 - 封装Interact类:用
Interact类管理MockStdin的输入队列和MockStdout的输出缓冲区,实现expect(检查缓冲区是否匹配预期内容)和send(往输入队列添加数据)方法。
核心代码示例
import 'dart:io'; import 'dart:async'; import 'dart:collection'; class MockStdin extends Stdin { final Queue<String> _inputQueue = Queue(); void addInput(String input) { _inputQueue.add(input); } @override String? readLineSync({bool? retainNewlines}) { if (_inputQueue.isEmpty) return null; return _inputQueue.removeFirst(); } @override int get available => 0; @override Stream<List<int>> get asyncExpand => Stream.empty(); // 其他未实现的Stdin方法可按需补充,或抛出UnsupportedOperationException } class MockStdout extends Stdout { final StringBuffer _outputBuffer = StringBuffer(); String get output => _outputBuffer.toString(); @override void write(Object? obj) { _outputBuffer.write(obj); } @override void writeln([Object? obj = '']) { _outputBuffer.writeln(obj); } @override void writeAll(Iterable objects, [String separator = '']) { _outputBuffer.writeAll(objects, separator); } } class Interact { final MockStdin _mockStdin = MockStdin(); final MockStdout _mockStdout = MockStdout(); late final Future<void> _runFuture; Interact({required void Function() spawn}) { _runFuture = IOOverrides.runZoned(() { spawn(); return Future.value(); }, createStdin: () => _mockStdin, createStdout: () => _mockStdout); } void expect(String expected) { if (!_mockStdout.output.contains(expected)) { throw Exception('Expected "$expected" not found in output: "${_mockStdout.output}"'); } // 可选:匹配后清空缓冲区,避免后续重复匹配 // _mockStdout._outputBuffer.clear(); } void send(String input) { _mockStdin.addInput(input); } } // 示例中的ask函数实现 String ask(String prompt, {String defaultValue = '', String Function(String, String, {bool hidden})? customPrompt}) { final promptText = customPrompt?.call(prompt, defaultValue) ?? '$prompt [$defaultValue]: '; stdout.write(promptText); final input = stdin.readLineSync() ?? defaultValue; return input.isNotEmpty ? input : defaultValue; } // 使用示例 void main() { Interact(spawn: () { final age = ask( 'How old are you', defaultValue: '5', customPrompt: (prompt, defaultValue, {hidden = false}) => 'AAA$prompt:$defaultValue', ); print('You are $age years old'); }) ..expect('AAAHow old are you:5') ..send('6') ..expect('You are 6 years old'); }
内容的提问来源于stack exchange,提问作者Brett Sutton
相关产品推荐
相关产品推荐

