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 |