argument_parser.js (35223B)
1 /** 2 * class ArgumentParser 3 * 4 * Object for parsing command line strings into js objects. 5 * 6 * Inherited from [[ActionContainer]] 7 **/ 8 'use strict'; 9 10 var util = require('util'); 11 var format = require('util').format; 12 var Path = require('path'); 13 var sprintf = require('sprintf-js').sprintf; 14 15 // Constants 16 var c = require('./const'); 17 18 var $$ = require('./utils'); 19 20 var ActionContainer = require('./action_container'); 21 22 // Errors 23 var argumentErrorHelper = require('./argument/error'); 24 25 var HelpFormatter = require('./help/formatter'); 26 27 var Namespace = require('./namespace'); 28 29 30 /** 31 * new ArgumentParser(options) 32 * 33 * Create a new ArgumentParser object. 34 * 35 * ##### Options: 36 * - `prog` The name of the program (default: Path.basename(process.argv[1])) 37 * - `usage` A usage message (default: auto-generated from arguments) 38 * - `description` A description of what the program does 39 * - `epilog` Text following the argument descriptions 40 * - `parents` Parsers whose arguments should be copied into this one 41 * - `formatterClass` HelpFormatter class for printing help messages 42 * - `prefixChars` Characters that prefix optional arguments 43 * - `fromfilePrefixChars` Characters that prefix files containing additional arguments 44 * - `argumentDefault` The default value for all arguments 45 * - `addHelp` Add a -h/-help option 46 * - `conflictHandler` Specifies how to handle conflicting argument names 47 * - `debug` Enable debug mode. Argument errors throw exception in 48 * debug mode and process.exit in normal. Used for development and 49 * testing (default: false) 50 * 51 * See also [original guide][1] 52 * 53 * [1]:http://docs.python.org/dev/library/argparse.html#argumentparser-objects 54 **/ 55 function ArgumentParser(options) { 56 if (!(this instanceof ArgumentParser)) { 57 return new ArgumentParser(options); 58 } 59 var self = this; 60 options = options || {}; 61 62 options.description = (options.description || null); 63 options.argumentDefault = (options.argumentDefault || null); 64 options.prefixChars = (options.prefixChars || '-'); 65 options.conflictHandler = (options.conflictHandler || 'error'); 66 ActionContainer.call(this, options); 67 68 options.addHelp = typeof options.addHelp === 'undefined' || !!options.addHelp; 69 options.parents = options.parents || []; 70 // default program name 71 options.prog = (options.prog || Path.basename(process.argv[1])); 72 this.prog = options.prog; 73 this.usage = options.usage; 74 this.epilog = options.epilog; 75 this.version = options.version; 76 77 this.debug = (options.debug === true); 78 79 this.formatterClass = (options.formatterClass || HelpFormatter); 80 this.fromfilePrefixChars = options.fromfilePrefixChars || null; 81 this._positionals = this.addArgumentGroup({ title: 'Positional arguments' }); 82 this._optionals = this.addArgumentGroup({ title: 'Optional arguments' }); 83 this._subparsers = null; 84 85 // register types 86 function FUNCTION_IDENTITY(o) { 87 return o; 88 } 89 this.register('type', 'auto', FUNCTION_IDENTITY); 90 this.register('type', null, FUNCTION_IDENTITY); 91 this.register('type', 'int', function (x) { 92 var result = parseInt(x, 10); 93 if (isNaN(result)) { 94 throw new Error(x + ' is not a valid integer.'); 95 } 96 return result; 97 }); 98 this.register('type', 'float', function (x) { 99 var result = parseFloat(x); 100 if (isNaN(result)) { 101 throw new Error(x + ' is not a valid float.'); 102 } 103 return result; 104 }); 105 this.register('type', 'string', function (x) { 106 return '' + x; 107 }); 108 109 // add help and version arguments if necessary 110 var defaultPrefix = (this.prefixChars.indexOf('-') > -1) ? '-' : this.prefixChars[0]; 111 if (options.addHelp) { 112 this.addArgument( 113 [ defaultPrefix + 'h', defaultPrefix + defaultPrefix + 'help' ], 114 { 115 action: 'help', 116 defaultValue: c.SUPPRESS, 117 help: 'Show this help message and exit.' 118 } 119 ); 120 } 121 if (typeof this.version !== 'undefined') { 122 this.addArgument( 123 [ defaultPrefix + 'v', defaultPrefix + defaultPrefix + 'version' ], 124 { 125 action: 'version', 126 version: this.version, 127 defaultValue: c.SUPPRESS, 128 help: "Show program's version number and exit." 129 } 130 ); 131 } 132 133 // add parent arguments and defaults 134 options.parents.forEach(function (parent) { 135 self._addContainerActions(parent); 136 if (typeof parent._defaults !== 'undefined') { 137 for (var defaultKey in parent._defaults) { 138 if (parent._defaults.hasOwnProperty(defaultKey)) { 139 self._defaults[defaultKey] = parent._defaults[defaultKey]; 140 } 141 } 142 } 143 }); 144 } 145 146 util.inherits(ArgumentParser, ActionContainer); 147 148 /** 149 * ArgumentParser#addSubparsers(options) -> [[ActionSubparsers]] 150 * - options (object): hash of options see [[ActionSubparsers.new]] 151 * 152 * See also [subcommands][1] 153 * 154 * [1]:http://docs.python.org/dev/library/argparse.html#sub-commands 155 **/ 156 ArgumentParser.prototype.addSubparsers = function (options) { 157 if (this._subparsers) { 158 this.error('Cannot have multiple subparser arguments.'); 159 } 160 161 options = options || {}; 162 options.debug = (this.debug === true); 163 options.optionStrings = []; 164 options.parserClass = (options.parserClass || ArgumentParser); 165 166 167 if (!!options.title || !!options.description) { 168 169 this._subparsers = this.addArgumentGroup({ 170 title: (options.title || 'subcommands'), 171 description: options.description 172 }); 173 delete options.title; 174 delete options.description; 175 176 } else { 177 this._subparsers = this._positionals; 178 } 179 180 // prog defaults to the usage message of this parser, skipping 181 // optional arguments and with no "usage:" prefix 182 if (!options.prog) { 183 var formatter = this._getFormatter(); 184 var positionals = this._getPositionalActions(); 185 var groups = this._mutuallyExclusiveGroups; 186 formatter.addUsage(this.usage, positionals, groups, ''); 187 options.prog = formatter.formatHelp().trim(); 188 } 189 190 // create the parsers action and add it to the positionals list 191 var ParsersClass = this._popActionClass(options, 'parsers'); 192 var action = new ParsersClass(options); 193 this._subparsers._addAction(action); 194 195 // return the created parsers action 196 return action; 197 }; 198 199 ArgumentParser.prototype._addAction = function (action) { 200 if (action.isOptional()) { 201 this._optionals._addAction(action); 202 } else { 203 this._positionals._addAction(action); 204 } 205 return action; 206 }; 207 208 ArgumentParser.prototype._getOptionalActions = function () { 209 return this._actions.filter(function (action) { 210 return action.isOptional(); 211 }); 212 }; 213 214 ArgumentParser.prototype._getPositionalActions = function () { 215 return this._actions.filter(function (action) { 216 return action.isPositional(); 217 }); 218 }; 219 220 221 /** 222 * ArgumentParser#parseArgs(args, namespace) -> Namespace|Object 223 * - args (array): input elements 224 * - namespace (Namespace|Object): result object 225 * 226 * Parsed args and throws error if some arguments are not recognized 227 * 228 * See also [original guide][1] 229 * 230 * [1]:http://docs.python.org/dev/library/argparse.html#the-parse-args-method 231 **/ 232 ArgumentParser.prototype.parseArgs = function (args, namespace) { 233 var argv; 234 var result = this.parseKnownArgs(args, namespace); 235 236 args = result[0]; 237 argv = result[1]; 238 if (argv && argv.length > 0) { 239 this.error( 240 format('Unrecognized arguments: %s.', argv.join(' ')) 241 ); 242 } 243 return args; 244 }; 245 246 /** 247 * ArgumentParser#parseKnownArgs(args, namespace) -> array 248 * - args (array): input options 249 * - namespace (Namespace|Object): result object 250 * 251 * Parse known arguments and return tuple of result object 252 * and unknown args 253 * 254 * See also [original guide][1] 255 * 256 * [1]:http://docs.python.org/dev/library/argparse.html#partial-parsing 257 **/ 258 ArgumentParser.prototype.parseKnownArgs = function (args, namespace) { 259 var self = this; 260 261 // args default to the system args 262 args = args || process.argv.slice(2); 263 264 // default Namespace built from parser defaults 265 namespace = namespace || new Namespace(); 266 267 self._actions.forEach(function (action) { 268 if (action.dest !== c.SUPPRESS) { 269 if (!$$.has(namespace, action.dest)) { 270 if (action.defaultValue !== c.SUPPRESS) { 271 var defaultValue = action.defaultValue; 272 if (typeof action.defaultValue === 'string') { 273 defaultValue = self._getValue(action, defaultValue); 274 } 275 namespace[action.dest] = defaultValue; 276 } 277 } 278 } 279 }); 280 281 Object.keys(self._defaults).forEach(function (dest) { 282 namespace[dest] = self._defaults[dest]; 283 }); 284 285 // parse the arguments and exit if there are any errors 286 try { 287 var res = this._parseKnownArgs(args, namespace); 288 289 namespace = res[0]; 290 args = res[1]; 291 if ($$.has(namespace, c._UNRECOGNIZED_ARGS_ATTR)) { 292 args = $$.arrayUnion(args, namespace[c._UNRECOGNIZED_ARGS_ATTR]); 293 delete namespace[c._UNRECOGNIZED_ARGS_ATTR]; 294 } 295 return [ namespace, args ]; 296 } catch (e) { 297 this.error(e); 298 } 299 }; 300 301 ArgumentParser.prototype._parseKnownArgs = function (argStrings, namespace) { 302 var self = this; 303 304 var extras = []; 305 306 // replace arg strings that are file references 307 if (this.fromfilePrefixChars !== null) { 308 argStrings = this._readArgsFromFiles(argStrings); 309 } 310 // map all mutually exclusive arguments to the other arguments 311 // they can't occur with 312 // Python has 'conflicts = action_conflicts.setdefault(mutex_action, [])' 313 // though I can't conceive of a way in which an action could be a member 314 // of two different mutually exclusive groups. 315 316 function actionHash(action) { 317 // some sort of hashable key for this action 318 // action itself cannot be a key in actionConflicts 319 // I think getName() (join of optionStrings) is unique enough 320 return action.getName(); 321 } 322 323 var conflicts, key; 324 var actionConflicts = {}; 325 326 this._mutuallyExclusiveGroups.forEach(function (mutexGroup) { 327 mutexGroup._groupActions.forEach(function (mutexAction, i, groupActions) { 328 key = actionHash(mutexAction); 329 if (!$$.has(actionConflicts, key)) { 330 actionConflicts[key] = []; 331 } 332 conflicts = actionConflicts[key]; 333 conflicts.push.apply(conflicts, groupActions.slice(0, i)); 334 conflicts.push.apply(conflicts, groupActions.slice(i + 1)); 335 }); 336 }); 337 338 // find all option indices, and determine the arg_string_pattern 339 // which has an 'O' if there is an option at an index, 340 // an 'A' if there is an argument, or a '-' if there is a '--' 341 var optionStringIndices = {}; 342 343 var argStringPatternParts = []; 344 345 argStrings.forEach(function (argString, argStringIndex) { 346 if (argString === '--') { 347 argStringPatternParts.push('-'); 348 while (argStringIndex < argStrings.length) { 349 argStringPatternParts.push('A'); 350 argStringIndex++; 351 } 352 } else { 353 // otherwise, add the arg to the arg strings 354 // and note the index if it was an option 355 var pattern; 356 var optionTuple = self._parseOptional(argString); 357 if (!optionTuple) { 358 pattern = 'A'; 359 } else { 360 optionStringIndices[argStringIndex] = optionTuple; 361 pattern = 'O'; 362 } 363 argStringPatternParts.push(pattern); 364 } 365 }); 366 var argStringsPattern = argStringPatternParts.join(''); 367 368 var seenActions = []; 369 var seenNonDefaultActions = []; 370 371 372 function takeAction(action, argumentStrings, optionString) { 373 seenActions.push(action); 374 var argumentValues = self._getValues(action, argumentStrings); 375 376 // error if this argument is not allowed with other previously 377 // seen arguments, assuming that actions that use the default 378 // value don't really count as "present" 379 if (argumentValues !== action.defaultValue) { 380 seenNonDefaultActions.push(action); 381 if (actionConflicts[actionHash(action)]) { 382 actionConflicts[actionHash(action)].forEach(function (actionConflict) { 383 if (seenNonDefaultActions.indexOf(actionConflict) >= 0) { 384 throw argumentErrorHelper( 385 action, 386 format('Not allowed with argument "%s".', actionConflict.getName()) 387 ); 388 } 389 }); 390 } 391 } 392 393 if (argumentValues !== c.SUPPRESS) { 394 action.call(self, namespace, argumentValues, optionString); 395 } 396 } 397 398 function consumeOptional(startIndex) { 399 // get the optional identified at this index 400 var optionTuple = optionStringIndices[startIndex]; 401 var action = optionTuple[0]; 402 var optionString = optionTuple[1]; 403 var explicitArg = optionTuple[2]; 404 405 // identify additional optionals in the same arg string 406 // (e.g. -xyz is the same as -x -y -z if no args are required) 407 var actionTuples = []; 408 409 var args, argCount, start, stop; 410 411 for (;;) { 412 if (!action) { 413 extras.push(argStrings[startIndex]); 414 return startIndex + 1; 415 } 416 if (explicitArg) { 417 argCount = self._matchArgument(action, 'A'); 418 419 // if the action is a single-dash option and takes no 420 // arguments, try to parse more single-dash options out 421 // of the tail of the option string 422 var chars = self.prefixChars; 423 if (argCount === 0 && chars.indexOf(optionString[1]) < 0) { 424 actionTuples.push([ action, [], optionString ]); 425 optionString = optionString[0] + explicitArg[0]; 426 var newExplicitArg = explicitArg.slice(1) || null; 427 var optionalsMap = self._optionStringActions; 428 429 if (Object.keys(optionalsMap).indexOf(optionString) >= 0) { 430 action = optionalsMap[optionString]; 431 explicitArg = newExplicitArg; 432 } else { 433 throw argumentErrorHelper(action, sprintf('ignored explicit argument %r', explicitArg)); 434 } 435 } else if (argCount === 1) { 436 // if the action expect exactly one argument, we've 437 // successfully matched the option; exit the loop 438 stop = startIndex + 1; 439 args = [ explicitArg ]; 440 actionTuples.push([ action, args, optionString ]); 441 break; 442 } else { 443 // error if a double-dash option did not use the 444 // explicit argument 445 throw argumentErrorHelper(action, sprintf('ignored explicit argument %r', explicitArg)); 446 } 447 } else { 448 // if there is no explicit argument, try to match the 449 // optional's string arguments with the following strings 450 // if successful, exit the loop 451 452 start = startIndex + 1; 453 var selectedPatterns = argStringsPattern.substr(start); 454 455 argCount = self._matchArgument(action, selectedPatterns); 456 stop = start + argCount; 457 458 459 args = argStrings.slice(start, stop); 460 461 actionTuples.push([ action, args, optionString ]); 462 break; 463 } 464 465 } 466 467 // add the Optional to the list and return the index at which 468 // the Optional's string args stopped 469 if (actionTuples.length < 1) { 470 throw new Error('length should be > 0'); 471 } 472 for (var i = 0; i < actionTuples.length; i++) { 473 takeAction.apply(self, actionTuples[i]); 474 } 475 return stop; 476 } 477 478 // the list of Positionals left to be parsed; this is modified 479 // by consume_positionals() 480 var positionals = self._getPositionalActions(); 481 482 function consumePositionals(startIndex) { 483 // match as many Positionals as possible 484 var selectedPattern = argStringsPattern.substr(startIndex); 485 var argCounts = self._matchArgumentsPartial(positionals, selectedPattern); 486 487 // slice off the appropriate arg strings for each Positional 488 // and add the Positional and its args to the list 489 for (var i = 0; i < positionals.length; i++) { 490 var action = positionals[i]; 491 var argCount = argCounts[i]; 492 if (typeof argCount === 'undefined') { 493 continue; 494 } 495 var args = argStrings.slice(startIndex, startIndex + argCount); 496 497 startIndex += argCount; 498 takeAction(action, args); 499 } 500 501 // slice off the Positionals that we just parsed and return the 502 // index at which the Positionals' string args stopped 503 positionals = positionals.slice(argCounts.length); 504 return startIndex; 505 } 506 507 // consume Positionals and Optionals alternately, until we have 508 // passed the last option string 509 var startIndex = 0; 510 var position; 511 512 var maxOptionStringIndex = -1; 513 514 Object.keys(optionStringIndices).forEach(function (position) { 515 maxOptionStringIndex = Math.max(maxOptionStringIndex, parseInt(position, 10)); 516 }); 517 518 var positionalsEndIndex, nextOptionStringIndex; 519 520 while (startIndex <= maxOptionStringIndex) { 521 // consume any Positionals preceding the next option 522 nextOptionStringIndex = null; 523 for (position in optionStringIndices) { 524 if (!optionStringIndices.hasOwnProperty(position)) { continue; } 525 526 position = parseInt(position, 10); 527 if (position >= startIndex) { 528 if (nextOptionStringIndex !== null) { 529 nextOptionStringIndex = Math.min(nextOptionStringIndex, position); 530 } else { 531 nextOptionStringIndex = position; 532 } 533 } 534 } 535 536 if (startIndex !== nextOptionStringIndex) { 537 positionalsEndIndex = consumePositionals(startIndex); 538 // only try to parse the next optional if we didn't consume 539 // the option string during the positionals parsing 540 if (positionalsEndIndex > startIndex) { 541 startIndex = positionalsEndIndex; 542 continue; 543 } else { 544 startIndex = positionalsEndIndex; 545 } 546 } 547 548 // if we consumed all the positionals we could and we're not 549 // at the index of an option string, there were extra arguments 550 if (!optionStringIndices[startIndex]) { 551 var strings = argStrings.slice(startIndex, nextOptionStringIndex); 552 extras = extras.concat(strings); 553 startIndex = nextOptionStringIndex; 554 } 555 // consume the next optional and any arguments for it 556 startIndex = consumeOptional(startIndex); 557 } 558 559 // consume any positionals following the last Optional 560 var stopIndex = consumePositionals(startIndex); 561 562 // if we didn't consume all the argument strings, there were extras 563 extras = extras.concat(argStrings.slice(stopIndex)); 564 565 // if we didn't use all the Positional objects, there were too few 566 // arg strings supplied. 567 if (positionals.length > 0) { 568 self.error('too few arguments'); 569 } 570 571 // make sure all required actions were present 572 self._actions.forEach(function (action) { 573 if (action.required) { 574 if (seenActions.indexOf(action) < 0) { 575 self.error(format('Argument "%s" is required', action.getName())); 576 } 577 } 578 }); 579 580 // make sure all required groups have one option present 581 var actionUsed = false; 582 self._mutuallyExclusiveGroups.forEach(function (group) { 583 if (group.required) { 584 actionUsed = group._groupActions.some(function (action) { 585 return seenNonDefaultActions.indexOf(action) !== -1; 586 }); 587 588 // if no actions were used, report the error 589 if (!actionUsed) { 590 var names = []; 591 group._groupActions.forEach(function (action) { 592 if (action.help !== c.SUPPRESS) { 593 names.push(action.getName()); 594 } 595 }); 596 names = names.join(' '); 597 var msg = 'one of the arguments ' + names + ' is required'; 598 self.error(msg); 599 } 600 } 601 }); 602 603 // return the updated namespace and the extra arguments 604 return [ namespace, extras ]; 605 }; 606 607 ArgumentParser.prototype._readArgsFromFiles = function (argStrings) { 608 // expand arguments referencing files 609 var self = this; 610 var fs = require('fs'); 611 var newArgStrings = []; 612 argStrings.forEach(function (argString) { 613 if (self.fromfilePrefixChars.indexOf(argString[0]) < 0) { 614 // for regular arguments, just add them back into the list 615 newArgStrings.push(argString); 616 } else { 617 // replace arguments referencing files with the file content 618 try { 619 var argstrs = []; 620 var filename = argString.slice(1); 621 var content = fs.readFileSync(filename, 'utf8'); 622 content = content.trim().split('\n'); 623 content.forEach(function (argLine) { 624 self.convertArgLineToArgs(argLine).forEach(function (arg) { 625 argstrs.push(arg); 626 }); 627 argstrs = self._readArgsFromFiles(argstrs); 628 }); 629 newArgStrings.push.apply(newArgStrings, argstrs); 630 } catch (error) { 631 return self.error(error.message); 632 } 633 } 634 }); 635 return newArgStrings; 636 }; 637 638 ArgumentParser.prototype.convertArgLineToArgs = function (argLine) { 639 return [ argLine ]; 640 }; 641 642 ArgumentParser.prototype._matchArgument = function (action, regexpArgStrings) { 643 644 // match the pattern for this action to the arg strings 645 var regexpNargs = new RegExp('^' + this._getNargsPattern(action)); 646 var matches = regexpArgStrings.match(regexpNargs); 647 var message; 648 649 // throw an exception if we weren't able to find a match 650 if (!matches) { 651 switch (action.nargs) { 652 /*eslint-disable no-undefined*/ 653 case undefined: 654 case null: 655 message = 'Expected one argument.'; 656 break; 657 case c.OPTIONAL: 658 message = 'Expected at most one argument.'; 659 break; 660 case c.ONE_OR_MORE: 661 message = 'Expected at least one argument.'; 662 break; 663 default: 664 message = 'Expected %s argument(s)'; 665 } 666 667 throw argumentErrorHelper( 668 action, 669 format(message, action.nargs) 670 ); 671 } 672 // return the number of arguments matched 673 return matches[1].length; 674 }; 675 676 ArgumentParser.prototype._matchArgumentsPartial = function (actions, regexpArgStrings) { 677 // progressively shorten the actions list by slicing off the 678 // final actions until we find a match 679 var self = this; 680 var result = []; 681 var actionSlice, pattern, matches; 682 var i, j; 683 684 function getLength(string) { 685 return string.length; 686 } 687 688 for (i = actions.length; i > 0; i--) { 689 pattern = ''; 690 actionSlice = actions.slice(0, i); 691 for (j = 0; j < actionSlice.length; j++) { 692 pattern += self._getNargsPattern(actionSlice[j]); 693 } 694 695 pattern = new RegExp('^' + pattern); 696 matches = regexpArgStrings.match(pattern); 697 698 if (matches && matches.length > 0) { 699 // need only groups 700 matches = matches.splice(1); 701 result = result.concat(matches.map(getLength)); 702 break; 703 } 704 } 705 706 // return the list of arg string counts 707 return result; 708 }; 709 710 ArgumentParser.prototype._parseOptional = function (argString) { 711 var action, optionString, argExplicit, optionTuples; 712 713 // if it's an empty string, it was meant to be a positional 714 if (!argString) { 715 return null; 716 } 717 718 // if it doesn't start with a prefix, it was meant to be positional 719 if (this.prefixChars.indexOf(argString[0]) < 0) { 720 return null; 721 } 722 723 // if the option string is present in the parser, return the action 724 if (this._optionStringActions[argString]) { 725 return [ this._optionStringActions[argString], argString, null ]; 726 } 727 728 // if it's just a single character, it was meant to be positional 729 if (argString.length === 1) { 730 return null; 731 } 732 733 // if the option string before the "=" is present, return the action 734 if (argString.indexOf('=') >= 0) { 735 optionString = argString.split('=', 1)[0]; 736 argExplicit = argString.slice(optionString.length + 1); 737 738 if (this._optionStringActions[optionString]) { 739 action = this._optionStringActions[optionString]; 740 return [ action, optionString, argExplicit ]; 741 } 742 } 743 744 // search through all possible prefixes of the option string 745 // and all actions in the parser for possible interpretations 746 optionTuples = this._getOptionTuples(argString); 747 748 // if multiple actions match, the option string was ambiguous 749 if (optionTuples.length > 1) { 750 var optionStrings = optionTuples.map(function (optionTuple) { 751 return optionTuple[1]; 752 }); 753 this.error(format( 754 'Ambiguous option: "%s" could match %s.', 755 argString, optionStrings.join(', ') 756 )); 757 // if exactly one action matched, this segmentation is good, 758 // so return the parsed action 759 } else if (optionTuples.length === 1) { 760 return optionTuples[0]; 761 } 762 763 // if it was not found as an option, but it looks like a negative 764 // number, it was meant to be positional 765 // unless there are negative-number-like options 766 if (argString.match(this._regexpNegativeNumber)) { 767 if (!this._hasNegativeNumberOptionals.some(Boolean)) { 768 return null; 769 } 770 } 771 // if it contains a space, it was meant to be a positional 772 if (argString.search(' ') >= 0) { 773 return null; 774 } 775 776 // it was meant to be an optional but there is no such option 777 // in this parser (though it might be a valid option in a subparser) 778 return [ null, argString, null ]; 779 }; 780 781 ArgumentParser.prototype._getOptionTuples = function (optionString) { 782 var result = []; 783 var chars = this.prefixChars; 784 var optionPrefix; 785 var argExplicit; 786 var action; 787 var actionOptionString; 788 789 // option strings starting with two prefix characters are only split at 790 // the '=' 791 if (chars.indexOf(optionString[0]) >= 0 && chars.indexOf(optionString[1]) >= 0) { 792 if (optionString.indexOf('=') >= 0) { 793 var optionStringSplit = optionString.split('=', 1); 794 795 optionPrefix = optionStringSplit[0]; 796 argExplicit = optionStringSplit[1]; 797 } else { 798 optionPrefix = optionString; 799 argExplicit = null; 800 } 801 802 for (actionOptionString in this._optionStringActions) { 803 if (actionOptionString.substr(0, optionPrefix.length) === optionPrefix) { 804 action = this._optionStringActions[actionOptionString]; 805 result.push([ action, actionOptionString, argExplicit ]); 806 } 807 } 808 809 // single character options can be concatenated with their arguments 810 // but multiple character options always have to have their argument 811 // separate 812 } else if (chars.indexOf(optionString[0]) >= 0 && chars.indexOf(optionString[1]) < 0) { 813 optionPrefix = optionString; 814 argExplicit = null; 815 var optionPrefixShort = optionString.substr(0, 2); 816 var argExplicitShort = optionString.substr(2); 817 818 for (actionOptionString in this._optionStringActions) { 819 if (!$$.has(this._optionStringActions, actionOptionString)) continue; 820 821 action = this._optionStringActions[actionOptionString]; 822 if (actionOptionString === optionPrefixShort) { 823 result.push([ action, actionOptionString, argExplicitShort ]); 824 } else if (actionOptionString.substr(0, optionPrefix.length) === optionPrefix) { 825 result.push([ action, actionOptionString, argExplicit ]); 826 } 827 } 828 829 // shouldn't ever get here 830 } else { 831 throw new Error(format('Unexpected option string: %s.', optionString)); 832 } 833 // return the collected option tuples 834 return result; 835 }; 836 837 ArgumentParser.prototype._getNargsPattern = function (action) { 838 // in all examples below, we have to allow for '--' args 839 // which are represented as '-' in the pattern 840 var regexpNargs; 841 842 switch (action.nargs) { 843 // the default (null) is assumed to be a single argument 844 case undefined: 845 case null: 846 regexpNargs = '(-*A-*)'; 847 break; 848 // allow zero or more arguments 849 case c.OPTIONAL: 850 regexpNargs = '(-*A?-*)'; 851 break; 852 // allow zero or more arguments 853 case c.ZERO_OR_MORE: 854 regexpNargs = '(-*[A-]*)'; 855 break; 856 // allow one or more arguments 857 case c.ONE_OR_MORE: 858 regexpNargs = '(-*A[A-]*)'; 859 break; 860 // allow any number of options or arguments 861 case c.REMAINDER: 862 regexpNargs = '([-AO]*)'; 863 break; 864 // allow one argument followed by any number of options or arguments 865 case c.PARSER: 866 regexpNargs = '(-*A[-AO]*)'; 867 break; 868 // all others should be integers 869 default: 870 regexpNargs = '(-*' + $$.repeat('-*A', action.nargs) + '-*)'; 871 } 872 873 // if this is an optional action, -- is not allowed 874 if (action.isOptional()) { 875 regexpNargs = regexpNargs.replace(/-\*/g, ''); 876 regexpNargs = regexpNargs.replace(/-/g, ''); 877 } 878 879 // return the pattern 880 return regexpNargs; 881 }; 882 883 // 884 // Value conversion methods 885 // 886 887 ArgumentParser.prototype._getValues = function (action, argStrings) { 888 var self = this; 889 890 // for everything but PARSER args, strip out '--' 891 if (action.nargs !== c.PARSER && action.nargs !== c.REMAINDER) { 892 argStrings = argStrings.filter(function (arrayElement) { 893 return arrayElement !== '--'; 894 }); 895 } 896 897 var value, argString; 898 899 // optional argument produces a default when not present 900 if (argStrings.length === 0 && action.nargs === c.OPTIONAL) { 901 902 value = (action.isOptional()) ? action.constant : action.defaultValue; 903 904 if (typeof (value) === 'string') { 905 value = this._getValue(action, value); 906 this._checkValue(action, value); 907 } 908 909 // when nargs='*' on a positional, if there were no command-line 910 // args, use the default if it is anything other than None 911 } else if (argStrings.length === 0 && action.nargs === c.ZERO_OR_MORE && 912 action.optionStrings.length === 0) { 913 914 value = (action.defaultValue || argStrings); 915 this._checkValue(action, value); 916 917 // single argument or optional argument produces a single value 918 } else if (argStrings.length === 1 && 919 (!action.nargs || action.nargs === c.OPTIONAL)) { 920 921 argString = argStrings[0]; 922 value = this._getValue(action, argString); 923 this._checkValue(action, value); 924 925 // REMAINDER arguments convert all values, checking none 926 } else if (action.nargs === c.REMAINDER) { 927 value = argStrings.map(function (v) { 928 return self._getValue(action, v); 929 }); 930 931 // PARSER arguments convert all values, but check only the first 932 } else if (action.nargs === c.PARSER) { 933 value = argStrings.map(function (v) { 934 return self._getValue(action, v); 935 }); 936 this._checkValue(action, value[0]); 937 938 // all other types of nargs produce a list 939 } else { 940 value = argStrings.map(function (v) { 941 return self._getValue(action, v); 942 }); 943 value.forEach(function (v) { 944 self._checkValue(action, v); 945 }); 946 } 947 948 // return the converted value 949 return value; 950 }; 951 952 ArgumentParser.prototype._getValue = function (action, argString) { 953 var result; 954 955 var typeFunction = this._registryGet('type', action.type, action.type); 956 if (typeof typeFunction !== 'function') { 957 var message = format('%s is not callable', typeFunction); 958 throw argumentErrorHelper(action, message); 959 } 960 961 // convert the value to the appropriate type 962 try { 963 result = typeFunction(argString); 964 965 // ArgumentTypeErrors indicate errors 966 // If action.type is not a registered string, it is a function 967 // Try to deduce its name for inclusion in the error message 968 // Failing that, include the error message it raised. 969 } catch (e) { 970 var name = null; 971 if (typeof action.type === 'string') { 972 name = action.type; 973 } else { 974 name = action.type.name || action.type.displayName || '<function>'; 975 } 976 var msg = format('Invalid %s value: %s', name, argString); 977 if (name === '<function>') { msg += '\n' + e.message; } 978 throw argumentErrorHelper(action, msg); 979 } 980 // return the converted value 981 return result; 982 }; 983 984 ArgumentParser.prototype._checkValue = function (action, value) { 985 // converted value must be one of the choices (if specified) 986 var choices = action.choices; 987 if (choices) { 988 // choise for argument can by array or string 989 if ((typeof choices === 'string' || Array.isArray(choices)) && 990 choices.indexOf(value) !== -1) { 991 return; 992 } 993 // choise for subparsers can by only hash 994 if (typeof choices === 'object' && !Array.isArray(choices) && choices[value]) { 995 return; 996 } 997 998 if (typeof choices === 'string') { 999 choices = choices.split('').join(', '); 1000 } else if (Array.isArray(choices)) { 1001 choices = choices.join(', '); 1002 } else { 1003 choices = Object.keys(choices).join(', '); 1004 } 1005 var message = format('Invalid choice: %s (choose from [%s])', value, choices); 1006 throw argumentErrorHelper(action, message); 1007 } 1008 }; 1009 1010 // 1011 // Help formatting methods 1012 // 1013 1014 /** 1015 * ArgumentParser#formatUsage -> string 1016 * 1017 * Return usage string 1018 * 1019 * See also [original guide][1] 1020 * 1021 * [1]:http://docs.python.org/dev/library/argparse.html#printing-help 1022 **/ 1023 ArgumentParser.prototype.formatUsage = function () { 1024 var formatter = this._getFormatter(); 1025 formatter.addUsage(this.usage, this._actions, this._mutuallyExclusiveGroups); 1026 return formatter.formatHelp(); 1027 }; 1028 1029 /** 1030 * ArgumentParser#formatHelp -> string 1031 * 1032 * Return help 1033 * 1034 * See also [original guide][1] 1035 * 1036 * [1]:http://docs.python.org/dev/library/argparse.html#printing-help 1037 **/ 1038 ArgumentParser.prototype.formatHelp = function () { 1039 var formatter = this._getFormatter(); 1040 1041 // usage 1042 formatter.addUsage(this.usage, this._actions, this._mutuallyExclusiveGroups); 1043 1044 // description 1045 formatter.addText(this.description); 1046 1047 // positionals, optionals and user-defined groups 1048 this._actionGroups.forEach(function (actionGroup) { 1049 formatter.startSection(actionGroup.title); 1050 formatter.addText(actionGroup.description); 1051 formatter.addArguments(actionGroup._groupActions); 1052 formatter.endSection(); 1053 }); 1054 1055 // epilog 1056 formatter.addText(this.epilog); 1057 1058 // determine help from format above 1059 return formatter.formatHelp(); 1060 }; 1061 1062 ArgumentParser.prototype._getFormatter = function () { 1063 var FormatterClass = this.formatterClass; 1064 var formatter = new FormatterClass({ prog: this.prog }); 1065 return formatter; 1066 }; 1067 1068 // 1069 // Print functions 1070 // 1071 1072 /** 1073 * ArgumentParser#printUsage() -> Void 1074 * 1075 * Print usage 1076 * 1077 * See also [original guide][1] 1078 * 1079 * [1]:http://docs.python.org/dev/library/argparse.html#printing-help 1080 **/ 1081 ArgumentParser.prototype.printUsage = function () { 1082 this._printMessage(this.formatUsage()); 1083 }; 1084 1085 /** 1086 * ArgumentParser#printHelp() -> Void 1087 * 1088 * Print help 1089 * 1090 * See also [original guide][1] 1091 * 1092 * [1]:http://docs.python.org/dev/library/argparse.html#printing-help 1093 **/ 1094 ArgumentParser.prototype.printHelp = function () { 1095 this._printMessage(this.formatHelp()); 1096 }; 1097 1098 ArgumentParser.prototype._printMessage = function (message, stream) { 1099 if (!stream) { 1100 stream = process.stdout; 1101 } 1102 if (message) { 1103 stream.write('' + message); 1104 } 1105 }; 1106 1107 // 1108 // Exit functions 1109 // 1110 1111 /** 1112 * ArgumentParser#exit(status=0, message) -> Void 1113 * - status (int): exit status 1114 * - message (string): message 1115 * 1116 * Print message in stderr/stdout and exit program 1117 **/ 1118 ArgumentParser.prototype.exit = function (status, message) { 1119 if (message) { 1120 if (status === 0) { 1121 this._printMessage(message); 1122 } else { 1123 this._printMessage(message, process.stderr); 1124 } 1125 } 1126 1127 process.exit(status); 1128 }; 1129 1130 /** 1131 * ArgumentParser#error(message) -> Void 1132 * - err (Error|string): message 1133 * 1134 * Error method Prints a usage message incorporating the message to stderr and 1135 * exits. If you override this in a subclass, 1136 * it should not return -- it should 1137 * either exit or throw an exception. 1138 * 1139 **/ 1140 ArgumentParser.prototype.error = function (err) { 1141 var message; 1142 if (err instanceof Error) { 1143 if (this.debug === true) { 1144 throw err; 1145 } 1146 message = err.message; 1147 } else { 1148 message = err; 1149 } 1150 var msg = format('%s: error: %s', this.prog, message) + c.EOL; 1151 1152 if (this.debug === true) { 1153 throw new Error(msg); 1154 } 1155 1156 this.printUsage(process.stderr); 1157 1158 return this.exit(2, msg); 1159 }; 1160 1161 module.exports = ArgumentParser;