| // Copyright (c) 2013, 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. |
| |
| library pub.command; |
| |
| import 'dart:async'; |
| import 'dart:math' as math; |
| |
| import 'package:args/args.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import 'command/build.dart'; |
| import 'command/cache.dart'; |
| import 'command/deps.dart'; |
| import 'command/get.dart'; |
| import 'command/help.dart'; |
| import 'command/lish.dart'; |
| import 'command/list_package_dirs.dart'; |
| import 'command/serve.dart'; |
| import 'command/upgrade.dart'; |
| import 'command/uploader.dart'; |
| import 'command/version.dart'; |
| import 'entrypoint.dart'; |
| import 'exit_codes.dart' as exit_codes; |
| import 'log.dart' as log; |
| import 'system_cache.dart'; |
| import 'utils.dart'; |
| |
| /// The base class for commands for the pub executable. |
| /// |
| /// A command may either be a "leaf" command or it may be a parent for a set |
| /// of subcommands. Only leaf commands are ever actually invoked. If a command |
| /// has subcommands, then one of those must always be chosen. |
| abstract class PubCommand { |
| /// The commands that pub understands. |
| static final Map<String, PubCommand> mainCommands = _initCommands(); |
| |
| /// The top-level [ArgParser] used to parse the pub command line. |
| static final pubArgParser = _initArgParser(); |
| |
| /// Displays usage information for the app. |
| static void printGlobalUsage() { |
| // Build up a buffer so it shows up as a single log entry. |
| var buffer = new StringBuffer(); |
| buffer.writeln('Pub is a package manager for Dart.'); |
| buffer.writeln(); |
| buffer.writeln('Usage: pub <command> [arguments]'); |
| buffer.writeln(); |
| buffer.writeln('Global options:'); |
| buffer.writeln(pubArgParser.getUsage()); |
| buffer.writeln(); |
| buffer.write(_listCommands(mainCommands)); |
| buffer.writeln(); |
| buffer.writeln( |
| 'Use "pub help [command]" for more information about a command.'); |
| |
| log.message(buffer); |
| } |
| |
| /// Fails with a usage error [message] when trying to select from one of |
| /// [commands]. |
| static void usageErrorWithCommands(Map<String, PubCommand> commands, |
| String message) { |
| throw new UsageException(message, _listCommands(commands)); |
| } |
| |
| /// Writes [commands] in a nicely formatted list to [buffer]. |
| static String _listCommands(Map<String, PubCommand> commands) { |
| // If there are no subcommands, do nothing. |
| if (commands.isEmpty) return ""; |
| |
| // Don't include aliases. |
| var names = 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); |
| if (visible.isNotEmpty) names = visible; |
| |
| // Show the commands alphabetically. |
| names = ordered(names); |
| var length = names.map((name) => name.length).reduce(math.max); |
| var isSubcommand = commands != mainCommands; |
| |
| var buffer = new StringBuffer(); |
| buffer.writeln('Available ${isSubcommand ? "sub" : ""}commands:'); |
| for (var name in names) { |
| buffer.writeln(' ${padRight(name, length)} ' |
| '${commands[name].description.split("\n").first}'); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| SystemCache cache; |
| |
| /// The parsed options for this command. |
| ArgResults get commandOptions => _commandOptions; |
| ArgResults _commandOptions; |
| |
| Entrypoint entrypoint; |
| |
| /// A one-line description of this command. |
| String get description; |
| |
| /// If the command is undocumented and should not appear in command listings, |
| /// this will be `true`. |
| bool get hidden { |
| // Leaf commands are visible by default. |
| if (subcommands.isEmpty) return false; |
| |
| // Otherwise, a command is hidden if all of its subcommands are. |
| return subcommands.values.every((subcommand) => subcommand.hidden); |
| } |
| |
| /// How to invoke this command (e.g. `"pub get [package]"`). |
| String get usage; |
| |
| /// Whether or not this command requires [entrypoint] to be defined. |
| /// |
| /// If false, pub won't look for a pubspec and [entrypoint] will be null when |
| /// the command runs. This only needs to be set in leaf commands. |
| bool get requiresEntrypoint => true; |
| |
| /// Whether or not this command takes arguments in addition to options. |
| /// |
| /// If false, pub will exit with an error if arguments are provided. This |
| /// only needs to be set in leaf commands. |
| bool get takesArguments => false; |
| |
| /// Alternate names for this command. These names won't be used in the |
| /// documentation, but they will work when invoked on the command line. |
| final aliases = const <String>[]; |
| |
| /// 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. This only needs to be set in leaf commands. |
| bool get isOffline => false; |
| |
| PubCommand() { |
| // Allow "--help" after a command to get command help. |
| commandParser.addFlag('help', abbr: 'h', negatable: false, |
| help: 'Print usage information for this command.'); |
| } |
| |
| /// Runs this command using a system cache at [cacheDir] with [options]. |
| Future run(String cacheDir, ArgResults options) { |
| _commandOptions = options; |
| |
| cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); |
| |
| if (requiresEntrypoint) { |
| // TODO(rnystrom): Will eventually need better logic to walk up |
| // subdirectories until we hit one that looks package-like. For now, |
| // just assume the cwd is it. |
| entrypoint = new Entrypoint(path.current, cache); |
| } |
| |
| return syncFuture(onRun); |
| } |
| |
| /// 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`. Only |
| /// leaf command should override this. |
| Future onRun() { |
| // Leaf commands should override this and non-leaf commands should never |
| // call it. |
| assert(false); |
| return null; |
| } |
| |
| /// Displays usage information for this command. |
| /// |
| /// If [description] is omitted, defaults to the command's description. |
| void printUsage([String description]) { |
| if (description == null) description = this.description; |
| log.message('$description\n\n${_getUsage()}'); |
| } |
| |
| // TODO(rnystrom): Use this in other places handle usage failures. |
| /// Throw a [UsageException] for a usage error of this command with |
| /// [message]. |
| void usageError(String message) { |
| throw new UsageException(message, _getUsage()); |
| } |
| |
| /// Throw a [DataException] with [message] to indicate that the command has |
| /// failed because of invalid input data. |
| /// |
| /// This will report the error and cause pub to exit with [exit_codes.DATA]. |
| void dataError(String message) { |
| throw new DataException(message); |
| } |
| |
| /// Parses a user-supplied integer [intString] named [name]. |
| /// |
| /// If the parsing fails, prints a usage message and exits. |
| int parseInt(String intString, String name) { |
| try { |
| return int.parse(intString); |
| } on FormatException catch (_) { |
| usageError('Could not parse $name "$intString".'); |
| } |
| } |
| |
| /// Generates a string of usage information for this command. |
| String _getUsage() { |
| var buffer = new StringBuffer(); |
| buffer.write('Usage: $usage'); |
| |
| var commandUsage = commandParser.getUsage(); |
| if (!commandUsage.isEmpty) { |
| buffer.writeln(); |
| buffer.writeln(commandUsage); |
| } |
| |
| if (subcommands.isNotEmpty) { |
| buffer.writeln(); |
| buffer.write(_listCommands(subcommands)); |
| } |
| |
| return buffer.toString(); |
| } |
| } |
| |
| _initCommands() { |
| var commands = { |
| 'build': new BuildCommand(), |
| 'cache': new CacheCommand(), |
| 'get': new GetCommand(), |
| 'help': new HelpCommand(), |
| 'deps': new DepsCommand(), |
| 'list-package-dirs': new ListPackageDirsCommand(), |
| 'publish': new LishCommand(), |
| 'serve': new ServeCommand(), |
| 'upgrade': new UpgradeCommand(), |
| 'uploader': new UploaderCommand(), |
| 'version': new VersionCommand() |
| }; |
| |
| for (var command in commands.values.toList()) { |
| for (var alias in command.aliases) { |
| commands[alias] = command; |
| } |
| } |
| |
| return commands; |
| } |
| |
| /// Creates the top-level [ArgParser] used to parse the pub command line. |
| ArgParser _initArgParser() { |
| var argParser = new ArgParser(); |
| |
| // Add the global options. |
| argParser.addFlag('help', abbr: 'h', negatable: false, |
| help: 'Print this usage information.'); |
| argParser.addFlag('version', negatable: false, |
| help: 'Print pub version.'); |
| argParser.addFlag('trace', |
| help: 'Print debugging information when an error occurs.'); |
| argParser.addOption('verbosity', |
| help: 'Control output verbosity.', |
| allowed: ['normal', 'io', 'solver', 'all'], |
| allowedHelp: { |
| 'normal': 'Show errors, warnings, and user messages.', |
| 'io': 'Also show IO operations.', |
| 'solver': 'Show steps during version resolution.', |
| 'all': 'Show all output including internal tracing messages.' |
| }); |
| argParser.addFlag('verbose', abbr: 'v', negatable: false, |
| help: 'Shortcut for "--verbosity=all".'); |
| |
| // Register the commands. |
| 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); |
| }); |
| } |