Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Unified Diff: packages/args/lib/command_runner.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/args/lib/args.dart ('k') | packages/args/lib/src/arg_parser.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/args/lib/command_runner.dart
diff --git a/packages/args/lib/command_runner.dart b/packages/args/lib/command_runner.dart
index d728cb0269402fd7c160e06c3aa9595f2495e0e1..6a6d2826a241777900272dc3f59ab604d41a96c1 100644
--- a/packages/args/lib/command_runner.dart
+++ b/packages/args/lib/command_runner.dart
@@ -2,13 +2,12 @@
// 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.
-library args.command_runner;
-
import 'dart:async';
import 'dart:collection';
import 'dart:math' as math;
import 'src/arg_parser.dart';
+import 'src/arg_parser_exception.dart';
import 'src/arg_results.dart';
import 'src/help_command.dart';
import 'src/usage_exception.dart';
@@ -17,7 +16,11 @@ import 'src/utils.dart';
export 'src/usage_exception.dart';
/// A class for invoking [Commands] based on raw command-line arguments.
-class CommandRunner {
+///
+/// The type argument `T` represents the type returned by [Command.run] and
+/// [CommandRunner.run]; it can be ommitted if you're not using the return
+/// values.
+class CommandRunner<T> {
/// The name of the executable being run.
///
/// Used for error reporting and [usage].
@@ -42,7 +45,7 @@ class CommandRunner {
///
/// If a subclass overrides this to return a string, it will automatically be
/// added to the end of [usage].
- final String usageFooter = null;
+ String get usageFooter => null;
/// Returns [usage] with [description] removed from the beginning.
String get _usageWithoutDescription {
@@ -61,20 +64,21 @@ Run "$executableName help <command>" for more information about a command.''';
}
/// An unmodifiable view of all top-level commands defined for this runner.
- Map<String, Command> get commands => new UnmodifiableMapView(_commands);
- final _commands = new Map<String, Command>();
+ Map<String, Command<T>> get commands => new UnmodifiableMapView(_commands);
+ final _commands = <String, Command<T>>{};
/// The top-level argument parser.
///
/// Global options should be registered with this parser; they'll end up
/// available via [Command.globalResults]. Commands should be registered with
/// [addCommand] rather than directly on the parser.
- final argParser = new ArgParser();
+ ArgParser get argParser => _argParser;
+ final _argParser = new ArgParser();
CommandRunner(this.executableName, this.description) {
argParser.addFlag('help',
abbr: 'h', negatable: false, help: 'Print this usage information.');
- addCommand(new HelpCommand());
+ addCommand(new HelpCommand<T>());
}
/// Prints the usage information for this runner.
@@ -88,7 +92,7 @@ Run "$executableName help <command>" for more information about a command.''';
throw new UsageException(message, _usageWithoutDescription);
/// Adds [Command] as a top-level command to this runner.
- void addCommand(Command command) {
+ void addCommand(Command<T> command) {
var names = [command.name]..addAll(command.aliases);
for (var name in names) {
_commands[name] = command;
@@ -100,22 +104,28 @@ Run "$executableName help <command>" for more information about a command.''';
/// Parses [args] and invokes [Command.run] on the chosen command.
///
/// This always returns a [Future] in case the command is asynchronous. The
- /// [Future] will throw a [UsageError] if [args] was invalid.
- Future run(Iterable<String> args) =>
+ /// [Future] will throw a [UsageException] if [args] was invalid.
+ Future<T> run(Iterable<String> args) =>
new Future.sync(() => runCommand(parse(args)));
- /// Parses [args] and returns the result, converting a [FormatException] to a
- /// [UsageException].
+ /// Parses [args] and returns the result, converting an [ArgParserException]
+ /// to a [UsageException].
///
/// This is notionally a protected method. It may be overridden or called from
/// subclasses, but it shouldn't be called externally.
ArgResults parse(Iterable<String> args) {
try {
- // TODO(nweiz): if arg parsing fails for a command, print that command's
- // usage, not the global usage.
return argParser.parse(args);
- } on FormatException catch (error) {
- usageException(error.message);
+ } on ArgParserException catch (error) {
+ if (error.commands.isEmpty) usageException(error.message);
+
+ var command = commands[error.commands.first];
+ for (var commandName in error.commands.skip(1)) {
+ command = command.subcommands[commandName];
+ }
+
+ command.usageException(error.message);
+ return null;
}
}
@@ -127,56 +137,61 @@ Run "$executableName help <command>" for more information about a command.''';
/// It's useful to override this to handle global flags and/or wrap the entire
/// command in a block. For example, you might handle the `--verbose` flag
/// here to enable verbose logging before running the command.
- Future runCommand(ArgResults topLevelResults) {
- return new Future.sync(() {
- var argResults = topLevelResults;
- var commands = _commands;
- var command;
- var commandString = executableName;
-
- while (commands.isNotEmpty) {
- if (argResults.command == null) {
- if (argResults.rest.isEmpty) {
- if (command == null) {
- // No top-level command was chosen.
- printUsage();
- return new Future.value();
- }
-
- command.usageException('Missing subcommand for "$commandString".');
- } else {
- if (command == null) {
- usageException(
- 'Could not find a command named "${argResults.rest[0]}".');
- }
-
- command.usageException('Could not find a subcommand named '
- '"${argResults.rest[0]}" for "$commandString".');
+ ///
+ /// This returns the return value of [Command.run].
+ Future<T> runCommand(ArgResults topLevelResults) async {
+ var argResults = topLevelResults;
+ var commands = _commands;
+ Command command;
+ var commandString = executableName;
+
+ while (commands.isNotEmpty) {
+ if (argResults.command == null) {
+ if (argResults.rest.isEmpty) {
+ if (command == null) {
+ // No top-level command was chosen.
+ printUsage();
+ return null;
}
- }
- // Step into the command.
- argResults = argResults.command;
- command = commands[argResults.name];
- command._globalResults = topLevelResults;
- command._argResults = argResults;
- commands = command._subcommands;
- commandString += " ${argResults.name}";
-
- if (argResults['help']) {
- command.printUsage();
- return new Future.value();
+ command.usageException('Missing subcommand for "$commandString".');
+ } else {
+ if (command == null) {
+ usageException(
+ 'Could not find a command named "${argResults.rest[0]}".');
+ }
+
+ command.usageException('Could not find a subcommand named '
+ '"${argResults.rest[0]}" for "$commandString".');
}
}
- // Make sure there aren't unexpected arguments.
- if (!command.takesArguments && argResults.rest.isNotEmpty) {
- command.usageException(
- 'Command "${argResults.name}" does not take any arguments.');
+ // Step into the command.
+ argResults = argResults.command;
+ command = commands[argResults.name];
+ command._globalResults = topLevelResults;
+ command._argResults = argResults;
+ commands = command._subcommands;
+ commandString += " ${argResults.name}";
+
+ if (argResults['help']) {
+ command.printUsage();
+ return null;
}
+ }
- return command.run();
- });
+ if (topLevelResults['help']) {
+ command.printUsage();
+ return null;
+ }
+
+ // Make sure there aren't unexpected arguments.
+ if (!command.takesArguments && argResults.rest.isNotEmpty) {
+ command.usageException(
+ 'Command "${argResults.name}" does not take any arguments.');
+ }
+
+ return (await command.run()) as T;
}
}
@@ -188,13 +203,19 @@ Run "$executableName help <command>" for more information about a command.''';
/// A command with subcommands is known as a "branch command" and cannot be run
/// itself. It should call [addSubcommand] (often from the constructor) to
/// register subcommands.
-abstract class Command {
+abstract class Command<T> {
/// The name of this command.
String get name;
- /// A short description of this command.
+ /// A description of this command, included in [usage].
String get description;
+ /// A short description of this command, included in [parent]'s
+ /// [CommandRunner.usage].
+ ///
+ /// This defaults to the first line of [description].
+ String get summary => description.split("\n").first;
+
/// A single-line template for how to invoke this command (e.g. `"pub get
/// [package]"`).
String get invocation {
@@ -214,18 +235,18 @@ abstract class Command {
///
/// This will be `null` until [Command.addSubcommmand] has been called with
/// this command.
- Command get parent => _parent;
- Command _parent;
+ Command<T> get parent => _parent;
+ Command<T> _parent;
/// The command runner for this command.
///
/// This will be `null` until [CommandRunner.addCommand] has been called with
/// this command or one of its parents.
- CommandRunner get runner {
+ CommandRunner<T> get runner {
if (parent == null) return _runner;
return parent.runner;
}
- CommandRunner _runner;
+ CommandRunner<T> _runner;
/// The parsed global argument results.
///
@@ -245,7 +266,8 @@ abstract class Command {
/// the constructor); they'll end up available via [argResults]. Subcommands
/// should be registered with [addSubcommand] rather than directly on the
/// parser.
- final argParser = new ArgParser();
+ ArgParser get argParser => _argParser;
+ final _argParser = new ArgParser();
/// Generates a string displaying usage information for this command.
///
@@ -257,13 +279,13 @@ abstract class Command {
///
/// If a subclass overrides this to return a string, it will automatically be
/// added to the end of [usage].
- final String usageFooter = null;
+ String get usageFooter => null;
/// Returns [usage] with [description] removed from the beginning.
String get _usageWithoutDescription {
var buffer = new StringBuffer()
- ..writeln('Usage: $invocation')
- ..writeln(argParser.usage);
+ ..writeln('Usage: $invocation')
+ ..writeln(argParser.usage);
if (_subcommands.isNotEmpty) {
buffer.writeln();
@@ -282,8 +304,9 @@ abstract class Command {
}
/// An unmodifiable view of all sublevel commands of this command.
- Map<String, Command> get subcommands => new UnmodifiableMapView(_subcommands);
- final _subcommands = new Map<String, Command>();
+ Map<String, Command<T>> get subcommands =>
+ new UnmodifiableMapView(_subcommands);
+ final _subcommands = <String, Command<T>>{};
/// Whether or not this command should be hidden from help listings.
///
@@ -308,7 +331,7 @@ abstract class Command {
///
/// This is intended to be overridden by commands that don't want to receive
/// arguments. It has no effect for branch commands.
- final takesArguments = true;
+ bool get takesArguments => true;
/// Alternate names for this command.
///
@@ -316,7 +339,7 @@ abstract class Command {
/// invoked on the command line.
///
/// This is intended to be overridden.
- final aliases = const <String>[];
+ List<String> get aliases => const [];
Command() {
argParser.addFlag('help',
@@ -325,14 +348,15 @@ abstract class Command {
/// Runs this command.
///
- /// If this returns a [Future], [CommandRunner.run] won't complete until the
- /// returned [Future] does. Otherwise, the return value is ignored.
+ /// This must return a `T`, a `Future<T>`, or `null`. The value is returned by
+ /// [CommandRunner.runCommand]. Subclasses must explicitly declare a return
+ /// type for `run()`, and may not use `void` if `T` is defined.
run() {
throw new UnimplementedError("Leaf command $this must implement run().");
}
/// Adds [Command] as a subcommand of this.
- void addSubcommand(Command command) {
+ void addSubcommand(Command<T> command) {
var names = [command.name]..addAll(command.aliases);
for (var name in names) {
_subcommands[name] = command;
@@ -349,7 +373,7 @@ abstract class Command {
/// Throws a [UsageException] with [message].
void usageException(String message) =>
- throw new UsageException(message, _usageWithoutDescription);
+ throw new UsageException(message, _usageWithoutDescription);
}
/// Returns a string representation of [commands] fit for use in a usage string.
@@ -360,7 +384,7 @@ String _getCommandUsage(Map<String, Command> commands,
{bool isSubcommand: false}) {
// Don't include aliases.
var names =
- commands.keys.where((name) => !commands[name].aliases.contains(name));
+ commands.keys.where((name) => !commands[name].aliases.contains(name));
// Filter out hidden ones, unless they are all hidden.
var visible = names.where((name) => !commands[name].hidden);
@@ -371,11 +395,17 @@ String _getCommandUsage(Map<String, Command> commands,
var length = names.map((name) => name.length).reduce(math.max);
var buffer =
- new StringBuffer('Available ${isSubcommand ? "sub" : ""}commands:');
+ new StringBuffer('Available ${isSubcommand ? "sub" : ""}commands:');
for (var name in names) {
+ var lines = commands[name].summary.split("\n");
buffer.writeln();
- buffer.write(' ${padRight(name, length)} '
- '${commands[name].description.split("\n").first}');
+ buffer.write(' ${padRight(name, length)} ${lines.first}');
+
+ for (var line in lines.skip(1)) {
+ buffer.writeln();
+ buffer.write(' ' * (length + 5));
+ buffer.write(line);
+ }
}
return buffer.toString();
« no previous file with comments | « packages/args/lib/args.dart ('k') | packages/args/lib/src/arg_parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698