blob: 1592d6d36e1a30d2d75dd9f1a1e12377d7a71ffd [file] [log] [blame]
// 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:io';
import 'dart:math' as math;
import 'package:async/async.dart';
import 'package:pedantic/pedantic.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, and may return a [Future].
void registerCommand(
String name, String description, dynamic Function() body) {
if (_commands.containsKey(name)) {
throw ArgumentError('The console already has a command named "$name".');
}
_commands[name] = _Command(name, description, 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}');
}
}
}
/// An individual console command.
class _Command {
/// The name of the command.
final String name;
/// The single-line description of the command.
final String description;
/// The callback to run when the command is invoked.
final Function body;
_Command(this.name, this.description, this.body);
}