You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1161 lines
34 KiB

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