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