Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(285)

Side by Side Diff: pkg/args/lib/args.dart

Issue 11819068: Add command support to args. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix a couple of type annotations. Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/args/lib/src/parser.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, 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 /** 5 /**
6 * This library lets you define parsers for parsing raw command-line arguments 6 * This library lets you define parsers for parsing raw command-line arguments
7 * into a set of options and values using [GNU][] and [POSIX][] style options. 7 * into a set of options and values using [GNU][] and [POSIX][] style options.
8 * 8 *
9 * ## Defining options ## 9 * ## Defining options ##
10 * 10 *
11 * To use this library, you create an [ArgParser] object which will contain 11 * To use this library, you create an [ArgParser] object which will contain
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 * 138 *
139 * If you need multiple values, set the [allowMultiple] flag. In that 139 * If you need multiple values, set the [allowMultiple] flag. In that
140 * case the option can occur multiple times and when parsing arguments a 140 * case the option can occur multiple times and when parsing arguments a
141 * List of values will be returned: 141 * List of values will be returned:
142 * 142 *
143 * var parser = new ArgParser(); 143 * var parser = new ArgParser();
144 * parser.addOption('mode', allowMultiple: true); 144 * parser.addOption('mode', allowMultiple: true);
145 * var results = parser.parse(['--mode', 'on', '--mode', 'off']); 145 * var results = parser.parse(['--mode', 'on', '--mode', 'off']);
146 * print(results['mode']); // prints '[on, off]' 146 * print(results['mode']); // prints '[on, off]'
147 * 147 *
148 * ## Usage ## 148 * ## Defining commands ##
149 *
150 * In addition to *options*, you can also define *commands*. A command is a
151 * named argument that has its own set of options. For example, when you run:
152 *
153 * $ git commit -a
154 *
155 * The executable is `git`, the command is `commit`, and the `-a` option is an
156 * option passed to the command. You can add a command like so:
157 *
158 * var parser = new ArgParser();
159 * var command = parser.addCommand("commit");
160 * command.addFlag('all', abbr: 'a');
161 *
162 * It returns another [ArgParser] which you can use to define options and
163 * subcommands on that command. When an argument list is parsed, you can then
164 * determine which command was entered and what options were provided for it.
165 *
166 * var results = parser.parse(['commit', '-a']);
167 * print(results.command.name); // "commit"
168 * print(results.command['a']); // true
169 *
170 * ## Displaying usage ##
149 * 171 *
150 * This library can also be used to automatically generate nice usage help 172 * This library can also be used to automatically generate nice usage help
151 * text like you get when you run a program with `--help`. To use this, you 173 * text like you get when you run a program with `--help`. To use this, you
152 * will also want to provide some help text when you create your options. To 174 * will also want to provide some help text when you create your options. To
153 * define help text for the entire option, do: 175 * define help text for the entire option, do:
154 * 176 *
155 * parser.addOption('mode', help: 'The compiler configuration', 177 * parser.addOption('mode', help: 'The compiler configuration',
156 * allowed: ['debug', 'release']); 178 * allowed: ['debug', 'release']);
157 * parser.addFlag('verbose', help: 'Show additional diagnostic info'); 179 * parser.addFlag('verbose', help: 'Show additional diagnostic info');
158 * 180 *
(...skipping 13 matching lines...) Expand all
172 * Will display something like: 194 * Will display something like:
173 * 195 *
174 * --mode The compiler configuration 196 * --mode The compiler configuration
175 * [debug, release] 197 * [debug, release]
176 * 198 *
177 * --[no-]verbose Show additional diagnostic info 199 * --[no-]verbose Show additional diagnostic info
178 * --arch The architecture to compile for 200 * --arch The architecture to compile for
179 * 201 *
180 * [arm] ARM Holding 32-bit chip 202 * [arm] ARM Holding 32-bit chip
181 * [ia32] Intel x86 203 * [ia32] Intel x86
182 * 204 *
183 * To assist the formatting of the usage help, single line help text will 205 * To assist the formatting of the usage help, single line help text will
184 * be followed by a single new line. Options with multi-line help text 206 * be followed by a single new line. Options with multi-line help text
185 * will be followed by two new lines. This provides spatial diversity between 207 * will be followed by two new lines. This provides spatial diversity between
186 * options. 208 * options.
187 * 209 *
188 * [posix]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.h tml#tag_12_02 210 * [posix]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.h tml#tag_12_02
189 * [gnu]: http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Inte rfaces 211 * [gnu]: http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Inte rfaces
190 */ 212 */
191 library args; 213 library args;
192 214
193 import 'dart:math'; 215 import 'src/parser.dart';
194 216 import 'src/usage.dart';
195 // TODO(rnystrom): Use "package:" URL here when test.dart can handle pub.
196 import 'src/utils.dart';
197 217
198 /** 218 /**
199 * A class for taking a list of raw command line arguments and parsing out 219 * A class for taking a list of raw command line arguments and parsing out
200 * options and flags from them. 220 * options and flags from them.
201 */ 221 */
202 class ArgParser { 222 class ArgParser {
203 static final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$'); 223 /**
204 static final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$'); 224 * The options that have been defined for this parser.
205 static final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$'); 225 */
206 226 final Map<String, Option> options = <String, Option>{};
207 final Map<String, _Option> _options;
208 227
209 /** 228 /**
210 * The names of the options, in the order that they were added. This way we 229 * The commands that have been defined for this parser.
211 * can generate usage information in the same order.
212 */ 230 */
213 // TODO(rnystrom): Use an ordered map type, if one appears. 231 final Map<String, ArgParser> commands = <String, ArgParser>{};
214 final List<String> _optionNames;
215
216 /** The current argument list being parsed. Set by [parse()]. */
217 List<String> _args;
218
219 /** Index of the current argument being parsed in [_args]. */
220 int _current;
221 232
222 /** Creates a new ArgParser. */ 233 /** Creates a new ArgParser. */
223 ArgParser() 234 ArgParser();
224 : _options = <String, _Option>{}, 235
225 _optionNames = <String>[]; 236 /**
237 * Defines a command. A command is a named argument which may in turn
238 * define its own options and subcommands. Returns an [ArgParser] that can
239 * be used to define the command's options.
240 */
241 ArgParser addCommand(String name) {
242 // Make sure the name isn't in use.
243 if (commands.containsKey(name)) {
244 throw new ArgumentError('Duplicate command "$name".');
245 }
246
247 var command = new ArgParser();
248 commands[name] = command;
249 return command;
250 }
226 251
227 /** 252 /**
228 * Defines a flag. Throws an [ArgumentError] if: 253 * Defines a flag. Throws an [ArgumentError] if:
229 * 254 *
230 * * There is already an option named [name]. 255 * * There is already an option named [name].
231 * * There is already an option using abbreviation [abbr]. 256 * * There is already an option using abbreviation [abbr].
232 */ 257 */
233 void addFlag(String name, {String abbr, String help, bool defaultsTo: false, 258 void addFlag(String name, {String abbr, String help, bool defaultsTo: false,
234 bool negatable: true, void callback(bool value)}) { 259 bool negatable: true, void callback(bool value)}) {
235 _addOption(name, abbr, help, null, null, defaultsTo, callback, 260 _addOption(name, abbr, help, null, null, defaultsTo, callback,
(...skipping 11 matching lines...) Expand all
247 void callback(value), bool allowMultiple: false}) { 272 void callback(value), bool allowMultiple: false}) {
248 _addOption(name, abbr, help, allowed, allowedHelp, defaultsTo, 273 _addOption(name, abbr, help, allowed, allowedHelp, defaultsTo,
249 callback, isFlag: false, allowMultiple: allowMultiple); 274 callback, isFlag: false, allowMultiple: allowMultiple);
250 } 275 }
251 276
252 void _addOption(String name, String abbr, String help, List<String> allowed, 277 void _addOption(String name, String abbr, String help, List<String> allowed,
253 Map<String, String> allowedHelp, defaultsTo, 278 Map<String, String> allowedHelp, defaultsTo,
254 void callback(value), {bool isFlag, bool negatable: false, 279 void callback(value), {bool isFlag, bool negatable: false,
255 bool allowMultiple: false}) { 280 bool allowMultiple: false}) {
256 // Make sure the name isn't in use. 281 // Make sure the name isn't in use.
257 if (_options.containsKey(name)) { 282 if (options.containsKey(name)) {
258 throw new ArgumentError('Duplicate option "$name".'); 283 throw new ArgumentError('Duplicate option "$name".');
259 } 284 }
260 285
261 // Make sure the abbreviation isn't too long or in use. 286 // Make sure the abbreviation isn't too long or in use.
262 if (abbr != null) { 287 if (abbr != null) {
263 if (abbr.length > 1) { 288 if (abbr.length > 1) {
264 throw new ArgumentError( 289 throw new ArgumentError(
265 'Abbreviation "$abbr" is longer than one character.'); 290 'Abbreviation "$abbr" is longer than one character.');
266 } 291 }
267 292
268 var existing = _findByAbbr(abbr); 293 var existing = findByAbbreviation(abbr);
269 if (existing != null) { 294 if (existing != null) {
270 throw new ArgumentError( 295 throw new ArgumentError(
271 'Abbreviation "$abbr" is already used by "${existing.name}".'); 296 'Abbreviation "$abbr" is already used by "${existing.name}".');
272 } 297 }
273 } 298 }
274 299
275 _options[name] = new _Option(name, abbr, help, allowed, allowedHelp, 300 options[name] = new Option(name, abbr, help, allowed, allowedHelp,
276 defaultsTo, callback, isFlag: isFlag, negatable: negatable, 301 defaultsTo, callback, isFlag: isFlag, negatable: negatable,
277 allowMultiple: allowMultiple); 302 allowMultiple: allowMultiple);
278 _optionNames.add(name);
279 } 303 }
280 304
281 /** 305 /**
282 * Parses [args], a list of command-line arguments, matches them against the 306 * Parses [args], a list of command-line arguments, matches them against the
283 * flags and options defined by this parser, and returns the result. 307 * flags and options defined by this parser, and returns the result.
284 */ 308 */
285 ArgResults parse(List<String> args) { 309 ArgResults parse(List<String> args) =>
286 _args = args; 310 new Parser(null, this, args.toList()).parse();
287 _current = 0;
288 var results = {};
289
290 // Initialize flags to their defaults.
291 _options.forEach((name, option) {
292 if (option.allowMultiple) {
293 results[name] = [];
294 } else {
295 results[name] = option.defaultValue;
296 }
297 });
298
299 // Parse the args.
300 for (_current = 0; _current < args.length; _current++) {
301 var arg = args[_current];
302
303 if (arg == '--') {
304 // Reached the argument terminator, so stop here.
305 _current++;
306 break;
307 }
308
309 // Try to parse the current argument as an option. Note that the order
310 // here matters.
311 if (_parseSoloOption(results)) continue;
312 if (_parseAbbreviation(results)) continue;
313 if (_parseLongOption(results)) continue;
314
315 // If we got here, the argument doesn't look like an option, so stop.
316 break;
317 }
318
319 // Set unspecified multivalued arguments to their default value,
320 // if any, and invoke the callbacks.
321 for (var name in _optionNames) {
322 var option = _options[name];
323 if (option.allowMultiple &&
324 results[name].length == 0 &&
325 option.defaultValue != null) {
326 results[name].add(option.defaultValue);
327 }
328 if (option.callback != null) option.callback(results[name]);
329 }
330
331 // Add in the leftover arguments we didn't parse.
332 return new ArgResults(results,
333 _args.getRange(_current, _args.length - _current));
334 }
335 311
336 /** 312 /**
337 * Generates a string displaying usage information for the defined options. 313 * Generates a string displaying usage information for the defined options.
338 * This is basically the help text shown on the command line. 314 * This is basically the help text shown on the command line.
339 */ 315 */
340 String getUsage() { 316 String getUsage() => new Usage(this).generate();
341 return new _Usage(this).generate();
342 }
343 317
344 /** 318 /**
345 * Called during parsing to validate the arguments. Throws a 319 * Get the default value for an option. Useful after parsing to test
346 * [FormatException] if [condition] is `false`. 320 * if the user specified something other than the default.
347 */ 321 */
348 _validate(bool condition, String message) { 322 getDefault(String option) {
349 if (!condition) throw new FormatException(message); 323 if (!options.containsKey(option)) {
350 } 324 throw new ArgumentError('No option named $option');
351
352 /** Validates and stores [value] as the value for [option]. */
353 _setOption(Map results, _Option option, value) {
354 // See if it's one of the allowed values.
355 if (option.allowed != null) {
356 _validate(option.allowed.any((allow) => allow == value),
357 '"$value" is not an allowed value for option "${option.name}".');
358 } 325 }
359 326 return options[option].defaultValue;
360 if (option.allowMultiple) {
361 results[option.name].add(value);
362 } else {
363 results[option.name] = value;
364 }
365 }
366
367 /**
368 * Pulls the value for [option] from the next argument in [_args] (where the
369 * current option is at index [_current]. Validates that there is a valid
370 * value there.
371 */
372 void _readNextArgAsValue(Map results, _Option option) {
373 _current++;
374 // Take the option argument from the next command line arg.
375 _validate(_current < _args.length,
376 'Missing argument for "${option.name}".');
377
378 // Make sure it isn't an option itself.
379 _validate(!_ABBR_OPT.hasMatch(_args[_current]) &&
380 !_LONG_OPT.hasMatch(_args[_current]),
381 'Missing argument for "${option.name}".');
382
383 _setOption(results, option, _args[_current]);
384 }
385
386 /**
387 * Tries to parse the current argument as a "solo" option, which is a single
388 * hyphen followed by a single letter. We treat this differently than
389 * collapsed abbreviations (like "-abc") to handle the possible value that
390 * may follow it.
391 */
392 bool _parseSoloOption(Map results) {
393 var soloOpt = _SOLO_OPT.firstMatch(_args[_current]);
394 if (soloOpt == null) return false;
395
396 var option = _findByAbbr(soloOpt[1]);
397 _validate(option != null,
398 'Could not find an option or flag "-${soloOpt[1]}".');
399
400 if (option.isFlag) {
401 _setOption(results, option, true);
402 } else {
403 _readNextArgAsValue(results, option);
404 }
405
406 return true;
407 }
408
409 /**
410 * Tries to parse the current argument as a series of collapsed abbreviations
411 * (like "-abc") or a single abbreviation with the value directly attached
412 * to it (like "-mrelease").
413 */
414 bool _parseAbbreviation(Map results) {
415 var abbrOpt = _ABBR_OPT.firstMatch(_args[_current]);
416 if (abbrOpt == null) return false;
417
418 // If the first character is the abbreviation for a non-flag option, then
419 // the rest is the value.
420 var c = abbrOpt[1].substring(0, 1);
421 var first = _findByAbbr(c);
422 if (first == null) {
423 _validate(false, 'Could not find an option with short name "-$c".');
424 } else if (!first.isFlag) {
425 // The first character is a non-flag option, so the rest must be the
426 // value.
427 var value = '${abbrOpt[1].substring(1)}${abbrOpt[2]}';
428 _setOption(results, first, value);
429 } else {
430 // If we got some non-flag characters, then it must be a value, but
431 // if we got here, it's a flag, which is wrong.
432 _validate(abbrOpt[2] == '',
433 'Option "-$c" is a flag and cannot handle value '
434 '"${abbrOpt[1].substring(1)}${abbrOpt[2]}".');
435
436 // Not an option, so all characters should be flags.
437 for (var i = 0; i < abbrOpt[1].length; i++) {
438 var c = abbrOpt[1].substring(i, i + 1);
439 var option = _findByAbbr(c);
440 _validate(option != null,
441 'Could not find an option with short name "-$c".');
442
443 // In a list of short options, only the first can be a non-flag. If
444 // we get here we've checked that already.
445 _validate(option.isFlag,
446 'Option "-$c" must be a flag to be in a collapsed "-".');
447
448 _setOption(results, option, true);
449 }
450 }
451
452 return true;
453 }
454
455 /**
456 * Tries to parse the current argument as a long-form named option, which
457 * may include a value like "--mode=release" or "--mode release".
458 */
459 bool _parseLongOption(Map results) {
460 var longOpt = _LONG_OPT.firstMatch(_args[_current]);
461 if (longOpt == null) return false;
462
463 var name = longOpt[1];
464 var option = _options[name];
465 if (option != null) {
466 if (option.isFlag) {
467 _validate(longOpt[3] == null,
468 'Flag option "$name" should not be given a value.');
469
470 _setOption(results, option, true);
471 } else if (longOpt[3] != null) {
472 // We have a value like --foo=bar.
473 _setOption(results, option, longOpt[3]);
474 } else {
475 // Option like --foo, so look for the value as the next arg.
476 _readNextArgAsValue(results, option);
477 }
478 } else if (name.startsWith('no-')) {
479 // See if it's a negated flag.
480 name = name.substring('no-'.length);
481 option = _options[name];
482 _validate(option != null, 'Could not find an option named "$name".');
483 _validate(option.isFlag, 'Cannot negate non-flag option "$name".');
484 _validate(option.negatable, 'Cannot negate option "$name".');
485
486 _setOption(results, option, false);
487 } else {
488 _validate(option != null, 'Could not find an option named "$name".');
489 }
490
491 return true;
492 } 327 }
493 328
494 /** 329 /**
495 * Finds the option whose abbreviation is [abbr], or `null` if no option has 330 * Finds the option whose abbreviation is [abbr], or `null` if no option has
496 * that abbreviation. 331 * that abbreviation.
497 */ 332 */
498 _Option _findByAbbr(String abbr) { 333 Option findByAbbreviation(String abbr) {
499 for (var option in _options.values) { 334 return options.values.firstMatching((option) => option.abbreviation == abbr,
500 if (option.abbreviation == abbr) return option; 335 orElse: () => null);
501 }
502
503 return null;
504 }
505
506 /**
507 * Get the default value for an option. Useful after parsing to test
508 * if the user specified something other than the default.
509 */
510 getDefault(String option) {
511 if (!_options.containsKey(option)) {
512 throw new ArgumentError('No option named $option');
513 }
514 return _options[option].defaultValue;
515 } 336 }
516 } 337 }
517 338
518 /** 339 /**
519 * The results of parsing a series of command line arguments using 340 * A command-line option. Includes both flags and options which take a value.
520 * [ArgParser.parse()]. Includes the parsed options and any remaining unparsed
521 * command line arguments.
522 */ 341 */
523 class ArgResults { 342 class Option {
524 final Map _options;
525
526 /**
527 * The remaining command-line arguments that were not parsed as options or
528 * flags. If `--` was used to separate the options from the remaining
529 * arguments, it will not be included in this list.
530 */
531 final List<String> rest;
532
533 /** Creates a new [ArgResults]. */
534 ArgResults(this._options, this.rest);
535
536 /** Gets the parsed command-line option named [name]. */
537 operator [](String name) {
538 if (!_options.containsKey(name)) {
539 throw new ArgumentError(
540 'Could not find an option named "$name".');
541 }
542
543 return _options[name];
544 }
545
546 /** Get the names of the options as a [Collection]. */
547 Collection<String> get options => _options.keys.toList();
548 }
549
550 class _Option {
551 final String name; 343 final String name;
552 final String abbreviation; 344 final String abbreviation;
553 final List allowed; 345 final List allowed;
554 final defaultValue; 346 final defaultValue;
555 final Function callback; 347 final Function callback;
556 final String help; 348 final String help;
557 final Map<String, String> allowedHelp; 349 final Map<String, String> allowedHelp;
558 final bool isFlag; 350 final bool isFlag;
559 final bool negatable; 351 final bool negatable;
560 final bool allowMultiple; 352 final bool allowMultiple;
561 353
562 _Option(this.name, this.abbreviation, this.help, this.allowed, 354 Option(this.name, this.abbreviation, this.help, this.allowed,
563 this.allowedHelp, this.defaultValue, this.callback, {this.isFlag, 355 this.allowedHelp, this.defaultValue, this.callback, {this.isFlag,
564 this.negatable, this.allowMultiple: false}); 356 this.negatable, this.allowMultiple: false});
565 } 357 }
566 358
567 /** 359 /**
568 * Takes an [ArgParser] and generates a string of usage (i.e. help) text for its 360 * The results of parsing a series of command line arguments using
569 * defined options. Internally, it works like a tabular printer. The output is 361 * [ArgParser.parse()]. Includes the parsed options and any remaining unparsed
570 * divided into three horizontal columns, like so: 362 * command line arguments.
571 *
572 * -h, --help Prints the usage information
573 * | | | |
574 *
575 * It builds the usage text up one column at a time and handles padding with
576 * spaces and wrapping to the next line to keep the cells correctly lined up.
577 */ 363 */
578 class _Usage { 364 class ArgResults {
579 static const NUM_COLUMNS = 3; // Abbreviation, long name, help. 365 final Map _options;
580
581 /** The parser this is generating usage for. */
582 final ArgParser args;
583
584 /** The working buffer for the generated usage text. */
585 StringBuffer buffer;
586 366
587 /** 367 /**
588 * The column that the "cursor" is currently on. If the next call to 368 * If these are the results for parsing a command's options, this will be
589 * [write()] is not for this column, it will correctly handle advancing to 369 * the name of the command. For top-level results, this returns `null`.
590 * the next column (and possibly the next row).
591 */ 370 */
592 int currentColumn = 0; 371 final String name;
593
594 /** The width in characters of each column. */
595 List<int> columnWidths;
596 372
597 /** 373 /**
598 * The number of sequential lines of text that have been written to the last 374 * The command that was selected, or `null` if none was. This will contain
599 * column (which shows help info). We track this so that help text that spans 375 * the options that were selected for that command.
600 * multiple lines can be padded with a blank line after it for separation.
601 * Meanwhile, sequential options with single-line help will be compacted next
602 * to each other.
603 */ 376 */
604 int numHelpLines = 0; 377 final ArgResults command;
605 378
606 /** 379 /**
607 * How many newlines need to be rendered before the next bit of text can be 380 * The remaining command-line arguments that were not parsed as options or
608 * written. We do this lazily so that the last bit of usage doesn't have 381 * flags. If `--` was used to separate the options from the remaining
609 * dangling newlines. We only write newlines right *before* we write some 382 * arguments, it will not be included in this list.
610 * real content.
611 */ 383 */
612 int newlinesNeeded = 0; 384 final List<String> rest;
613 385
614 _Usage(this.args); 386 /** Creates a new [ArgResults]. */
387 ArgResults(this._options, this.name, this.command, this.rest);
615 388
616 /** 389 /** Gets the parsed command-line option named [name]. */
617 * Generates a string displaying usage information for the defined options. 390 operator [](String name) {
618 * This is basically the help text shown on the command line. 391 if (!_options.containsKey(name)) {
619 */ 392 throw new ArgumentError(
620 String generate() { 393 'Could not find an option named "$name".');
621 buffer = new StringBuffer();
622
623 calculateColumnWidths();
624
625 for (var name in args._optionNames) {
626 var option = args._options[name];
627 write(0, getAbbreviation(option));
628 write(1, getLongOption(option));
629
630 if (option.help != null) write(2, option.help);
631
632 if (option.allowedHelp != null) {
633 var allowedNames = option.allowedHelp.keys.toList();
634 allowedNames.sort();
635 newline();
636 for (var name in allowedNames) {
637 write(1, getAllowedTitle(name));
638 write(2, option.allowedHelp[name]);
639 }
640 newline();
641 } else if (option.allowed != null) {
642 write(2, buildAllowedList(option));
643 } else if (option.defaultValue != null) {
644 if (option.isFlag && option.defaultValue == true) {
645 write(2, '(defaults to on)');
646 } else if (!option.isFlag) {
647 write(2, '(defaults to "${option.defaultValue}")');
648 }
649 }
650
651 // If any given option displays more than one line of text on the right
652 // column (i.e. help, default value, allowed options, etc.) then put a
653 // blank line after it. This gives space where it's useful while still
654 // keeping simple one-line options clumped together.
655 if (numHelpLines > 1) newline();
656 } 394 }
657 395
658 return buffer.toString(); 396 return _options[name];
659 } 397 }
660 398
661 String getAbbreviation(_Option option) { 399 /** Get the names of the options as a [Collection]. */
662 if (option.abbreviation != null) { 400 Collection<String> get options => _options.keys.toList();
663 return '-${option.abbreviation}, '; 401 }
664 } else {
665 return '';
666 }
667 }
668 402
669 String getLongOption(_Option option) {
670 if (option.negatable) {
671 return '--[no-]${option.name}';
672 } else {
673 return '--${option.name}';
674 }
675 }
676
677 String getAllowedTitle(String allowed) {
678 return ' [$allowed]';
679 }
680
681 void calculateColumnWidths() {
682 int abbr = 0;
683 int title = 0;
684 for (var name in args._optionNames) {
685 var option = args._options[name];
686
687 // Make room in the first column if there are abbreviations.
688 abbr = max(abbr, getAbbreviation(option).length);
689
690 // Make room for the option.
691 title = max(title, getLongOption(option).length);
692
693 // Make room for the allowed help.
694 if (option.allowedHelp != null) {
695 for (var allowed in option.allowedHelp.keys) {
696 title = max(title, getAllowedTitle(allowed).length);
697 }
698 }
699 }
700
701 // Leave a gutter between the columns.
702 title += 4;
703 columnWidths = [abbr, title];
704 }
705
706 newline() {
707 newlinesNeeded++;
708 currentColumn = 0;
709 numHelpLines = 0;
710 }
711
712 write(int column, String text) {
713 var lines = text.split('\n');
714
715 // Strip leading and trailing empty lines.
716 while (lines.length > 0 && lines[0].trim() == '') {
717 lines.removeRange(0, 1);
718 }
719
720 while (lines.length > 0 && lines[lines.length - 1].trim() == '') {
721 lines.removeLast();
722 }
723
724 for (var line in lines) {
725 writeLine(column, line);
726 }
727 }
728
729 writeLine(int column, String text) {
730 // Write any pending newlines.
731 while (newlinesNeeded > 0) {
732 buffer.add('\n');
733 newlinesNeeded--;
734 }
735
736 // Advance until we are at the right column (which may mean wrapping around
737 // to the next line.
738 while (currentColumn != column) {
739 if (currentColumn < NUM_COLUMNS - 1) {
740 buffer.add(padRight('', columnWidths[currentColumn]));
741 } else {
742 buffer.add('\n');
743 }
744 currentColumn = (currentColumn + 1) % NUM_COLUMNS;
745 }
746
747 if (column < columnWidths.length) {
748 // Fixed-size column, so pad it.
749 buffer.add(padRight(text, columnWidths[column]));
750 } else {
751 // The last column, so just write it.
752 buffer.add(text);
753 }
754
755 // Advance to the next column.
756 currentColumn = (currentColumn + 1) % NUM_COLUMNS;
757
758 // If we reached the last column, we need to wrap to the next line.
759 if (column == NUM_COLUMNS - 1) newlinesNeeded++;
760
761 // Keep track of how many consecutive lines we've written in the last
762 // column.
763 if (column == NUM_COLUMNS - 1) {
764 numHelpLines++;
765 } else {
766 numHelpLines = 0;
767 }
768 }
769
770 buildAllowedList(_Option option) {
771 var allowedBuffer = new StringBuffer();
772 allowedBuffer.add('[');
773 bool first = true;
774 for (var allowed in option.allowed) {
775 if (!first) allowedBuffer.add(', ');
776 allowedBuffer.add(allowed);
777 if (allowed == option.defaultValue) {
778 allowedBuffer.add(' (default)');
779 }
780 first = false;
781 }
782 allowedBuffer.add(']');
783 return allowedBuffer.toString();
784 }
785 }
OLDNEW
« no previous file with comments | « no previous file | pkg/args/lib/src/parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698