l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

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;