argument_parser.js
   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;