Chromium Code Reviews| Index: sdk/lib/_internal/pub/lib/src/command.dart | 
| diff --git a/sdk/lib/_internal/pub/lib/src/command.dart b/sdk/lib/_internal/pub/lib/src/command.dart | 
| index e3dcab5ef072fac8cfb9c72cf00217044dcaeee4..2a4d13e29ab41fd524f9023a214dc04bfb25f836 100644 | 
| --- a/sdk/lib/_internal/pub/lib/src/command.dart | 
| +++ b/sdk/lib/_internal/pub/lib/src/command.dart | 
| @@ -33,7 +33,7 @@ import 'utils.dart'; | 
| /// The base class for commands for the pub executable. | 
| 
 
nweiz
2014/01/31 21:42:06
Explain how commands vs subcommands work.
 
Bob Nystrom
2014/02/01 01:49:32
Done.
 
 | 
| abstract class PubCommand { | 
| /// The commands that pub understands. | 
| - static final Map<String, PubCommand> commands = _initCommands(); | 
| + static final Map<String, PubCommand> mainCommands = _initCommands(); | 
| /// The top-level [ArgParser] used to parse the pub command line. | 
| static final pubArgParser = _initArgParser(); | 
| @@ -44,35 +44,45 @@ abstract class PubCommand { | 
| var buffer = new StringBuffer(); | 
| buffer.writeln('Pub is a package manager for Dart.'); | 
| buffer.writeln(); | 
| - buffer.writeln('Usage: pub command [arguments]'); | 
| + buffer.writeln('Usage: pub <command> [arguments]'); | 
| buffer.writeln(); | 
| buffer.writeln('Global options:'); | 
| buffer.writeln(pubArgParser.getUsage()); | 
| + listCommands(mainCommands, buffer); | 
| buffer.writeln(); | 
| + buffer.writeln( | 
| + 'Use "pub help [command]" for more information about a command.'); | 
| + | 
| + log.message(buffer); | 
| + } | 
| + /// Writes [commands] in a nicely formatted list to [buffer]. | 
| 
 
nweiz
2014/01/31 21:42:06
Document [isSubcommand].
 
Bob Nystrom
2014/02/01 01:49:32
Done.
 
 | 
| + static void listCommands(Map<String, PubCommand> commands, | 
| + StringBuffer buffer, {bool isSubcommand: false}) { | 
| 
 
nweiz
2014/01/31 21:42:06
I really don't like passing in a StringBuffer as o
 
Bob Nystrom
2014/02/01 01:49:32
Done.
 
 | 
| // Show the public commands alphabetically. | 
| var names = ordered(commands.keys.where((name) => | 
| !commands[name].aliases.contains(name) && | 
| !commands[name].hidden)); | 
| + // If all of the subcommands are hidden, do nothing. | 
| + // TODO(rnystrom): Remove this once cache has a visible command. | 
| 
 
nweiz
2014/01/31 21:42:06
I don't think you should remove this even if there
 
Bob Nystrom
2014/02/01 01:49:32
Yup. It turns out this needs to stay to handle ano
 
 | 
| + if (names.isEmpty) return; | 
| + | 
| var length = names.map((name) => name.length).reduce(math.max); | 
| - buffer.writeln('Available commands:'); | 
| + buffer.writeln(); | 
| + buffer.writeln('Available ${isSubcommand ? "sub" : ""}commands:'); | 
| for (var name in names) { | 
| buffer.writeln(' ${padRight(name, length)} ' | 
| '${commands[name].description}'); | 
| } | 
| - | 
| - buffer.writeln(); | 
| - buffer.write( | 
| - 'Use "pub help [command]" for more information about a command.'); | 
| - log.message(buffer.toString()); | 
| } | 
| SystemCache cache; | 
| /// The parsed options for this command. | 
| - ArgResults commandOptions; | 
| + ArgResults get commandOptions => _commandOptions; | 
| + ArgResults _commandOptions; | 
| Entrypoint entrypoint; | 
| @@ -102,6 +112,13 @@ abstract class PubCommand { | 
| /// The [ArgParser] for this command. | 
| final commandParser = new ArgParser(); | 
| + /// Subcommands exposed by this command. | 
| + /// | 
| + /// If empty, then this command has no subcommands. Otherwise, a subcommand | 
| + /// must be specified by the user. In that case, this command's [onRun] will | 
| + /// not be called and the subcommand's will. | 
| + final subcommands = <String, PubCommand>{}; | 
| + | 
| /// Override this to use offline-only sources instead of hitting the network. | 
| /// This will only be called before the [SystemCache] is created. After that, | 
| /// it has no effect. | 
| @@ -113,13 +130,10 @@ abstract class PubCommand { | 
| help: 'Print usage information for this command.'); | 
| } | 
| - void run(String cacheDir, ArgResults options, List<String> arguments) { | 
| - commandOptions = options.command; | 
| - | 
| - if (commandOptions['help']) { | 
| - this.printUsage(); | 
| - return; | 
| - } | 
| + /// Runs this command. | 
| 
 
nweiz
2014/01/31 21:42:06
Document the arguments. It's especially unclear wh
 
Bob Nystrom
2014/02/01 01:49:32
Documented. "global" is a little misleading becaus
 
 | 
| + void run(String cacheDir, ArgResults mainOptions, ArgResults commandOptions, | 
| + List<String> arguments) { | 
| + _commandOptions = commandOptions; | 
| cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); | 
| @@ -131,7 +145,7 @@ abstract class PubCommand { | 
| log.error(getErrorMessage(error)); | 
| log.fine("Exception type: ${error.runtimeType}"); | 
| - if (options['trace'] || !isUserFacingException(error)) { | 
| + if (mainOptions['trace'] || !isUserFacingException(error)) { | 
| log.error(chain.terse); | 
| } else { | 
| log.fine(chain.terse); | 
| @@ -143,7 +157,7 @@ abstract class PubCommand { | 
| log.fine(message); | 
| } | 
| - if (options['trace']) { | 
| + if (mainOptions['trace']) { | 
| log.dumpTranscript(); | 
| } else if (!isUserFacingException(error)) { | 
| log.error(""" | 
| @@ -159,7 +173,10 @@ and include the results in a bug report on http://dartbug.com/new. | 
| } | 
| var captureStackChains = | 
| - options['trace'] || options['verbose'] || options['verbosity'] == 'all'; | 
| + mainOptions['trace'] || | 
| + mainOptions['verbose'] || | 
| + mainOptions['verbosity'] == 'all'; | 
| + | 
| captureErrors(() { | 
| return syncFuture(() { | 
| // Make sure there aren't unexpected arguments. | 
| @@ -193,7 +210,10 @@ and include the results in a bug report on http://dartbug.com/new. | 
| /// Override this to perform the specific command. Return a future that | 
| /// completes when the command is done or fails if the command fails. If the | 
| /// command is synchronous, it may return `null`. | 
| 
 
nweiz
2014/01/31 21:42:06
Mention that this only needs to be overridden for
 
Bob Nystrom
2014/02/01 01:49:32
Done.
 
 | 
| - Future onRun(); | 
| + Future onRun() { | 
| + // Leaf commands should override this. | 
| 
 
nweiz
2014/01/31 21:42:06
For non-leaf commands, this should still never be
 
Bob Nystrom
2014/02/01 01:49:32
Yup. Changed to assert(false).
 
 | 
| + assert(subcommands.isNotEmpty); | 
| + } | 
| /// Displays usage information for this command. | 
| void printUsage([String description]) { | 
| @@ -208,6 +228,11 @@ and include the results in a bug report on http://dartbug.com/new. | 
| buffer.write(commandUsage); | 
| } | 
| + if (subcommands.isNotEmpty) { | 
| + buffer.writeln(); | 
| + listCommands(subcommands, buffer); | 
| + } | 
| + | 
| log.message(buffer.toString()); | 
| } | 
| @@ -272,9 +297,19 @@ ArgParser _initArgParser() { | 
| help: 'Shortcut for "--verbosity=all".'); | 
| // Register the commands. | 
| - PubCommand.commands.forEach((name, command) { | 
| - argParser.addCommand(name, command.commandParser); | 
| + PubCommand.mainCommands.forEach((name, command) { | 
| + _registerCommand(name, command, argParser); | 
| }); | 
| return argParser; | 
| } | 
| + | 
| +/// Registers a [command] with [name] on [parser]. | 
| +void _registerCommand(String name, PubCommand command, ArgParser parser) { | 
| + parser.addCommand(name, command.commandParser); | 
| + | 
| + // Recursively wire up any subcommands. | 
| + command.subcommands.forEach((name, subcommand) { | 
| + _registerCommand(name, subcommand, command.commandParser); | 
| + }); | 
| +} |