I'm looking to build the equivalent of the bash 'expect' command which is able to capture output to stdout as well as inject data into stdin.
Dart provides the IOOverrides class that looks like it allows you to override stdout, stdin and stderr.
The ZoneSpecification that you pass to IOOverrides expects a instance of type StdIn to override stdin.
IOOverrides.runZoned(() => action,
stdin: () => Stdin._(mystream), // this won't work as there is no public ctor
);
As such I was hoping I could instantiate my own copy of StdIn and then inject data into it.
The problem is that StdIn only has a private constructor.
So it would appear that there is no way to actually override Stdin using a ZoneSpecification.
Am I missing something here?
The stdin getter actually has the code to allow it to be overriden:
/// The standard input stream of data read by this program.
Stdin get stdin {
return IOOverrides.current?.stdin ?? _stdin;
}
Are there other ways to achieve this?
Ultimately this is what I'm trying to achieve:
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 ar you:5')
..send('6')
..expect('You are 6 years old');
CodePudding user response:
The Process
class exposes a stdin
member. See https://api.dart.dev/stable/2.10.5/dart-io/Process-class.html#standard-io-streams.
CodePudding user response:
So this is not even close to a working solution, but based on @jamesdlin comment the basic implementation looks like this.
Currently all of the puppet wrapper classes just forward calls to the real stdin/out/err. In reality these will need to intercept and process the calls rather than passing them through.
void test() {
Puppet(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 ar you:5')
..send('6')
..expect('You are 6 years old');
}
The Puppet class:
class Puppet<T> {
Puppet({required this.spawn});
T Function() spawn;
void _run() {
IOOverrides.runZoned(() => spawn,
stdin: PuppetStdin.new,
stdout: () => PuppetStdout(stdout),
stderr: () => PuppetStdout(stderr));
}
void expect(String expected) {}
void send(String s) {}
}
PuppetStdin
import 'dart:async';
import 'dart:convert';
import 'dart:io';
class PuppetStdin extends Stream<List<int>> implements Stdin {
PuppetStdin(
{this.echoMode = true,
this.echoNewlineMode = true,
this.lineMode = true});
StreamController<List<int>> controller = StreamController();
@override
bool echoMode;
@override
bool echoNewlineMode;
@override
bool lineMode;
@override
bool get hasTerminal => stdin.hasTerminal;
@override
StreamSubscription<List<int>> listen(void Function(List<int> event)? onData,
{Function? one rror, void Function()? onDone, bool? cancelOnError}) {
throw UnimplementedError();
}
@override
int readByteSync() => stdin.readByteSync();
@override
String? readLineSync(
{Encoding encoding = systemEncoding, bool retainNewlines = false}) =>
stdin.readLineSync(encoding: encoding, retainNewlines: retainNewlines);
@override
bool get supportsAnsiEscapes => stdin.supportsAnsiEscapes;
}
PuppetStdout which will probably also serve the needs of overloading stderr
import 'dart:convert';
import 'dart:io';
class PuppetStdout implements Stdout {
PuppetStdout(this.stdout);
Stdout stdout;
IOSink? _nonBlocking;
/// Whether there is a terminal attached to stdout.
@override
bool get hasTerminal => stdout.hasTerminal;
/// The number of columns of the terminal.
///
/// If no terminal is attached to stdout, a [StdoutException] is thrown. See
/// [hasTerminal] for more info.
@override
int get terminalColumns => stdout.terminalColumns;
/// The number of lines of the terminal.
///
/// If no terminal is attached to stdout, a [StdoutException] is thrown. See
/// [hasTerminal] for more info.
@override
int get terminalLines => stdout.terminalLines;
/// Whether connected to a terminal that supports ANSI escape sequences.
///
/// Not all terminals are recognized, and not all recognized terminals can
/// report whether they support ANSI escape sequences, so this value is a
/// best-effort attempt at detecting the support.
///
/// The actual escape sequence support may differ between terminals,
/// with some terminals supporting more escape sequences than others,
/// and some terminals even differing in behavior for the same escape
/// sequence.
///
/// The ANSI color selection is generally supported.
///
/// Currently, a `TERM` environment variable containing the string `xterm`
/// will be taken as evidence that ANSI escape sequences are supported.
/// On Windows, only versions of Windows 10 after v.1511
/// ("TH2", OS build 10586) will be detected as supporting the output of
/// ANSI escape sequences, and only versions after v.1607 ("Anniversary
/// Update", OS build 14393) will be detected as supporting the input of
/// ANSI escape sequences.
@override
bool get supportsAnsiEscapes => stdout.supportsAnsiEscapes;
/// A non-blocking `IOSink` for the same output.
@override
IOSink get nonBlocking => _nonBlocking ??= stdout.nonBlocking;
@override
Encoding get encoding => stdout.encoding;
@override
set encoding(Encoding encoding) {
stdout.encoding = encoding;
}
@override
void add(List<int> data) {
stdout.add(data);
}
@override
void addError(Object error, [StackTrace? stackTrace]) {
stdout.addError(error, stackTrace);
}
@override
// ignore: strict_raw_type
Future addStream(Stream<List<int>> stream) => stdout.addStream(stream);
@override
// ignore: strict_raw_type
Future close() => stdout.close();
@override
// ignore: strict_raw_type
Future get done => stdout.done;
@override
// ignore: strict_raw_type
Future flush() => stdout.flush();
@override
void write(Object? object) {
stdout.write(object);
}
@override
// ignore: strict_raw_type
void writeAll(Iterable objects, [String sep = '']) {
stdout.writeAll(objects, sep);
}
@override
void writeCharCode(int charCode) {
stdout.writeCharCode(charCode);
}
@override
void writeln([Object? object = '']) {
stdout.writeln(object);
}
}