| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library pub.command; | 5 library pub.command; |
| 6 | 6 |
| 7 import 'dart:async'; | |
| 8 import 'dart:math' as math; | |
| 9 | |
| 10 import 'package:args/args.dart'; | 7 import 'package:args/args.dart'; |
| 8 import 'package:args/command_runner.dart'; |
| 11 import 'package:path/path.dart' as path; | 9 import 'package:path/path.dart' as path; |
| 12 | 10 |
| 13 import 'command/build.dart'; | |
| 14 import 'command/cache.dart'; | |
| 15 import 'command/deps.dart'; | |
| 16 import 'command/downgrade.dart'; | |
| 17 import 'command/get.dart'; | |
| 18 import 'command/global.dart'; | |
| 19 import 'command/help.dart'; | |
| 20 import 'command/lish.dart'; | |
| 21 import 'command/list_package_dirs.dart'; | |
| 22 import 'command/run.dart'; | |
| 23 import 'command/serve.dart'; | |
| 24 import 'command/upgrade.dart'; | |
| 25 import 'command/uploader.dart'; | |
| 26 import 'command/version.dart'; | |
| 27 import 'entrypoint.dart'; | 11 import 'entrypoint.dart'; |
| 28 import 'exceptions.dart'; | |
| 29 import 'log.dart' as log; | 12 import 'log.dart' as log; |
| 30 import 'global_packages.dart'; | 13 import 'global_packages.dart'; |
| 31 import 'system_cache.dart'; | 14 import 'system_cache.dart'; |
| 32 import 'utils.dart'; | |
| 33 | 15 |
| 34 /// The base class for commands for the pub executable. | 16 /// The base class for commands for the pub executable. |
| 35 /// | 17 /// |
| 36 /// A command may either be a "leaf" command or it may be a parent for a set | 18 /// A command may either be a "leaf" command or it may be a parent for a set |
| 37 /// of subcommands. Only leaf commands are ever actually invoked. If a command | 19 /// of subcommands. Only leaf commands are ever actually invoked. If a command |
| 38 /// has subcommands, then one of those must always be chosen. | 20 /// has subcommands, then one of those must always be chosen. |
| 39 abstract class PubCommand { | 21 abstract class PubCommand extends Command { |
| 40 /// The commands that pub understands. | 22 SystemCache get cache { |
| 41 static final Map<String, PubCommand> mainCommands = _initCommands(); | 23 if (_cache == null) { |
| 42 | 24 _cache = new SystemCache.withSources(isOffline: isOffline); |
| 43 /// The top-level [ArgParser] used to parse the pub command line. | 25 } |
| 44 static final pubArgParser = _initArgParser(); | 26 return _cache; |
| 45 | |
| 46 /// Displays usage information for the app. | |
| 47 static void printGlobalUsage() { | |
| 48 // Build up a buffer so it shows up as a single log entry. | |
| 49 var buffer = new StringBuffer(); | |
| 50 buffer.writeln('Pub is a package manager for Dart.'); | |
| 51 buffer.writeln(); | |
| 52 buffer.writeln('Usage: pub <command> [arguments]'); | |
| 53 buffer.writeln(); | |
| 54 buffer.writeln('Global options:'); | |
| 55 buffer.writeln(pubArgParser.getUsage()); | |
| 56 buffer.writeln(); | |
| 57 buffer.write(_listCommands(mainCommands)); | |
| 58 buffer.writeln(); | |
| 59 buffer.writeln( | |
| 60 'Run "pub help [command]" for more information about a command.'); | |
| 61 buffer.writeln( | |
| 62 'See http://dartlang.org/tools/pub for detailed documentation.'); | |
| 63 | |
| 64 log.message(buffer); | |
| 65 } | 27 } |
| 66 | |
| 67 /// Fails with a usage error [message] when trying to select from one of | |
| 68 /// [commands]. | |
| 69 static void usageErrorWithCommands(Map<String, PubCommand> commands, | |
| 70 String message) { | |
| 71 throw new UsageException(message, _listCommands(commands)); | |
| 72 } | |
| 73 | |
| 74 /// Writes [commands] in a nicely formatted list to [buffer]. | |
| 75 static String _listCommands(Map<String, PubCommand> commands) { | |
| 76 // If there are no subcommands, do nothing. | |
| 77 if (commands.isEmpty) return ""; | |
| 78 | |
| 79 // Don't include aliases. | |
| 80 var names = commands.keys | |
| 81 .where((name) => !commands[name].aliases.contains(name)); | |
| 82 | |
| 83 // Filter out hidden ones, unless they are all hidden. | |
| 84 var visible = names.where((name) => !commands[name].hidden); | |
| 85 if (visible.isNotEmpty) names = visible; | |
| 86 | |
| 87 // Show the commands alphabetically. | |
| 88 names = ordered(names); | |
| 89 var length = names.map((name) => name.length).reduce(math.max); | |
| 90 var isSubcommand = commands != mainCommands; | |
| 91 | |
| 92 var buffer = new StringBuffer(); | |
| 93 buffer.writeln('Available ${isSubcommand ? "sub" : ""}commands:'); | |
| 94 for (var name in names) { | |
| 95 buffer.writeln(' ${padRight(name, length)} ' | |
| 96 '${commands[name].description.split("\n").first}'); | |
| 97 } | |
| 98 | |
| 99 return buffer.toString(); | |
| 100 } | |
| 101 | |
| 102 SystemCache get cache => _cache; | |
| 103 SystemCache _cache; | 28 SystemCache _cache; |
| 104 | 29 |
| 105 GlobalPackages get globals => _globals; | 30 GlobalPackages get globals { |
| 31 if (_globals == null) { |
| 32 _globals = new GlobalPackages(cache); |
| 33 } |
| 34 return _globals; |
| 35 } |
| 106 GlobalPackages _globals; | 36 GlobalPackages _globals; |
| 107 | 37 |
| 108 /// The parsed options for the pub executable. | |
| 109 ArgResults get globalOptions => _globalOptions; | |
| 110 ArgResults _globalOptions; | |
| 111 | |
| 112 /// The parsed options for this command. | |
| 113 ArgResults get commandOptions => _commandOptions; | |
| 114 ArgResults _commandOptions; | |
| 115 | |
| 116 /// Gets the [Entrypoint] package for the current working directory. | 38 /// Gets the [Entrypoint] package for the current working directory. |
| 117 /// | 39 /// |
| 118 /// This will load the pubspec and fail with an error if the current directory | 40 /// This will load the pubspec and fail with an error if the current directory |
| 119 /// is not a package. | 41 /// is not a package. |
| 120 Entrypoint get entrypoint { | 42 Entrypoint get entrypoint { |
| 121 // Lazy load it. | 43 // Lazy load it. |
| 122 if (_entrypoint == null) { | 44 if (_entrypoint == null) { |
| 123 _entrypoint = new Entrypoint(path.current, _cache, | 45 _entrypoint = new Entrypoint(path.current, cache, |
| 124 packageSymlinks: globalOptions['package-symlinks']); | 46 packageSymlinks: globalResults['package-symlinks']); |
| 125 } | 47 } |
| 126 return _entrypoint; | 48 return _entrypoint; |
| 127 } | 49 } |
| 128 | |
| 129 Entrypoint _entrypoint; | 50 Entrypoint _entrypoint; |
| 130 | 51 |
| 131 /// A one-line description of this command. | |
| 132 String get description; | |
| 133 | |
| 134 /// If the command is undocumented and should not appear in command listings, | |
| 135 /// this will be `true`. | |
| 136 bool get hidden { | |
| 137 // Leaf commands are visible by default. | |
| 138 if (subcommands.isEmpty) return false; | |
| 139 | |
| 140 // Otherwise, a command is hidden if all of its subcommands are. | |
| 141 return subcommands.values.every((subcommand) => subcommand.hidden); | |
| 142 } | |
| 143 | |
| 144 /// How to invoke this command (e.g. `"pub get [package]"`). | |
| 145 String get usage; | |
| 146 | |
| 147 /// The URL for web documentation for this command. | 52 /// The URL for web documentation for this command. |
| 148 String get docUrl => null; | 53 String get docUrl => null; |
| 149 | 54 |
| 150 /// Whether or not this command takes arguments in addition to options. | |
| 151 /// | |
| 152 /// If false, pub will exit with an error if arguments are provided. This | |
| 153 /// only needs to be set in leaf commands. | |
| 154 bool get takesArguments => false; | |
| 155 | |
| 156 /// Override this and return `false` to disallow trailing options from being | 55 /// Override this and return `false` to disallow trailing options from being |
| 157 /// parsed after a non-option argument is parsed. | 56 /// parsed after a non-option argument is parsed. |
| 158 bool get allowTrailingOptions => true; | 57 bool get allowTrailingOptions => true; |
| 159 | 58 |
| 160 /// Alternate names for this command. | 59 ArgParser get argParser { |
| 161 /// | 60 // Lazily initialize the parser because the superclass constructor requires |
| 162 /// These names won't be used in the documentation, but they will work when | 61 // it but we want to initialize it based on [allowTrailingOptions]. |
| 163 /// invoked on the command line. | 62 if (_argParser == null) { |
| 164 final aliases = const <String>[]; | 63 _argParser = new ArgParser(allowTrailingOptions: allowTrailingOptions); |
| 165 | 64 } |
| 166 /// The [ArgParser] for this command. | 65 return _argParser; |
| 167 ArgParser get commandParser => _commandParser; | 66 } |
| 168 ArgParser _commandParser; | 67 ArgParser _argParser; |
| 169 | |
| 170 /// Subcommands exposed by this command. | |
| 171 /// | |
| 172 /// If empty, then this command has no subcommands. Otherwise, a subcommand | |
| 173 /// must be specified by the user. In that case, this command's [onRun] will | |
| 174 /// not be called and the subcommand's will. | |
| 175 final subcommands = <String, PubCommand>{}; | |
| 176 | 68 |
| 177 /// Override this to use offline-only sources instead of hitting the network. | 69 /// Override this to use offline-only sources instead of hitting the network. |
| 178 /// | 70 /// |
| 179 /// This will only be called before the [SystemCache] is created. After that, | 71 /// This will only be called before the [SystemCache] is created. After that, |
| 180 /// it has no effect. This only needs to be set in leaf commands. | 72 /// it has no effect. This only needs to be set in leaf commands. |
| 181 bool get isOffline => false; | 73 bool get isOffline => false; |
| 182 | 74 |
| 183 PubCommand() { | 75 String get usageFooter { |
| 184 _commandParser = new ArgParser(allowTrailingOptions: allowTrailingOptions); | 76 if (docUrl == null) return null; |
| 185 | 77 return "See $docUrl for detailed documentation."; |
| 186 // Allow "--help" after a command to get command help. | |
| 187 commandParser.addFlag('help', abbr: 'h', negatable: false, | |
| 188 help: 'Print usage information for this command.'); | |
| 189 } | 78 } |
| 190 | 79 |
| 191 /// Runs this command using a system cache at [cacheDir] with [globalOptions] | 80 void printUsage() { |
| 192 /// and [options]. | 81 log.message(usage); |
| 193 Future run(String cacheDir, ArgResults globalOptions, ArgResults options) { | |
| 194 _globalOptions = globalOptions; | |
| 195 _commandOptions = options; | |
| 196 | |
| 197 _cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); | |
| 198 _globals = new GlobalPackages(_cache); | |
| 199 | |
| 200 return new Future.sync(onRun); | |
| 201 } | |
| 202 | |
| 203 /// Override this to perform the specific command. | |
| 204 /// | |
| 205 /// Return a future that completes when the command is done or fails if the | |
| 206 /// command fails. If the command is synchronous, it may return `null`. Only | |
| 207 /// leaf command should override this. | |
| 208 Future onRun() { | |
| 209 // Leaf commands should override this and non-leaf commands should never | |
| 210 // call it. | |
| 211 assert(false); | |
| 212 return null; | |
| 213 } | |
| 214 | |
| 215 /// Displays usage information for this command. | |
| 216 /// | |
| 217 /// If [description] is omitted, defaults to the command's description. | |
| 218 void printUsage([String description]) { | |
| 219 if (description == null) description = this.description; | |
| 220 log.message('$description\n\n${_getUsage()}'); | |
| 221 } | |
| 222 | |
| 223 /// Throw a [UsageException] for a usage error of this command with | |
| 224 /// [message]. | |
| 225 void usageError(String message) { | |
| 226 throw new UsageException(message, _getUsage()); | |
| 227 } | 82 } |
| 228 | 83 |
| 229 /// Parses a user-supplied integer [intString] named [name]. | 84 /// Parses a user-supplied integer [intString] named [name]. |
| 230 /// | 85 /// |
| 231 /// If the parsing fails, prints a usage message and exits. | 86 /// If the parsing fails, prints a usage message and exits. |
| 232 int parseInt(String intString, String name) { | 87 int parseInt(String intString, String name) { |
| 233 try { | 88 try { |
| 234 return int.parse(intString); | 89 return int.parse(intString); |
| 235 } on FormatException catch (_) { | 90 } on FormatException catch (_) { |
| 236 usageError('Could not parse $name "$intString".'); | 91 usageException('Could not parse $name "$intString".'); |
| 237 } | 92 } |
| 238 } | 93 } |
| 239 | |
| 240 /// Generates a string of usage information for this command. | |
| 241 String _getUsage() { | |
| 242 var buffer = new StringBuffer(); | |
| 243 buffer.write('Usage: $usage'); | |
| 244 | |
| 245 var commandUsage = commandParser.getUsage(); | |
| 246 if (!commandUsage.isEmpty) { | |
| 247 buffer.writeln(); | |
| 248 buffer.writeln(commandUsage); | |
| 249 } | |
| 250 | |
| 251 if (subcommands.isNotEmpty) { | |
| 252 buffer.writeln(); | |
| 253 buffer.write(_listCommands(subcommands)); | |
| 254 } | |
| 255 | |
| 256 buffer.writeln(); | |
| 257 buffer.writeln('Run "pub help" to see global options.'); | |
| 258 if (docUrl != null) { | |
| 259 buffer.writeln("See $docUrl for detailed documentation."); | |
| 260 } | |
| 261 | |
| 262 return buffer.toString(); | |
| 263 } | |
| 264 } | 94 } |
| 265 | |
| 266 _initCommands() { | |
| 267 var commands = { | |
| 268 'build': new BuildCommand(), | |
| 269 'cache': new CacheCommand(), | |
| 270 'deps': new DepsCommand(), | |
| 271 'downgrade': new DowngradeCommand(), | |
| 272 'global': new GlobalCommand(), | |
| 273 'get': new GetCommand(), | |
| 274 'help': new HelpCommand(), | |
| 275 'list-package-dirs': new ListPackageDirsCommand(), | |
| 276 'publish': new LishCommand(), | |
| 277 'run': new RunCommand(), | |
| 278 'serve': new ServeCommand(), | |
| 279 'upgrade': new UpgradeCommand(), | |
| 280 'uploader': new UploaderCommand(), | |
| 281 'version': new VersionCommand() | |
| 282 }; | |
| 283 | |
| 284 for (var command in commands.values.toList()) { | |
| 285 for (var alias in command.aliases) { | |
| 286 commands[alias] = command; | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 return commands; | |
| 291 } | |
| 292 | |
| 293 /// Creates the top-level [ArgParser] used to parse the pub command line. | |
| 294 ArgParser _initArgParser() { | |
| 295 var argParser = new ArgParser(allowTrailingOptions: true); | |
| 296 | |
| 297 // Add the global options. | |
| 298 argParser.addFlag('help', abbr: 'h', negatable: false, | |
| 299 help: 'Print this usage information.'); | |
| 300 argParser.addFlag('version', negatable: false, | |
| 301 help: 'Print pub version.'); | |
| 302 argParser.addFlag('trace', | |
| 303 help: 'Print debugging information when an error occurs.'); | |
| 304 argParser.addOption('verbosity', | |
| 305 help: 'Control output verbosity.', | |
| 306 allowed: ['normal', 'io', 'solver', 'all'], | |
| 307 allowedHelp: { | |
| 308 'normal': 'Show errors, warnings, and user messages.', | |
| 309 'io': 'Also show IO operations.', | |
| 310 'solver': 'Show steps during version resolution.', | |
| 311 'all': 'Show all output including internal tracing messages.' | |
| 312 }); | |
| 313 argParser.addFlag('verbose', abbr: 'v', negatable: false, | |
| 314 help: 'Shortcut for "--verbosity=all".'); | |
| 315 argParser.addFlag('with-prejudice', hide: !isAprilFools, negatable: false, | |
| 316 help: 'Execute commands with prejudice.'); | |
| 317 argParser.addFlag('package-symlinks', hide: true, negatable: true, | |
| 318 defaultsTo: true); | |
| 319 | |
| 320 // Register the commands. | |
| 321 PubCommand.mainCommands.forEach((name, command) { | |
| 322 _registerCommand(name, command, argParser); | |
| 323 }); | |
| 324 | |
| 325 return argParser; | |
| 326 } | |
| 327 | |
| 328 /// Registers a [command] with [name] on [parser]. | |
| 329 void _registerCommand(String name, PubCommand command, ArgParser parser) { | |
| 330 parser.addCommand(name, command.commandParser); | |
| 331 | |
| 332 // Recursively wire up any subcommands. | |
| 333 command.subcommands.forEach((name, subcommand) { | |
| 334 _registerCommand(name, subcommand, command.commandParser); | |
| 335 }); | |
| 336 } | |
| OLD | NEW |