blob: 3bb0d27fd16c0d5359ce3a71861ccc627398524f [file] [edit]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
/// A shared singleton instance of `dart:io`'s [stdin] stream.
///
/// _Unlike_ the normal [stdin] stream, [sharedStdIn] may switch subscribers
/// as long as the previous subscriber cancels before the new subscriber starts
/// listening.
///
/// [SharedStdIn.terminate] *must* be invoked in order to close the underlying
/// connection to [stdin], allowing your program to close automatically without
/// hanging.
final SharedStdIn sharedStdIn = SharedStdIn(stdin);
/// A wrapper around a stream that allows new subscribers, intended for use
/// with [stdin] or other input streams that usually only allow one subscriber.
///
/// If you only use this class with [stdin], you should use the [sharedStdIn]
/// singleton.
///
/// You may still only have one listening [StreamSubscription] at a time.
class SharedStdIn extends Stream<List<int>> {
StreamController<List<int>>? _current;
StreamSubscription<List<int>>? _sub;
/// Creates a new [SharedStdIn] sharing the [stream].
///
/// If you only use this class with [stdin], you should use the [sharedStdIn]
/// singleton.
///
/// Calling this constructor more than once with the same source [stream]
/// will likely result in an error.
factory SharedStdIn(Stream<List<int>> stream) => SharedStdIn._(stream);
/// Actual constructor is private to prevent subclassing.
SharedStdIn._(Stream<List<int>> stream) {
_sub = stream.listen(_onInput);
}
/// Returns a future that completes with the next line.
///
/// This is similar to the standard [Stdin.readLineSync], but asynchronous.
Future<String> nextLine({Encoding encoding = systemEncoding}) =>
lines(encoding: encoding).first;
/// Returns the stream transformed as UTF8 strings separated by line breaks.
///
/// This is similar to synchronous code using [Stdin.readLineSync]:
/// ```dart
/// while (true) {
/// var line = stdin.readLineSync();
/// // ...
/// }
/// ```
///
/// ... but asynchronous.
Stream<String> lines({Encoding encoding = systemEncoding}) =>
transform(utf8.decoder).transform(const LineSplitter());
void _onInput(List<int> event) => _getCurrent().add(event);
StreamController<List<int>> _getCurrent() =>
_current ??= StreamController<List<int>>(
onCancel: () {
_current = null;
},
sync: true);
@override
StreamSubscription<List<int>> listen(
void Function(List<int> event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
if (_sub == null) {
throw StateError('Stdin has already been terminated.');
}
// ignore: close_sinks
final controller = _getCurrent();
if (controller.hasListener) {
throw StateError(''
'Subscriber already listening. The existing subscriber must cancel '
'before another may be added.');
}
return controller.stream.listen(
onData,
onDone: onDone,
onError: onError,
cancelOnError: cancelOnError,
);
}
/// Terminates the connection to `stdin`, closing all subscription.
Future<void> terminate() async {
if (_sub == null) {
throw StateError('Stdin has already been terminated.');
}
await _sub?.cancel();
await _current?.close();
_sub = null;
}
}