Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80713 views
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
};
1169
1170