Chromium Code Reviews| 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'; | 7 import 'dart:async'; |
| 8 import 'dart:io'; | |
| 9 import 'dart:math' as math; | 8 import 'dart:math' as math; |
| 10 | 9 |
| 11 import 'package:args/args.dart'; | 10 import 'package:args/args.dart'; |
| 12 import 'package:path/path.dart' as path; | 11 import 'package:path/path.dart' as path; |
| 13 import 'package:stack_trace/stack_trace.dart'; | |
| 14 | 12 |
| 15 import 'command/build.dart'; | 13 import 'command/build.dart'; |
| 16 import 'command/cache.dart'; | 14 import 'command/cache.dart'; |
| 17 import 'command/get.dart'; | 15 import 'command/get.dart'; |
| 18 import 'command/help.dart'; | 16 import 'command/help.dart'; |
| 19 import 'command/lish.dart'; | 17 import 'command/lish.dart'; |
| 20 import 'command/list_package_dirs.dart'; | 18 import 'command/list_package_dirs.dart'; |
| 21 import 'command/serve.dart'; | 19 import 'command/serve.dart'; |
| 22 import 'command/upgrade.dart'; | 20 import 'command/upgrade.dart'; |
| 23 import 'command/uploader.dart'; | 21 import 'command/uploader.dart'; |
| 24 import 'command/version.dart'; | 22 import 'command/version.dart'; |
| 25 import 'entrypoint.dart'; | 23 import 'entrypoint.dart'; |
| 26 import 'exit_codes.dart' as exit_codes; | |
| 27 import 'http.dart'; | |
| 28 import 'io.dart'; | |
| 29 import 'log.dart' as log; | 24 import 'log.dart' as log; |
| 30 import 'system_cache.dart'; | 25 import 'system_cache.dart'; |
| 31 import 'utils.dart'; | 26 import 'utils.dart'; |
| 32 | 27 |
| 33 /// The base class for commands for the pub executable. | 28 /// The base class for commands for the pub executable. |
| 29 /// | |
| 30 /// A command may either be a "leaf" command or it may be a parent for a set | |
| 31 /// of subcommands. Only leaf commands are ever actually invoked. If a command | |
| 32 /// has subcommands, then one of those must always be chosen. | |
| 34 abstract class PubCommand { | 33 abstract class PubCommand { |
| 35 /// The commands that pub understands. | 34 /// The commands that pub understands. |
| 36 static final Map<String, PubCommand> commands = _initCommands(); | 35 static final Map<String, PubCommand> mainCommands = _initCommands(); |
| 37 | 36 |
| 38 /// The top-level [ArgParser] used to parse the pub command line. | 37 /// The top-level [ArgParser] used to parse the pub command line. |
| 39 static final pubArgParser = _initArgParser(); | 38 static final pubArgParser = _initArgParser(); |
| 40 | 39 |
| 41 /// Displays usage information for the app. | 40 /// Displays usage information for the app. |
| 42 static void printGlobalUsage() { | 41 static void printGlobalUsage() { |
| 43 // Build up a buffer so it shows up as a single log entry. | 42 // Build up a buffer so it shows up as a single log entry. |
| 44 var buffer = new StringBuffer(); | 43 var buffer = new StringBuffer(); |
| 45 buffer.writeln('Pub is a package manager for Dart.'); | 44 buffer.writeln('Pub is a package manager for Dart.'); |
| 46 buffer.writeln(); | 45 buffer.writeln(); |
| 47 buffer.writeln('Usage: pub command [arguments]'); | 46 buffer.writeln('Usage: pub <command> [arguments]'); |
| 48 buffer.writeln(); | 47 buffer.writeln(); |
| 49 buffer.writeln('Global options:'); | 48 buffer.writeln('Global options:'); |
| 50 buffer.writeln(pubArgParser.getUsage()); | 49 buffer.writeln(pubArgParser.getUsage()); |
| 50 buffer.write(_listCommands(mainCommands)); | |
| 51 buffer.writeln(); | 51 buffer.writeln(); |
| 52 buffer.writeln( | |
| 53 'Use "pub help [command]" for more information about a command.'); | |
| 52 | 54 |
| 55 log.message(buffer); | |
| 56 } | |
| 57 | |
| 58 /// Fails with a usage error [message] when trying to select from one of | |
| 59 /// [commands]. | |
| 60 static void commandUsageError(Map<String, PubCommand> commands, | |
|
nweiz
2014/02/04 01:24:29
"PubCommand.commandUsageError" reads weird, both b
Bob Nystrom
2014/02/06 00:06:31
Done.
| |
| 61 String message) { | |
| 62 throw new UsageException("$message\n${_listCommands(commands)}"); | |
| 63 } | |
| 64 | |
| 65 /// Writes [commands] in a nicely formatted list to [buffer]. | |
| 66 /// | |
| 67 /// If [isSubcommand] is `true`, then the list will be labelled as | |
| 68 /// "subcommands", otherwise they are labelled "commands". | |
|
nweiz
2014/02/04 01:24:29
[isSubcommand] isn't passed anymore.
Bob Nystrom
2014/02/06 00:06:31
Done.
| |
| 69 static String _listCommands(Map<String, PubCommand> commands) { | |
| 53 // Show the public commands alphabetically. | 70 // Show the public commands alphabetically. |
| 54 var names = ordered(commands.keys.where((name) => | 71 var names = ordered(commands.keys.where((name) => |
| 55 !commands[name].aliases.contains(name) && | 72 !commands[name].aliases.contains(name) && |
| 56 !commands[name].hidden)); | 73 !commands[name].hidden)); |
| 57 | 74 |
| 75 // If all of the subcommands are hidden, do nothing. | |
| 76 // TODO(rnystrom): Remove this once cache has a visible command. | |
| 77 if (names.isEmpty) return ""; | |
| 78 | |
| 58 var length = names.map((name) => name.length).reduce(math.max); | 79 var length = names.map((name) => name.length).reduce(math.max); |
| 59 | 80 |
| 60 buffer.writeln('Available commands:'); | 81 var isSubcommand = commands != mainCommands; |
| 82 | |
| 83 var buffer = new StringBuffer(); | |
| 84 buffer.writeln(); | |
| 85 buffer.writeln('Available ${isSubcommand ? "sub" : ""}commands:'); | |
| 61 for (var name in names) { | 86 for (var name in names) { |
| 62 buffer.writeln(' ${padRight(name, length)} ' | 87 buffer.writeln(' ${padRight(name, length)} ' |
| 63 '${commands[name].description}'); | 88 '${commands[name].description}'); |
| 64 } | 89 } |
| 65 | 90 |
| 66 buffer.writeln(); | 91 return buffer.toString(); |
| 67 buffer.write( | |
| 68 'Use "pub help [command]" for more information about a command.'); | |
| 69 log.message(buffer.toString()); | |
| 70 } | 92 } |
| 71 | 93 |
| 72 SystemCache cache; | 94 SystemCache cache; |
| 73 | 95 |
| 74 /// The parsed options for this command. | 96 /// The parsed options for this command. |
| 75 ArgResults commandOptions; | 97 ArgResults get commandOptions => _commandOptions; |
| 98 ArgResults _commandOptions; | |
| 76 | 99 |
| 77 Entrypoint entrypoint; | 100 Entrypoint entrypoint; |
| 78 | 101 |
| 79 /// A one-line description of this command. | 102 /// A one-line description of this command. |
| 80 String get description; | 103 String get description; |
| 81 | 104 |
| 82 /// If the command is undocumented and should not appear in command listings, | 105 /// If the command is undocumented and should not appear in command listings, |
| 83 /// this will be `true`. | 106 /// this will be `true`. |
| 84 bool get hidden => false; | 107 bool get hidden => false; |
| 85 | 108 |
| 86 /// How to invoke this command (e.g. `"pub get [package]"`). | 109 /// How to invoke this command (e.g. `"pub get [package]"`). |
| 87 String get usage; | 110 String get usage; |
| 88 | 111 |
| 89 /// Whether or not this command requires [entrypoint] to be defined. If false, | 112 /// Whether or not this command requires [entrypoint] to be defined. |
| 90 /// pub won't look for a pubspec and [entrypoint] will be null when the | 113 /// |
| 91 /// command runs. | 114 /// If false, pub won't look for a pubspec and [entrypoint] will be null when |
| 115 /// the command runs. This only needs to be set in leaf commands. | |
| 92 bool get requiresEntrypoint => true; | 116 bool get requiresEntrypoint => true; |
| 93 | 117 |
| 94 /// Whether or not this command takes arguments in addition to options. If | 118 /// Whether or not this command takes arguments in addition to options. |
| 95 /// false, pub will exit with an error if arguments are provided. | 119 /// |
| 120 /// If false, pub will exit with an error if arguments are provided. This | |
| 121 /// only needs to be set in leaf commands. | |
| 96 bool get takesArguments => false; | 122 bool get takesArguments => false; |
| 97 | 123 |
| 98 /// Alternate names for this command. These names won't be used in the | 124 /// Alternate names for this command. These names won't be used in the |
| 99 /// documentation, but they will work when invoked on the command line. | 125 /// documentation, but they will work when invoked on the command line. |
| 100 final aliases = const <String>[]; | 126 final aliases = const <String>[]; |
| 101 | 127 |
| 102 /// The [ArgParser] for this command. | 128 /// The [ArgParser] for this command. |
| 103 final commandParser = new ArgParser(); | 129 final commandParser = new ArgParser(); |
| 104 | 130 |
| 131 /// Subcommands exposed by this command. | |
| 132 /// | |
| 133 /// If empty, then this command has no subcommands. Otherwise, a subcommand | |
| 134 /// must be specified by the user. In that case, this command's [onRun] will | |
| 135 /// not be called and the subcommand's will. | |
| 136 final subcommands = <String, PubCommand>{}; | |
| 137 | |
| 105 /// Override this to use offline-only sources instead of hitting the network. | 138 /// Override this to use offline-only sources instead of hitting the network. |
| 139 /// | |
| 106 /// This will only be called before the [SystemCache] is created. After that, | 140 /// This will only be called before the [SystemCache] is created. After that, |
| 107 /// it has no effect. | 141 /// it has no effect. This only needs to be set in leaf commands. |
| 108 bool get isOffline => false; | 142 bool get isOffline => false; |
| 109 | 143 |
| 110 PubCommand() { | 144 PubCommand() { |
| 111 // Allow "--help" after a command to get command help. | 145 // Allow "--help" after a command to get command help. |
| 112 commandParser.addFlag('help', abbr: 'h', negatable: false, | 146 commandParser.addFlag('help', abbr: 'h', negatable: false, |
| 113 help: 'Print usage information for this command.'); | 147 help: 'Print usage information for this command.'); |
| 114 } | 148 } |
| 115 | 149 |
| 116 void run(String cacheDir, ArgResults options, List<String> arguments) { | 150 /// Runs this command using a system cache at [cacheDir] with [options]. |
| 117 commandOptions = options.command; | 151 Future run(String cacheDir, ArgResults options) { |
|
nweiz
2014/02/04 01:24:29
What happened to mainOptions/rootOptions? Subcomma
Bob Nystrom
2014/02/06 00:06:31
I took it out because it wasn't being used. We pro
| |
| 118 | 152 _commandOptions = options; |
| 119 if (commandOptions['help']) { | |
| 120 this.printUsage(); | |
| 121 return; | |
| 122 } | |
| 123 | 153 |
| 124 cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); | 154 cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); |
| 125 | 155 |
| 126 handleError(error, Chain chain) { | 156 if (requiresEntrypoint) { |
| 127 // This is basically the top-level exception handler so that we don't | 157 // TODO(rnystrom): Will eventually need better logic to walk up |
| 128 // spew a stack trace on our users. | 158 // subdirectories until we hit one that looks package-like. For now, |
| 129 var message; | 159 // just assume the cwd is it. |
| 130 | 160 entrypoint = new Entrypoint(path.current, cache); |
| 131 log.error(getErrorMessage(error)); | |
| 132 log.fine("Exception type: ${error.runtimeType}"); | |
| 133 | |
| 134 if (options['trace'] || !isUserFacingException(error)) { | |
| 135 log.error(chain.terse); | |
| 136 } else { | |
| 137 log.fine(chain.terse); | |
| 138 } | |
| 139 | |
| 140 if (error is ApplicationException && error.innerError != null) { | |
| 141 var message = "Wrapped exception: ${error.innerError}"; | |
| 142 if (error.innerTrace != null) message = "$message\n${error.innerTrace}"; | |
| 143 log.fine(message); | |
| 144 } | |
| 145 | |
| 146 if (options['trace']) { | |
| 147 log.dumpTranscript(); | |
| 148 } else if (!isUserFacingException(error)) { | |
| 149 log.error(""" | |
| 150 This is an unexpected error. Please run | |
| 151 | |
| 152 pub --trace ${arguments.map((arg) => "'$arg'").join(' ')} | |
| 153 | |
| 154 and include the results in a bug report on http://dartbug.com/new. | |
| 155 """); | |
| 156 } | |
| 157 | |
| 158 return flushThenExit(_chooseExitCode(error)); | |
| 159 } | 161 } |
| 160 | 162 |
| 161 var captureStackChains = | 163 var commandFuture = onRun(); |
| 162 options['trace'] || options['verbose'] || options['verbosity'] == 'all'; | 164 if (commandFuture == null) return null; |
|
nweiz
2014/02/04 01:24:29
Why not just "return syncFuture(onRun)"?
Bob Nystrom
2014/02/06 00:06:31
Done, though that behavior is a bit different. But
| |
| 163 captureErrors(() { | |
| 164 return syncFuture(() { | |
| 165 // Make sure there aren't unexpected arguments. | |
| 166 if (!takesArguments && commandOptions.rest.isNotEmpty) { | |
| 167 log.error('Command "${commandOptions.name}" does not take any ' | |
| 168 'arguments.'); | |
| 169 this.printUsage(); | |
| 170 return flushThenExit(exit_codes.USAGE); | |
| 171 } | |
| 172 | 165 |
| 173 if (requiresEntrypoint) { | 166 return commandFuture; |
| 174 // TODO(rnystrom): Will eventually need better logic to walk up | |
| 175 // subdirectories until we hit one that looks package-like. For now, | |
| 176 // just assume the cwd is it. | |
| 177 entrypoint = new Entrypoint(path.current, cache); | |
| 178 } | |
| 179 | |
| 180 var commandFuture = onRun(); | |
| 181 if (commandFuture == null) return true; | |
| 182 | |
| 183 return commandFuture; | |
| 184 }).whenComplete(() => cache.deleteTempDir()); | |
| 185 }, captureStackChains: captureStackChains).catchError(handleError) | |
| 186 .then((_) { | |
| 187 // Explicitly exit on success to ensure that any dangling dart:io handles | |
| 188 // don't cause the process to never terminate. | |
| 189 return flushThenExit(exit_codes.SUCCESS); | |
| 190 }); | |
| 191 } | 167 } |
| 192 | 168 |
| 193 /// Override this to perform the specific command. Return a future that | 169 /// Override this to perform the specific command. |
| 194 /// completes when the command is done or fails if the command fails. If the | 170 /// |
| 195 /// command is synchronous, it may return `null`. | 171 /// Return a future that completes when the command is done or fails if the |
| 196 Future onRun(); | 172 /// command fails. If the command is synchronous, it may return `null`. Only |
| 173 /// lead command should override this. | |
|
nweiz
2014/02/04 01:24:29
"lead" -> "leaf".
Bob Nystrom
2014/02/06 00:06:31
Done.
| |
| 174 Future onRun() { | |
| 175 // Leaf commands should override this and non-leaf commands should never | |
| 176 // call it. | |
| 177 assert(false); | |
| 178 } | |
| 197 | 179 |
| 198 /// Displays usage information for this command. | 180 /// Displays usage information for this command. |
| 199 void printUsage([String description]) { | 181 void printUsage([String description]) { |
| 200 if (description == null) description = this.description; | 182 if (description == null) description = this.description; |
| 183 log.message('$description\n\n${_getUsage()}'); | |
| 184 } | |
| 201 | 185 |
| 186 /// Generates a string of usage information for this command. | |
| 187 String _getUsage() { | |
| 202 var buffer = new StringBuffer(); | 188 var buffer = new StringBuffer(); |
| 203 buffer.write('$description\n\nUsage: $usage'); | 189 buffer.write('Usage: $usage'); |
| 204 | 190 |
| 205 var commandUsage = commandParser.getUsage(); | 191 var commandUsage = commandParser.getUsage(); |
| 206 if (!commandUsage.isEmpty) { | 192 if (!commandUsage.isEmpty) { |
| 207 buffer.write('\n'); | 193 buffer.write('\n'); |
| 208 buffer.write(commandUsage); | 194 buffer.write(commandUsage); |
| 209 } | 195 } |
| 210 | 196 |
| 211 log.message(buffer.toString()); | 197 if (subcommands.isNotEmpty) { |
| 198 buffer.writeln(); | |
| 199 buffer.write(_listCommands(subcommands)); | |
| 200 } | |
| 201 | |
| 202 return buffer.toString(); | |
| 212 } | 203 } |
|
nweiz
2014/02/04 01:24:29
Nit: it reads weird to me to have this defined bet
Bob Nystrom
2014/02/06 00:06:31
Done.
| |
| 213 | 204 |
| 214 /// Returns the appropriate exit code for [exception], falling back on 1 if no | 205 // TODO(rnystrom): Use this in other places handle usage failures. |
| 215 /// appropriate exit code could be found. | 206 /// Throw an [ApplicationException] for a usage error of this command with |
| 216 int _chooseExitCode(exception) { | 207 /// [message]. |
| 217 if (exception is HttpException || exception is HttpException || | 208 void usageError(String message) { |
| 218 exception is SocketException || exception is PubHttpException) { | 209 throw new UsageException("$message\n\n${_getUsage()}"); |
| 219 return exit_codes.UNAVAILABLE; | |
| 220 } else if (exception is FormatException) { | |
| 221 return exit_codes.DATA; | |
| 222 } else { | |
| 223 return 1; | |
| 224 } | |
| 225 } | 210 } |
| 226 } | 211 } |
| 227 | 212 |
| 228 _initCommands() { | 213 _initCommands() { |
| 229 var commands = { | 214 var commands = { |
| 230 'build': new BuildCommand(), | 215 'build': new BuildCommand(), |
| 231 'cache': new CacheCommand(), | 216 'cache': new CacheCommand(), |
| 232 'get': new GetCommand(), | 217 'get': new GetCommand(), |
| 233 'help': new HelpCommand(), | 218 'help': new HelpCommand(), |
| 234 'list-package-dirs': new ListPackageDirsCommand(), | 219 'list-package-dirs': new ListPackageDirsCommand(), |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 265 allowedHelp: { | 250 allowedHelp: { |
| 266 'normal': 'Show errors, warnings, and user messages.', | 251 'normal': 'Show errors, warnings, and user messages.', |
| 267 'io': 'Also show IO operations.', | 252 'io': 'Also show IO operations.', |
| 268 'solver': 'Show steps during version resolution.', | 253 'solver': 'Show steps during version resolution.', |
| 269 'all': 'Show all output including internal tracing messages.' | 254 'all': 'Show all output including internal tracing messages.' |
| 270 }); | 255 }); |
| 271 argParser.addFlag('verbose', abbr: 'v', negatable: false, | 256 argParser.addFlag('verbose', abbr: 'v', negatable: false, |
| 272 help: 'Shortcut for "--verbosity=all".'); | 257 help: 'Shortcut for "--verbosity=all".'); |
| 273 | 258 |
| 274 // Register the commands. | 259 // Register the commands. |
| 275 PubCommand.commands.forEach((name, command) { | 260 PubCommand.mainCommands.forEach((name, command) { |
| 276 argParser.addCommand(name, command.commandParser); | 261 _registerCommand(name, command, argParser); |
| 277 }); | 262 }); |
| 278 | 263 |
| 279 return argParser; | 264 return argParser; |
| 280 } | 265 } |
| 266 | |
| 267 /// Registers a [command] with [name] on [parser]. | |
| 268 void _registerCommand(String name, PubCommand command, ArgParser parser) { | |
| 269 parser.addCommand(name, command.commandParser); | |
| 270 | |
| 271 // Recursively wire up any subcommands. | |
| 272 command.subcommands.forEach((name, subcommand) { | |
| 273 _registerCommand(name, subcommand, command.commandParser); | |
| 274 }); | |
| 275 } | |
| OLD | NEW |