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