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 /** | |
6 * Parser support for transforming raw command-line arguments into a set | |
7 * of options and values. | |
8 * | |
9 * This library supports [GNU][] and [POSIX][] style options, and it works | |
10 * in both server-side and client-side apps. | |
11 * | |
12 * For information on installing this library, see the | |
13 * [args package on pub.dartlang.org](http://pub.dartlang.org/packages/args). | |
14 * Here's an example of importing this library: | |
15 * | |
16 * import 'package:args/args.dart'; | |
17 * | |
18 * ## Defining options | |
19 * | |
20 * To use this library, first create an [ArgParser]: | |
21 * | |
22 * var parser = new ArgParser(); | |
23 * | |
24 * Then define a set of options on that parser using [addOption()] and | |
25 * [addFlag()]. Here's the minimal way to create an option named "name": | |
26 * | |
27 * parser.addOption('name'); | |
28 * | |
29 * When an option can only be set or unset (as opposed to taking a string | |
30 * value), use a flag: | |
31 * | |
32 * parser.addFlag('name'); | |
33 * | |
34 * Flag options, by default, accept a 'no-' prefix to negate the option. | |
35 * You can disable the 'no-' prefix using the `negatable` parameter: | |
36 * | |
37 * parser.addFlag('name', negatable: false); | |
38 * | |
39 * **Terminology note:** | |
40 * From here on out, the term _option_ refers to both regular options and | |
41 * flags. In cases where the distinction matters, this documentation uses | |
42 * the term _non-flag option._ | |
43 * | |
44 * Options can have an optional single-character abbreviation, specified | |
45 * with the `abbr` parameter: | |
46 * | |
47 * parser.addOption('mode', abbr: 'm'); | |
48 * parser.addFlag('verbose', abbr: 'v'); | |
49 * | |
50 * Options can also have a default value, specified with the `defaultsTo` | |
51 * parameter. The default value is used when arguments don't specify the | |
52 * option. | |
53 * | |
54 * parser.addOption('mode', defaultsTo: 'debug'); | |
55 * parser.addFlag('verbose', defaultsTo: false); | |
56 * | |
57 * The default value for non-flag options can be any [String]. For flags, | |
58 * it must be a [bool]. | |
59 * | |
60 * To validate a non-flag option, you can use the `allowed` parameter to | |
61 * provide an allowed set of values. When you do, the parser throws a | |
62 * [FormatException] if the value for an option is not in the allowed set. | |
63 * Here's an example of specifying allowed values: | |
64 * | |
65 * parser.addOption('mode', allowed: ['debug', 'release']); | |
66 * | |
67 * You can use the `callback` parameter to associate a function with an | |
68 * option. Later, when parsing occurs, the callback function is invoked | |
69 * with the value of the option: | |
70 * | |
71 * parser.addOption('mode', callback: (mode) => print('Got mode $mode)); | |
72 * parser.addFlag('verbose', callback: (verbose) { | |
73 * if (verbose) print('Verbose'); | |
74 * }); | |
75 * | |
76 * The callbacks for all options are called whenever a set of arguments | |
77 * is parsed. If an option isn't provided in the args, its callback is | |
78 * passed the default value, or `null` if no default value is set. | |
79 * | |
80 * ## Parsing arguments | |
81 * | |
82 * Once you have an [ArgParser] set up with some options and flags, you | |
83 * use it by calling [ArgParser.parse()] with a set of arguments: | |
84 * | |
85 * var results = parser.parse(['some', 'command', 'line', 'args']); | |
86 * | |
87 * These arguments usually come from the arguments to main | |
88 * (`main(List<String> arguments`), but you can pass in any list of strings. | |
89 * The parse() method returns an instance of [ArgResults], a map-like | |
90 * object that contains the values of the parsed options. | |
91 * | |
92 * var parser = new ArgParser(); | |
93 * parser.addOption('mode'); | |
94 * parser.addFlag('verbose', defaultsTo: true); | |
95 * var results = parser.parse(['--mode', 'debug', 'something', 'else']); | |
96 * | |
97 * print(results['mode']); // debug | |
98 * print(results['verbose']); // true | |
99 * | |
100 * By default, the parse() method stops as soon as it reaches `--` by itself | |
101 * or anything that the parser doesn't recognize as an option, flag, or | |
102 * option value. If arguments still remain, they go into [ArgResults.rest]. | |
103 * | |
104 * print(results.rest); // ['something', 'else'] | |
105 * | |
106 * To continue to parse options found after non-option arguments, call | |
107 * parse() with `allowTrailingOptions: true`. | |
108 * | |
109 * ## Specifying options | |
110 * | |
111 * To actually pass in options and flags on the command line, use GNU or | |
112 * POSIX style. Consider this option: | |
113 * | |
114 * parser.addOption('name', abbr: 'n'); | |
115 * | |
116 * You can specify its value on the command line using any of the following: | |
117 * | |
118 * --name=somevalue | |
119 * --name somevalue | |
120 * -nsomevalue | |
121 * -n somevalue | |
122 * | |
123 * Consider this flag: | |
124 * | |
125 * parser.addFlag('name', abbr: 'n'); | |
126 * | |
127 * You can set it to true using one of the following: | |
128 * | |
129 * --name | |
130 * -n | |
131 * | |
132 * You can set it to false using the following: | |
133 * | |
134 * --no-name | |
135 * | |
136 * Multiple flag abbreviations can be collapsed into a single argument. Say | |
137 * you define these flags: | |
138 * | |
139 * parser.addFlag('verbose', abbr: 'v'); | |
140 * parser.addFlag('french', abbr: 'f'); | |
141 * parser.addFlag('iambic-pentameter', abbr: 'i'); | |
142 * | |
143 * You can set all three flags at once: | |
144 * | |
145 * -vfi | |
146 * | |
147 * By default, an option has only a single value, with later option values | |
148 * overriding earlier ones; for example: | |
149 * | |
150 * var parser = new ArgParser(); | |
151 * parser.addOption('mode'); | |
152 * var results = parser.parse(['--mode', 'on', '--mode', 'off']); | |
153 * print(results['mode']); // prints 'off' | |
154 * | |
155 * If you need multiple values, set the `allowMultiple` parameter. In that | |
156 * case the option can occur multiple times, and the parse() method returns | |
157 * a list of values: | |
158 * | |
159 * var parser = new ArgParser(); | |
160 * parser.addOption('mode', allowMultiple: true); | |
161 * var results = parser.parse(['--mode', 'on', '--mode', 'off']); | |
162 * print(results['mode']); // prints '[on, off]' | |
163 * | |
164 * ## Defining commands ## | |
165 * | |
166 * In addition to *options*, you can also define *commands*. A command is | |
167 * a named argument that has its own set of options. For example, consider | |
168 * this shell command: | |
169 * | |
170 * $ git commit -a | |
171 * | |
172 * The executable is `git`, the command is `commit`, and the `-a` option is | |
173 * an option passed to the command. You can add a command using the | |
174 * [addCommand] method: | |
175 * | |
176 * var parser = new ArgParser(); | |
177 * var command = parser.addCommand('commit'); | |
178 * | |
179 * The addCommand() method returns another [ArgParser], which you can then | |
180 * use to define options specific to that command. If you already have an | |
181 * [ArgParser] for the command's options, you can pass it to addCommand: | |
182 * | |
183 * var parser = new ArgParser(); | |
184 * var command = new ArgParser(); | |
185 * parser.addCommand('commit', command); | |
186 * | |
187 * The [ArgParser] for a command can then define options or flags: | |
188 * | |
189 * command.addFlag('all', abbr: 'a'); | |
190 * | |
191 * You can add multiple commands to the same parser so that a user can select | |
192 * one from a range of possible commands. When parsing an argument list, | |
193 * you can then determine which command was entered and what options were | |
194 * provided for it. | |
195 * | |
196 * var results = parser.parse(['commit', '-a']); | |
197 * print(results.command.name); // "commit" | |
198 * print(results.command['all']); // true | |
199 * | |
200 * Options for a command must appear after the command in the argument list. | |
201 * For example, given the above parser, "git -a commit" is *not* valid. The | |
202 * parser tries to find the right-most command that accepts an option. For | |
203 * example: | |
204 * | |
205 * var parser = new ArgParser(); | |
206 * parser.addFlag('all', abbr: 'a'); | |
207 * var command = parser.addCommand('commit'); | |
208 * command.addFlag('all', abbr: 'a'); | |
209 * | |
210 * var results = parser.parse(['commit', '-a']); | |
211 * print(results.command['all']); // true | |
212 * | |
213 * Here, both the top-level parser and the "commit" command can accept a | |
214 * "-a" (which is probably a bad command line interface, admittedly). In | |
215 * that case, when "-a" appears after "commit", it is applied to that | |
216 * command. If it appears to the left of "commit", it is given to the | |
217 * top-level parser. | |
218 * | |
219 * ## Displaying usage | |
220 * | |
221 * You can automatically generate nice help text, suitable for use as the | |
222 * output of `--help`. To display good usage information, you should | |
223 * provide some help text when you create your options. | |
224 * | |
225 * To define help text for an entire option, use the `help` parameter: | |
226 * | |
227 * parser.addOption('mode', help: 'The compiler configuration', | |
228 * allowed: ['debug', 'release']); | |
229 * parser.addFlag('verbose', help: 'Show additional diagnostic info'); | |
230 * | |
231 * For non-flag options, you can also provide detailed help for each expected | |
232 * value by using the `allowedHelp` parameter: | |
233 * | |
234 * parser.addOption('arch', help: 'The architecture to compile for', | |
235 * allowedHelp: { | |
236 * 'ia32': 'Intel x86', | |
237 * 'arm': 'ARM Holding 32-bit chip' | |
238 * }); | |
239 * | |
240 * To display the help, use the ArgParser getUsage() method: | |
241 * | |
242 * print(parser.getUsage()); | |
243 * | |
244 * The resulting string looks something like this: | |
245 * | |
246 * --mode The compiler configuration | |
247 * [debug, release] | |
248 * | |
249 * --[no-]verbose Show additional diagnostic info | |
250 * --arch The architecture to compile for | |
251 * | |
252 * [arm] ARM Holding 32-bit chip | |
253 * [ia32] Intel x86 | |
254 * | |
255 * To assist the formatting of the usage help, single-line help text is | |
256 * followed by a single new line. Options with multi-line help text are | |
257 * followed by two new lines. This provides spatial diversity between options. | |
258 * | |
259 * [posix]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.h
tml#tag_12_02 | |
260 * [gnu]: http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Inte
rfaces | |
261 */ | |
262 library args; | 5 library args; |
263 | 6 |
264 import 'package:collection/wrappers.dart'; | 7 import 'package:collection/wrappers.dart'; |
265 | 8 |
266 import 'src/parser.dart'; | 9 import 'src/parser.dart'; |
267 import 'src/usage.dart'; | 10 import 'src/usage.dart'; |
268 import 'src/options.dart'; | 11 import 'src/options.dart'; |
269 export 'src/options.dart'; | 12 export 'src/options.dart'; |
270 | 13 |
271 /** | 14 /// A class for taking a list of raw command line arguments and parsing out |
272 * A class for taking a list of raw command line arguments and parsing out | 15 /// options and flags from them. |
273 * options and flags from them. | |
274 */ | |
275 class ArgParser { | 16 class ArgParser { |
276 final Map<String, Option> _options; | 17 final Map<String, Option> _options; |
277 final Map<String, ArgParser> _commands; | 18 final Map<String, ArgParser> _commands; |
278 | 19 |
279 /** | 20 /// The options that have been defined for this parser. |
280 * The options that have been defined for this parser. | |
281 */ | |
282 final Map<String, Option> options; | 21 final Map<String, Option> options; |
283 | 22 |
284 /** | 23 /// The commands that have been defined for this parser. |
285 * The commands that have been defined for this parser. | |
286 */ | |
287 final Map<String, ArgParser> commands; | 24 final Map<String, ArgParser> commands; |
288 | 25 |
289 /** | 26 /// Whether or not this parser parses options that appear after non-option |
290 * Whether or not this parser parses options that appear after non-option | 27 /// arguments. |
291 * arguments. | |
292 */ | |
293 final bool allowTrailingOptions; | 28 final bool allowTrailingOptions; |
294 | 29 |
295 /** | 30 /// Creates a new ArgParser. |
296 * Creates a new ArgParser. | 31 /// |
297 * | 32 /// If [allowTrailingOptions] is set, the parser will continue parsing even |
298 * If [allowTrailingOptions] is set, the parser will continue parsing even | 33 /// after it finds an argument that is neither an option nor a command. |
299 * after it finds an argument that is neither an option nor a command. | 34 /// This allows options to be specified after regular arguments. Defaults to |
300 * This allows options to be specified after regular arguments. Defaults to | 35 /// `false`. |
301 * `false`. | |
302 */ | |
303 factory ArgParser({bool allowTrailingOptions}) => | 36 factory ArgParser({bool allowTrailingOptions}) => |
304 new ArgParser._(<String, Option>{}, <String, ArgParser>{}, | 37 new ArgParser._(<String, Option>{}, <String, ArgParser>{}, |
305 allowTrailingOptions: allowTrailingOptions); | 38 allowTrailingOptions: allowTrailingOptions); |
306 | 39 |
307 ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands, | 40 ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands, |
308 {bool allowTrailingOptions}) : | 41 {bool allowTrailingOptions}) : |
309 this._options = options, | 42 this._options = options, |
310 this.options = new UnmodifiableMapView(options), | 43 this.options = new UnmodifiableMapView(options), |
311 this._commands = commands, | 44 this._commands = commands, |
312 this.commands = new UnmodifiableMapView(commands), | 45 this.commands = new UnmodifiableMapView(commands), |
313 this.allowTrailingOptions = allowTrailingOptions != null ? | 46 this.allowTrailingOptions = allowTrailingOptions != null ? |
314 allowTrailingOptions : false; | 47 allowTrailingOptions : false; |
315 | 48 |
316 /** | 49 /// Defines a command. |
317 * Defines a command. | 50 /// |
318 * | 51 /// A command is a named argument which may in turn define its own options and |
319 * A command is a named argument which may in turn define its own options and | 52 /// subcommands using the given parser. If [parser] is omitted, implicitly |
320 * subcommands using the given parser. If [parser] is omitted, implicitly | 53 /// creates a new one. Returns the parser for the command. |
321 * creates a new one. Returns the parser for the command. | |
322 */ | |
323 ArgParser addCommand(String name, [ArgParser parser]) { | 54 ArgParser addCommand(String name, [ArgParser parser]) { |
324 // Make sure the name isn't in use. | 55 // Make sure the name isn't in use. |
325 if (_commands.containsKey(name)) { | 56 if (_commands.containsKey(name)) { |
326 throw new ArgumentError('Duplicate command "$name".'); | 57 throw new ArgumentError('Duplicate command "$name".'); |
327 } | 58 } |
328 | 59 |
329 if (parser == null) parser = new ArgParser(); | 60 if (parser == null) parser = new ArgParser(); |
330 _commands[name] = parser; | 61 _commands[name] = parser; |
331 return parser; | 62 return parser; |
332 } | 63 } |
333 | 64 |
334 /** | 65 /// Defines a flag. Throws an [ArgumentError] if: |
335 * Defines a flag. Throws an [ArgumentError] if: | 66 /// |
336 * | 67 /// * There is already an option named [name]. |
337 * * There is already an option named [name]. | 68 /// * There is already an option using abbreviation [abbr]. |
338 * * There is already an option using abbreviation [abbr]. | |
339 */ | |
340 void addFlag(String name, {String abbr, String help, bool defaultsTo: false, | 69 void addFlag(String name, {String abbr, String help, bool defaultsTo: false, |
341 bool negatable: true, void callback(bool value), bool hide: false}) { | 70 bool negatable: true, void callback(bool value), bool hide: false}) { |
342 _addOption(name, abbr, help, null, null, defaultsTo, callback, | 71 _addOption(name, abbr, help, null, null, defaultsTo, callback, |
343 isFlag: true, negatable: negatable, hide: hide); | 72 isFlag: true, negatable: negatable, hide: hide); |
344 } | 73 } |
345 | 74 |
346 /** | 75 /// Defines a value-taking option. Throws an [ArgumentError] if: |
347 * Defines a value-taking option. Throws an [ArgumentError] if: | 76 /// |
348 * | 77 /// * There is already an option with name [name]. |
349 * * There is already an option with name [name]. | 78 /// * There is already an option using abbreviation [abbr]. |
350 * * There is already an option using abbreviation [abbr]. | |
351 */ | |
352 void addOption(String name, {String abbr, String help, List<String> allowed, | 79 void addOption(String name, {String abbr, String help, List<String> allowed, |
353 Map<String, String> allowedHelp, String defaultsTo, | 80 Map<String, String> allowedHelp, String defaultsTo, |
354 void callback(value), bool allowMultiple: false, bool hide: false}) { | 81 void callback(value), bool allowMultiple: false, bool hide: false}) { |
355 _addOption(name, abbr, help, allowed, allowedHelp, defaultsTo, | 82 _addOption(name, abbr, help, allowed, allowedHelp, defaultsTo, |
356 callback, isFlag: false, allowMultiple: allowMultiple, | 83 callback, isFlag: false, allowMultiple: allowMultiple, |
357 hide: hide); | 84 hide: hide); |
358 } | 85 } |
359 | 86 |
360 void _addOption(String name, String abbr, String help, List<String> allowed, | 87 void _addOption(String name, String abbr, String help, List<String> allowed, |
361 Map<String, String> allowedHelp, defaultsTo, | 88 Map<String, String> allowedHelp, defaultsTo, |
(...skipping 11 matching lines...) Expand all Loading... |
373 throw new ArgumentError( | 100 throw new ArgumentError( |
374 'Abbreviation "$abbr" is already used by "${existing.name}".'); | 101 'Abbreviation "$abbr" is already used by "${existing.name}".'); |
375 } | 102 } |
376 } | 103 } |
377 | 104 |
378 _options[name] = new Option(name, abbr, help, allowed, allowedHelp, | 105 _options[name] = new Option(name, abbr, help, allowed, allowedHelp, |
379 defaultsTo, callback, isFlag: isFlag, negatable: negatable, | 106 defaultsTo, callback, isFlag: isFlag, negatable: negatable, |
380 allowMultiple: allowMultiple, hide: hide); | 107 allowMultiple: allowMultiple, hide: hide); |
381 } | 108 } |
382 | 109 |
383 /** | 110 /// Parses [args], a list of command-line arguments, matches them against the |
384 * Parses [args], a list of command-line arguments, matches them against the | 111 /// flags and options defined by this parser, and returns the result. |
385 * flags and options defined by this parser, and returns the result. | |
386 */ | |
387 ArgResults parse(List<String> args) => | 112 ArgResults parse(List<String> args) => |
388 new Parser(null, this, args.toList(), null, null).parse(); | 113 new Parser(null, this, args.toList(), null, null).parse(); |
389 | 114 |
390 /** | 115 /// Generates a string displaying usage information for the defined options. |
391 * Generates a string displaying usage information for the defined options. | 116 /// This is basically the help text shown on the command line. |
392 * This is basically the help text shown on the command line. | |
393 */ | |
394 String getUsage() => new Usage(this).generate(); | 117 String getUsage() => new Usage(this).generate(); |
395 | 118 |
396 /** | 119 /// Get the default value for an option. Useful after parsing to test if the |
397 * Get the default value for an option. Useful after parsing to test | 120 /// user specified something other than the default. |
398 * if the user specified something other than the default. | |
399 */ | |
400 getDefault(String option) { | 121 getDefault(String option) { |
401 if (!options.containsKey(option)) { | 122 if (!options.containsKey(option)) { |
402 throw new ArgumentError('No option named $option'); | 123 throw new ArgumentError('No option named $option'); |
403 } | 124 } |
404 return options[option].defaultValue; | 125 return options[option].defaultValue; |
405 } | 126 } |
406 | 127 |
407 /** | 128 /// Finds the option whose abbreviation is [abbr], or `null` if no option has |
408 * Finds the option whose abbreviation is [abbr], or `null` if no option has | 129 /// that abbreviation. |
409 * that abbreviation. | |
410 */ | |
411 Option findByAbbreviation(String abbr) { | 130 Option findByAbbreviation(String abbr) { |
412 return options.values.firstWhere((option) => option.abbreviation == abbr, | 131 return options.values.firstWhere((option) => option.abbreviation == abbr, |
413 orElse: () => null); | 132 orElse: () => null); |
414 } | 133 } |
415 } | 134 } |
416 | 135 |
417 /** | 136 /// The results of parsing a series of command line arguments using |
418 * The results of parsing a series of command line arguments using | 137 /// [ArgParser.parse()]. Includes the parsed options and any remaining unparsed |
419 * [ArgParser.parse()]. Includes the parsed options and any remaining unparsed | 138 /// command line arguments. |
420 * command line arguments. | |
421 */ | |
422 class ArgResults { | 139 class ArgResults { |
423 final Map<String, dynamic> _options; | 140 final Map<String, dynamic> _options; |
424 | 141 |
425 /** | 142 /// If these are the results for parsing a command's options, this will be the |
426 * If these are the results for parsing a command's options, this will be | 143 /// name of the command. For top-level results, this returns `null`. |
427 * the name of the command. For top-level results, this returns `null`. | |
428 */ | |
429 final String name; | 144 final String name; |
430 | 145 |
431 /** | 146 /// The command that was selected, or `null` if none was. This will contain |
432 * The command that was selected, or `null` if none was. This will contain | 147 /// the options that were selected for that command. |
433 * the options that were selected for that command. | |
434 */ | |
435 final ArgResults command; | 148 final ArgResults command; |
436 | 149 |
437 /** | 150 /// The remaining command-line arguments that were not parsed as options or |
438 * The remaining command-line arguments that were not parsed as options or | 151 /// flags. If `--` was used to separate the options from the remaining |
439 * flags. If `--` was used to separate the options from the remaining | 152 /// arguments, it will not be included in this list. |
440 * arguments, it will not be included in this list. | |
441 */ | |
442 final List<String> rest; | 153 final List<String> rest; |
443 | 154 |
444 /** Creates a new [ArgResults]. */ | 155 /// Creates a new [ArgResults]. |
445 ArgResults(this._options, this.name, this.command, List<String> rest) | 156 ArgResults(this._options, this.name, this.command, List<String> rest) |
446 : this.rest = new UnmodifiableListView(rest); | 157 : this.rest = new UnmodifiableListView(rest); |
447 | 158 |
448 /** Gets the parsed command-line option named [name]. */ | 159 /// Gets the parsed command-line option named [name]. |
449 operator [](String name) { | 160 operator [](String name) { |
450 if (!_options.containsKey(name)) { | 161 if (!_options.containsKey(name)) { |
451 throw new ArgumentError( | 162 throw new ArgumentError( |
452 'Could not find an option named "$name".'); | 163 'Could not find an option named "$name".'); |
453 } | 164 } |
454 | 165 |
455 return _options[name]; | 166 return _options[name]; |
456 } | 167 } |
457 | 168 |
458 /** Get the names of the options as an [Iterable]. */ | 169 /// Get the names of the options as an [Iterable]. |
459 Iterable<String> get options => _options.keys; | 170 Iterable<String> get options => _options.keys; |
460 } | 171 } |
461 | 172 |
OLD | NEW |