| // Copyright (c) 2016, 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:io'; | |
| import 'dart:math' as math; | |
| import 'package:async/async.dart'; | |
| import '../util/io.dart'; | |
| /// An interactive console for taking user commands. | |
| class Console { | |
| /// The registered commands. | |
| final _commands = <String, _Command>{}; | |
| /// The pending next line of standard input, if we're waiting on one. | |
| CancelableOperation? _nextLine; | |
| /// Whether the console is currently running. | |
| bool _running = false; | |
| /// The terminal escape for red text, or the empty string if this is Windows | |
| /// or not outputting to a terminal. | |
| final String _red; | |
| /// The terminal escape for bold text, or the empty string if this is | |
| /// Windows or not outputting to a terminal. | |
| final String _bold; | |
| /// The terminal escape for removing test coloring, or the empty string if | |
| /// this is Windows or not outputting to a terminal. | |
| final String _noColor; | |
| /// Creates a new [Console]. | |
| /// | |
| /// If [color] is true, this uses Unix terminal colors. | |
| Console({bool color = true}) | |
| : _red = color ? '\u001b[31m' : '', | |
| _bold = color ? '\u001b[1m' : '', | |
| _noColor = color ? '\u001b[0m' : '' { | |
| registerCommand('help', 'Displays this help information.', _displayHelp); | |
| } | |
| /// Registers a command to be run whenever the user types [name]. | |
| /// | |
| /// The [description] should be a one-line description of the command to print | |
| /// in the help output. The [body] callback will be called when the user types | |
| /// the command. | |
| void registerCommand( | |
| String name, | |
| String description, | |
| FutureOr<void> Function() body, | |
| ) { | |
| if (_commands.containsKey(name)) { | |
| throw ArgumentError('The console already has a command named "$name".'); | |
| } | |
| _commands[name] = (name: name, description: description, body: body); | |
| } | |
| /// Starts running the console. | |
| /// | |
| /// This prints the initial prompt and loops while waiting for user input. | |
| void start() { | |
| _running = true; | |
| unawaited(() async { | |
| while (_running) { | |
| stdout.write('> '); | |
| _nextLine = stdinLines.cancelable((queue) => queue.next); | |
| var commandName = await _nextLine!.value; | |
| _nextLine = null; | |
| var command = _commands[commandName]; | |
| if (command == null) { | |
| stderr.writeln( | |
| '${_red}Unknown command $_bold$commandName$_noColor$_red.' | |
| '$_noColor', | |
| ); | |
| } else { | |
| await command.body(); | |
| } | |
| } | |
| }()); | |
| } | |
| /// Stops the console running. | |
| void stop() { | |
| _running = false; | |
| if (_nextLine != null) { | |
| stdout.writeln(); | |
| _nextLine!.cancel(); | |
| } | |
| } | |
| /// Displays the help info for the console commands. | |
| void _displayHelp() { | |
| var maxCommandLength = _commands.values | |
| .map((command) => command.name.length) | |
| .reduce(math.max); | |
| for (var command in _commands.values) { | |
| var name = command.name.padRight(maxCommandLength + 4); | |
| print('$_bold$name$_noColor${command.description}'); | |
| } | |
| } | |
| } | |
| typedef _Command = ({ | |
| String name, | |
| String description, | |
| FutureOr<void> Function() body, | |
| }); |