Home > database >  Override stdin, stdout, stderr in Dart
Override stdin, stdout, stderr in Dart

Time:09-16

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);
  }
}

  • Related