Geen omschrijving

argument_parser.js 34KB

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