yargs-parser.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. /**
  2. * @license
  3. * Copyright (c) 2016, Contributors
  4. * SPDX-License-Identifier: ISC
  5. */
  6. import { tokenizeArgString } from './tokenize-arg-string.js';
  7. import { DefaultValuesForTypeKey } from './yargs-parser-types.js';
  8. import { camelCase, decamelize, looksLikeNumber } from './string-utils.js';
  9. let mixin;
  10. export class YargsParser {
  11. constructor(_mixin) {
  12. mixin = _mixin;
  13. }
  14. parse(argsInput, options) {
  15. const opts = Object.assign({
  16. alias: undefined,
  17. array: undefined,
  18. boolean: undefined,
  19. config: undefined,
  20. configObjects: undefined,
  21. configuration: undefined,
  22. coerce: undefined,
  23. count: undefined,
  24. default: undefined,
  25. envPrefix: undefined,
  26. narg: undefined,
  27. normalize: undefined,
  28. string: undefined,
  29. number: undefined,
  30. __: undefined,
  31. key: undefined
  32. }, options);
  33. // allow a string argument to be passed in rather
  34. // than an argv array.
  35. const args = tokenizeArgString(argsInput);
  36. // aliases might have transitive relationships, normalize this.
  37. const aliases = combineAliases(Object.assign(Object.create(null), opts.alias));
  38. const configuration = Object.assign({
  39. 'boolean-negation': true,
  40. 'camel-case-expansion': true,
  41. 'combine-arrays': false,
  42. 'dot-notation': true,
  43. 'duplicate-arguments-array': true,
  44. 'flatten-duplicate-arrays': true,
  45. 'greedy-arrays': true,
  46. 'halt-at-non-option': false,
  47. 'nargs-eats-options': false,
  48. 'negation-prefix': 'no-',
  49. 'parse-numbers': true,
  50. 'parse-positional-numbers': true,
  51. 'populate--': false,
  52. 'set-placeholder-key': false,
  53. 'short-option-groups': true,
  54. 'strip-aliased': false,
  55. 'strip-dashed': false,
  56. 'unknown-options-as-args': false
  57. }, opts.configuration);
  58. const defaults = Object.assign(Object.create(null), opts.default);
  59. const configObjects = opts.configObjects || [];
  60. const envPrefix = opts.envPrefix;
  61. const notFlagsOption = configuration['populate--'];
  62. const notFlagsArgv = notFlagsOption ? '--' : '_';
  63. const newAliases = Object.create(null);
  64. const defaulted = Object.create(null);
  65. // allow a i18n handler to be passed in, default to a fake one (util.format).
  66. const __ = opts.__ || mixin.format;
  67. const flags = {
  68. aliases: Object.create(null),
  69. arrays: Object.create(null),
  70. bools: Object.create(null),
  71. strings: Object.create(null),
  72. numbers: Object.create(null),
  73. counts: Object.create(null),
  74. normalize: Object.create(null),
  75. configs: Object.create(null),
  76. nargs: Object.create(null),
  77. coercions: Object.create(null),
  78. keys: []
  79. };
  80. const negative = /^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/;
  81. const negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)');
  82. [].concat(opts.array || []).filter(Boolean).forEach(function (opt) {
  83. const key = typeof opt === 'object' ? opt.key : opt;
  84. // assign to flags[bools|strings|numbers]
  85. const assignment = Object.keys(opt).map(function (key) {
  86. const arrayFlagKeys = {
  87. boolean: 'bools',
  88. string: 'strings',
  89. number: 'numbers'
  90. };
  91. return arrayFlagKeys[key];
  92. }).filter(Boolean).pop();
  93. // assign key to be coerced
  94. if (assignment) {
  95. flags[assignment][key] = true;
  96. }
  97. flags.arrays[key] = true;
  98. flags.keys.push(key);
  99. });
  100. [].concat(opts.boolean || []).filter(Boolean).forEach(function (key) {
  101. flags.bools[key] = true;
  102. flags.keys.push(key);
  103. });
  104. [].concat(opts.string || []).filter(Boolean).forEach(function (key) {
  105. flags.strings[key] = true;
  106. flags.keys.push(key);
  107. });
  108. [].concat(opts.number || []).filter(Boolean).forEach(function (key) {
  109. flags.numbers[key] = true;
  110. flags.keys.push(key);
  111. });
  112. [].concat(opts.count || []).filter(Boolean).forEach(function (key) {
  113. flags.counts[key] = true;
  114. flags.keys.push(key);
  115. });
  116. [].concat(opts.normalize || []).filter(Boolean).forEach(function (key) {
  117. flags.normalize[key] = true;
  118. flags.keys.push(key);
  119. });
  120. if (typeof opts.narg === 'object') {
  121. Object.entries(opts.narg).forEach(([key, value]) => {
  122. if (typeof value === 'number') {
  123. flags.nargs[key] = value;
  124. flags.keys.push(key);
  125. }
  126. });
  127. }
  128. if (typeof opts.coerce === 'object') {
  129. Object.entries(opts.coerce).forEach(([key, value]) => {
  130. if (typeof value === 'function') {
  131. flags.coercions[key] = value;
  132. flags.keys.push(key);
  133. }
  134. });
  135. }
  136. if (typeof opts.config !== 'undefined') {
  137. if (Array.isArray(opts.config) || typeof opts.config === 'string') {
  138. ;
  139. [].concat(opts.config).filter(Boolean).forEach(function (key) {
  140. flags.configs[key] = true;
  141. });
  142. }
  143. else if (typeof opts.config === 'object') {
  144. Object.entries(opts.config).forEach(([key, value]) => {
  145. if (typeof value === 'boolean' || typeof value === 'function') {
  146. flags.configs[key] = value;
  147. }
  148. });
  149. }
  150. }
  151. // create a lookup table that takes into account all
  152. // combinations of aliases: {f: ['foo'], foo: ['f']}
  153. extendAliases(opts.key, aliases, opts.default, flags.arrays);
  154. // apply default values to all aliases.
  155. Object.keys(defaults).forEach(function (key) {
  156. (flags.aliases[key] || []).forEach(function (alias) {
  157. defaults[alias] = defaults[key];
  158. });
  159. });
  160. let error = null;
  161. checkConfiguration();
  162. let notFlags = [];
  163. const argv = Object.assign(Object.create(null), { _: [] });
  164. // TODO(bcoe): for the first pass at removing object prototype we didn't
  165. // remove all prototypes from objects returned by this API, we might want
  166. // to gradually move towards doing so.
  167. const argvReturn = {};
  168. for (let i = 0; i < args.length; i++) {
  169. const arg = args[i];
  170. const truncatedArg = arg.replace(/^-{3,}/, '---');
  171. let broken;
  172. let key;
  173. let letters;
  174. let m;
  175. let next;
  176. let value;
  177. // any unknown option (except for end-of-options, "--")
  178. if (arg !== '--' && isUnknownOptionAsArg(arg)) {
  179. pushPositional(arg);
  180. // ---, ---=, ----, etc,
  181. }
  182. else if (truncatedArg.match(/---+(=|$)/)) {
  183. // options without key name are invalid.
  184. pushPositional(arg);
  185. continue;
  186. // -- separated by =
  187. }
  188. else if (arg.match(/^--.+=/) || (!configuration['short-option-groups'] && arg.match(/^-.+=/))) {
  189. // Using [\s\S] instead of . because js doesn't support the
  190. // 'dotall' regex modifier. See:
  191. // http://stackoverflow.com/a/1068308/13216
  192. m = arg.match(/^--?([^=]+)=([\s\S]*)$/);
  193. // arrays format = '--f=a b c'
  194. if (m !== null && Array.isArray(m) && m.length >= 3) {
  195. if (checkAllAliases(m[1], flags.arrays)) {
  196. i = eatArray(i, m[1], args, m[2]);
  197. }
  198. else if (checkAllAliases(m[1], flags.nargs) !== false) {
  199. // nargs format = '--f=monkey washing cat'
  200. i = eatNargs(i, m[1], args, m[2]);
  201. }
  202. else {
  203. setArg(m[1], m[2]);
  204. }
  205. }
  206. }
  207. else if (arg.match(negatedBoolean) && configuration['boolean-negation']) {
  208. m = arg.match(negatedBoolean);
  209. if (m !== null && Array.isArray(m) && m.length >= 2) {
  210. key = m[1];
  211. setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false);
  212. }
  213. // -- separated by space.
  214. }
  215. else if (arg.match(/^--.+/) || (!configuration['short-option-groups'] && arg.match(/^-[^-]+/))) {
  216. m = arg.match(/^--?(.+)/);
  217. if (m !== null && Array.isArray(m) && m.length >= 2) {
  218. key = m[1];
  219. if (checkAllAliases(key, flags.arrays)) {
  220. // array format = '--foo a b c'
  221. i = eatArray(i, key, args);
  222. }
  223. else if (checkAllAliases(key, flags.nargs) !== false) {
  224. // nargs format = '--foo a b c'
  225. // should be truthy even if: flags.nargs[key] === 0
  226. i = eatNargs(i, key, args);
  227. }
  228. else {
  229. next = args[i + 1];
  230. if (next !== undefined && (!next.match(/^-/) ||
  231. next.match(negative)) &&
  232. !checkAllAliases(key, flags.bools) &&
  233. !checkAllAliases(key, flags.counts)) {
  234. setArg(key, next);
  235. i++;
  236. }
  237. else if (/^(true|false)$/.test(next)) {
  238. setArg(key, next);
  239. i++;
  240. }
  241. else {
  242. setArg(key, defaultValue(key));
  243. }
  244. }
  245. }
  246. // dot-notation flag separated by '='.
  247. }
  248. else if (arg.match(/^-.\..+=/)) {
  249. m = arg.match(/^-([^=]+)=([\s\S]*)$/);
  250. if (m !== null && Array.isArray(m) && m.length >= 3) {
  251. setArg(m[1], m[2]);
  252. }
  253. // dot-notation flag separated by space.
  254. }
  255. else if (arg.match(/^-.\..+/) && !arg.match(negative)) {
  256. next = args[i + 1];
  257. m = arg.match(/^-(.\..+)/);
  258. if (m !== null && Array.isArray(m) && m.length >= 2) {
  259. key = m[1];
  260. if (next !== undefined && !next.match(/^-/) &&
  261. !checkAllAliases(key, flags.bools) &&
  262. !checkAllAliases(key, flags.counts)) {
  263. setArg(key, next);
  264. i++;
  265. }
  266. else {
  267. setArg(key, defaultValue(key));
  268. }
  269. }
  270. }
  271. else if (arg.match(/^-[^-]+/) && !arg.match(negative)) {
  272. letters = arg.slice(1, -1).split('');
  273. broken = false;
  274. for (let j = 0; j < letters.length; j++) {
  275. next = arg.slice(j + 2);
  276. if (letters[j + 1] && letters[j + 1] === '=') {
  277. value = arg.slice(j + 3);
  278. key = letters[j];
  279. if (checkAllAliases(key, flags.arrays)) {
  280. // array format = '-f=a b c'
  281. i = eatArray(i, key, args, value);
  282. }
  283. else if (checkAllAliases(key, flags.nargs) !== false) {
  284. // nargs format = '-f=monkey washing cat'
  285. i = eatNargs(i, key, args, value);
  286. }
  287. else {
  288. setArg(key, value);
  289. }
  290. broken = true;
  291. break;
  292. }
  293. if (next === '-') {
  294. setArg(letters[j], next);
  295. continue;
  296. }
  297. // current letter is an alphabetic character and next value is a number
  298. if (/[A-Za-z]/.test(letters[j]) &&
  299. /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next) &&
  300. checkAllAliases(next, flags.bools) === false) {
  301. setArg(letters[j], next);
  302. broken = true;
  303. break;
  304. }
  305. if (letters[j + 1] && letters[j + 1].match(/\W/)) {
  306. setArg(letters[j], next);
  307. broken = true;
  308. break;
  309. }
  310. else {
  311. setArg(letters[j], defaultValue(letters[j]));
  312. }
  313. }
  314. key = arg.slice(-1)[0];
  315. if (!broken && key !== '-') {
  316. if (checkAllAliases(key, flags.arrays)) {
  317. // array format = '-f a b c'
  318. i = eatArray(i, key, args);
  319. }
  320. else if (checkAllAliases(key, flags.nargs) !== false) {
  321. // nargs format = '-f a b c'
  322. // should be truthy even if: flags.nargs[key] === 0
  323. i = eatNargs(i, key, args);
  324. }
  325. else {
  326. next = args[i + 1];
  327. if (next !== undefined && (!/^(-|--)[^-]/.test(next) ||
  328. next.match(negative)) &&
  329. !checkAllAliases(key, flags.bools) &&
  330. !checkAllAliases(key, flags.counts)) {
  331. setArg(key, next);
  332. i++;
  333. }
  334. else if (/^(true|false)$/.test(next)) {
  335. setArg(key, next);
  336. i++;
  337. }
  338. else {
  339. setArg(key, defaultValue(key));
  340. }
  341. }
  342. }
  343. }
  344. else if (arg.match(/^-[0-9]$/) &&
  345. arg.match(negative) &&
  346. checkAllAliases(arg.slice(1), flags.bools)) {
  347. // single-digit boolean alias, e.g: xargs -0
  348. key = arg.slice(1);
  349. setArg(key, defaultValue(key));
  350. }
  351. else if (arg === '--') {
  352. notFlags = args.slice(i + 1);
  353. break;
  354. }
  355. else if (configuration['halt-at-non-option']) {
  356. notFlags = args.slice(i);
  357. break;
  358. }
  359. else {
  360. pushPositional(arg);
  361. }
  362. }
  363. // order of precedence:
  364. // 1. command line arg
  365. // 2. value from env var
  366. // 3. value from config file
  367. // 4. value from config objects
  368. // 5. configured default value
  369. applyEnvVars(argv, true); // special case: check env vars that point to config file
  370. applyEnvVars(argv, false);
  371. setConfig(argv);
  372. setConfigObjects();
  373. applyDefaultsAndAliases(argv, flags.aliases, defaults, true);
  374. applyCoercions(argv);
  375. if (configuration['set-placeholder-key'])
  376. setPlaceholderKeys(argv);
  377. // for any counts either not in args or without an explicit default, set to 0
  378. Object.keys(flags.counts).forEach(function (key) {
  379. if (!hasKey(argv, key.split('.')))
  380. setArg(key, 0);
  381. });
  382. // '--' defaults to undefined.
  383. if (notFlagsOption && notFlags.length)
  384. argv[notFlagsArgv] = [];
  385. notFlags.forEach(function (key) {
  386. argv[notFlagsArgv].push(key);
  387. });
  388. if (configuration['camel-case-expansion'] && configuration['strip-dashed']) {
  389. Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => {
  390. delete argv[key];
  391. });
  392. }
  393. if (configuration['strip-aliased']) {
  394. ;
  395. [].concat(...Object.keys(aliases).map(k => aliases[k])).forEach(alias => {
  396. if (configuration['camel-case-expansion'] && alias.includes('-')) {
  397. delete argv[alias.split('.').map(prop => camelCase(prop)).join('.')];
  398. }
  399. delete argv[alias];
  400. });
  401. }
  402. // Push argument into positional array, applying numeric coercion:
  403. function pushPositional(arg) {
  404. const maybeCoercedNumber = maybeCoerceNumber('_', arg);
  405. if (typeof maybeCoercedNumber === 'string' || typeof maybeCoercedNumber === 'number') {
  406. argv._.push(maybeCoercedNumber);
  407. }
  408. }
  409. // how many arguments should we consume, based
  410. // on the nargs option?
  411. function eatNargs(i, key, args, argAfterEqualSign) {
  412. let ii;
  413. let toEat = checkAllAliases(key, flags.nargs);
  414. // NaN has a special meaning for the array type, indicating that one or
  415. // more values are expected.
  416. toEat = typeof toEat !== 'number' || isNaN(toEat) ? 1 : toEat;
  417. if (toEat === 0) {
  418. if (!isUndefined(argAfterEqualSign)) {
  419. error = Error(__('Argument unexpected for: %s', key));
  420. }
  421. setArg(key, defaultValue(key));
  422. return i;
  423. }
  424. let available = isUndefined(argAfterEqualSign) ? 0 : 1;
  425. if (configuration['nargs-eats-options']) {
  426. // classic behavior, yargs eats positional and dash arguments.
  427. if (args.length - (i + 1) + available < toEat) {
  428. error = Error(__('Not enough arguments following: %s', key));
  429. }
  430. available = toEat;
  431. }
  432. else {
  433. // nargs will not consume flag arguments, e.g., -abc, --foo,
  434. // and terminates when one is observed.
  435. for (ii = i + 1; ii < args.length; ii++) {
  436. if (!args[ii].match(/^-[^0-9]/) || args[ii].match(negative) || isUnknownOptionAsArg(args[ii]))
  437. available++;
  438. else
  439. break;
  440. }
  441. if (available < toEat)
  442. error = Error(__('Not enough arguments following: %s', key));
  443. }
  444. let consumed = Math.min(available, toEat);
  445. if (!isUndefined(argAfterEqualSign) && consumed > 0) {
  446. setArg(key, argAfterEqualSign);
  447. consumed--;
  448. }
  449. for (ii = i + 1; ii < (consumed + i + 1); ii++) {
  450. setArg(key, args[ii]);
  451. }
  452. return (i + consumed);
  453. }
  454. // if an option is an array, eat all non-hyphenated arguments
  455. // following it... YUM!
  456. // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
  457. function eatArray(i, key, args, argAfterEqualSign) {
  458. let argsToSet = [];
  459. let next = argAfterEqualSign || args[i + 1];
  460. // If both array and nargs are configured, enforce the nargs count:
  461. const nargsCount = checkAllAliases(key, flags.nargs);
  462. if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) {
  463. argsToSet.push(true);
  464. }
  465. else if (isUndefined(next) ||
  466. (isUndefined(argAfterEqualSign) && /^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))) {
  467. // for keys without value ==> argsToSet remains an empty []
  468. // set user default value, if available
  469. if (defaults[key] !== undefined) {
  470. const defVal = defaults[key];
  471. argsToSet = Array.isArray(defVal) ? defVal : [defVal];
  472. }
  473. }
  474. else {
  475. // value in --option=value is eaten as is
  476. if (!isUndefined(argAfterEqualSign)) {
  477. argsToSet.push(processValue(key, argAfterEqualSign));
  478. }
  479. for (let ii = i + 1; ii < args.length; ii++) {
  480. if ((!configuration['greedy-arrays'] && argsToSet.length > 0) ||
  481. (nargsCount && typeof nargsCount === 'number' && argsToSet.length >= nargsCount))
  482. break;
  483. next = args[ii];
  484. if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next))
  485. break;
  486. i = ii;
  487. argsToSet.push(processValue(key, next));
  488. }
  489. }
  490. // If both array and nargs are configured, create an error if less than
  491. // nargs positionals were found. NaN has special meaning, indicating
  492. // that at least one value is required (more are okay).
  493. if (typeof nargsCount === 'number' && ((nargsCount && argsToSet.length < nargsCount) ||
  494. (isNaN(nargsCount) && argsToSet.length === 0))) {
  495. error = Error(__('Not enough arguments following: %s', key));
  496. }
  497. setArg(key, argsToSet);
  498. return i;
  499. }
  500. function setArg(key, val) {
  501. if (/-/.test(key) && configuration['camel-case-expansion']) {
  502. const alias = key.split('.').map(function (prop) {
  503. return camelCase(prop);
  504. }).join('.');
  505. addNewAlias(key, alias);
  506. }
  507. const value = processValue(key, val);
  508. const splitKey = key.split('.');
  509. setKey(argv, splitKey, value);
  510. // handle populating aliases of the full key
  511. if (flags.aliases[key]) {
  512. flags.aliases[key].forEach(function (x) {
  513. const keyProperties = x.split('.');
  514. setKey(argv, keyProperties, value);
  515. });
  516. }
  517. // handle populating aliases of the first element of the dot-notation key
  518. if (splitKey.length > 1 && configuration['dot-notation']) {
  519. ;
  520. (flags.aliases[splitKey[0]] || []).forEach(function (x) {
  521. let keyProperties = x.split('.');
  522. // expand alias with nested objects in key
  523. const a = [].concat(splitKey);
  524. a.shift(); // nuke the old key.
  525. keyProperties = keyProperties.concat(a);
  526. // populate alias only if is not already an alias of the full key
  527. // (already populated above)
  528. if (!(flags.aliases[key] || []).includes(keyProperties.join('.'))) {
  529. setKey(argv, keyProperties, value);
  530. }
  531. });
  532. }
  533. // Set normalize getter and setter when key is in 'normalize' but isn't an array
  534. if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) {
  535. const keys = [key].concat(flags.aliases[key] || []);
  536. keys.forEach(function (key) {
  537. Object.defineProperty(argvReturn, key, {
  538. enumerable: true,
  539. get() {
  540. return val;
  541. },
  542. set(value) {
  543. val = typeof value === 'string' ? mixin.normalize(value) : value;
  544. }
  545. });
  546. });
  547. }
  548. }
  549. function addNewAlias(key, alias) {
  550. if (!(flags.aliases[key] && flags.aliases[key].length)) {
  551. flags.aliases[key] = [alias];
  552. newAliases[alias] = true;
  553. }
  554. if (!(flags.aliases[alias] && flags.aliases[alias].length)) {
  555. addNewAlias(alias, key);
  556. }
  557. }
  558. function processValue(key, val) {
  559. // strings may be quoted, clean this up as we assign values.
  560. if (typeof val === 'string' &&
  561. (val[0] === "'" || val[0] === '"') &&
  562. val[val.length - 1] === val[0]) {
  563. val = val.substring(1, val.length - 1);
  564. }
  565. // handle parsing boolean arguments --foo=true --bar false.
  566. if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) {
  567. if (typeof val === 'string')
  568. val = val === 'true';
  569. }
  570. let value = Array.isArray(val)
  571. ? val.map(function (v) { return maybeCoerceNumber(key, v); })
  572. : maybeCoerceNumber(key, val);
  573. // increment a count given as arg (either no value or value parsed as boolean)
  574. if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) {
  575. value = increment();
  576. }
  577. // Set normalized value when key is in 'normalize' and in 'arrays'
  578. if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) {
  579. if (Array.isArray(val))
  580. value = val.map((val) => { return mixin.normalize(val); });
  581. else
  582. value = mixin.normalize(val);
  583. }
  584. return value;
  585. }
  586. function maybeCoerceNumber(key, value) {
  587. if (!configuration['parse-positional-numbers'] && key === '_')
  588. return value;
  589. if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.bools) && !Array.isArray(value)) {
  590. const shouldCoerceNumber = looksLikeNumber(value) && configuration['parse-numbers'] && (Number.isSafeInteger(Math.floor(parseFloat(`${value}`))));
  591. if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) {
  592. value = Number(value);
  593. }
  594. }
  595. return value;
  596. }
  597. // set args from config.json file, this should be
  598. // applied last so that defaults can be applied.
  599. function setConfig(argv) {
  600. const configLookup = Object.create(null);
  601. // expand defaults/aliases, in-case any happen to reference
  602. // the config.json file.
  603. applyDefaultsAndAliases(configLookup, flags.aliases, defaults);
  604. Object.keys(flags.configs).forEach(function (configKey) {
  605. const configPath = argv[configKey] || configLookup[configKey];
  606. if (configPath) {
  607. try {
  608. let config = null;
  609. const resolvedConfigPath = mixin.resolve(mixin.cwd(), configPath);
  610. const resolveConfig = flags.configs[configKey];
  611. if (typeof resolveConfig === 'function') {
  612. try {
  613. config = resolveConfig(resolvedConfigPath);
  614. }
  615. catch (e) {
  616. config = e;
  617. }
  618. if (config instanceof Error) {
  619. error = config;
  620. return;
  621. }
  622. }
  623. else {
  624. config = mixin.require(resolvedConfigPath);
  625. }
  626. setConfigObject(config);
  627. }
  628. catch (ex) {
  629. // Deno will receive a PermissionDenied error if an attempt is
  630. // made to load config without the --allow-read flag:
  631. if (ex.name === 'PermissionDenied')
  632. error = ex;
  633. else if (argv[configKey])
  634. error = Error(__('Invalid JSON config file: %s', configPath));
  635. }
  636. }
  637. });
  638. }
  639. // set args from config object.
  640. // it recursively checks nested objects.
  641. function setConfigObject(config, prev) {
  642. Object.keys(config).forEach(function (key) {
  643. const value = config[key];
  644. const fullKey = prev ? prev + '.' + key : key;
  645. // if the value is an inner object and we have dot-notation
  646. // enabled, treat inner objects in config the same as
  647. // heavily nested dot notations (foo.bar.apple).
  648. if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) {
  649. // if the value is an object but not an array, check nested object
  650. setConfigObject(value, fullKey);
  651. }
  652. else {
  653. // setting arguments via CLI takes precedence over
  654. // values within the config file.
  655. if (!hasKey(argv, fullKey.split('.')) || (checkAllAliases(fullKey, flags.arrays) && configuration['combine-arrays'])) {
  656. setArg(fullKey, value);
  657. }
  658. }
  659. });
  660. }
  661. // set all config objects passed in opts
  662. function setConfigObjects() {
  663. if (typeof configObjects !== 'undefined') {
  664. configObjects.forEach(function (configObject) {
  665. setConfigObject(configObject);
  666. });
  667. }
  668. }
  669. function applyEnvVars(argv, configOnly) {
  670. if (typeof envPrefix === 'undefined')
  671. return;
  672. const prefix = typeof envPrefix === 'string' ? envPrefix : '';
  673. const env = mixin.env();
  674. Object.keys(env).forEach(function (envVar) {
  675. if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) {
  676. // get array of nested keys and convert them to camel case
  677. const keys = envVar.split('__').map(function (key, i) {
  678. if (i === 0) {
  679. key = key.substring(prefix.length);
  680. }
  681. return camelCase(key);
  682. });
  683. if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && !hasKey(argv, keys)) {
  684. setArg(keys.join('.'), env[envVar]);
  685. }
  686. }
  687. });
  688. }
  689. function applyCoercions(argv) {
  690. let coerce;
  691. const applied = new Set();
  692. Object.keys(argv).forEach(function (key) {
  693. if (!applied.has(key)) { // If we haven't already coerced this option via one of its aliases
  694. coerce = checkAllAliases(key, flags.coercions);
  695. if (typeof coerce === 'function') {
  696. try {
  697. const value = maybeCoerceNumber(key, coerce(argv[key]));
  698. ([].concat(flags.aliases[key] || [], key)).forEach(ali => {
  699. applied.add(ali);
  700. argv[ali] = value;
  701. });
  702. }
  703. catch (err) {
  704. error = err;
  705. }
  706. }
  707. }
  708. });
  709. }
  710. function setPlaceholderKeys(argv) {
  711. flags.keys.forEach((key) => {
  712. // don't set placeholder keys for dot notation options 'foo.bar'.
  713. if (~key.indexOf('.'))
  714. return;
  715. if (typeof argv[key] === 'undefined')
  716. argv[key] = undefined;
  717. });
  718. return argv;
  719. }
  720. function applyDefaultsAndAliases(obj, aliases, defaults, canLog = false) {
  721. Object.keys(defaults).forEach(function (key) {
  722. if (!hasKey(obj, key.split('.'))) {
  723. setKey(obj, key.split('.'), defaults[key]);
  724. if (canLog)
  725. defaulted[key] = true;
  726. (aliases[key] || []).forEach(function (x) {
  727. if (hasKey(obj, x.split('.')))
  728. return;
  729. setKey(obj, x.split('.'), defaults[key]);
  730. });
  731. }
  732. });
  733. }
  734. function hasKey(obj, keys) {
  735. let o = obj;
  736. if (!configuration['dot-notation'])
  737. keys = [keys.join('.')];
  738. keys.slice(0, -1).forEach(function (key) {
  739. o = (o[key] || {});
  740. });
  741. const key = keys[keys.length - 1];
  742. if (typeof o !== 'object')
  743. return false;
  744. else
  745. return key in o;
  746. }
  747. function setKey(obj, keys, value) {
  748. let o = obj;
  749. if (!configuration['dot-notation'])
  750. keys = [keys.join('.')];
  751. keys.slice(0, -1).forEach(function (key) {
  752. // TODO(bcoe): in the next major version of yargs, switch to
  753. // Object.create(null) for dot notation:
  754. key = sanitizeKey(key);
  755. if (typeof o === 'object' && o[key] === undefined) {
  756. o[key] = {};
  757. }
  758. if (typeof o[key] !== 'object' || Array.isArray(o[key])) {
  759. // ensure that o[key] is an array, and that the last item is an empty object.
  760. if (Array.isArray(o[key])) {
  761. o[key].push({});
  762. }
  763. else {
  764. o[key] = [o[key], {}];
  765. }
  766. // we want to update the empty object at the end of the o[key] array, so set o to that object
  767. o = o[key][o[key].length - 1];
  768. }
  769. else {
  770. o = o[key];
  771. }
  772. });
  773. // TODO(bcoe): in the next major version of yargs, switch to
  774. // Object.create(null) for dot notation:
  775. const key = sanitizeKey(keys[keys.length - 1]);
  776. const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays);
  777. const isValueArray = Array.isArray(value);
  778. let duplicate = configuration['duplicate-arguments-array'];
  779. // nargs has higher priority than duplicate
  780. if (!duplicate && checkAllAliases(key, flags.nargs)) {
  781. duplicate = true;
  782. if ((!isUndefined(o[key]) && flags.nargs[key] === 1) || (Array.isArray(o[key]) && o[key].length === flags.nargs[key])) {
  783. o[key] = undefined;
  784. }
  785. }
  786. if (value === increment()) {
  787. o[key] = increment(o[key]);
  788. }
  789. else if (Array.isArray(o[key])) {
  790. if (duplicate && isTypeArray && isValueArray) {
  791. o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]);
  792. }
  793. else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) {
  794. o[key] = value;
  795. }
  796. else {
  797. o[key] = o[key].concat([value]);
  798. }
  799. }
  800. else if (o[key] === undefined && isTypeArray) {
  801. o[key] = isValueArray ? value : [value];
  802. }
  803. else if (duplicate && !(o[key] === undefined ||
  804. checkAllAliases(key, flags.counts) ||
  805. checkAllAliases(key, flags.bools))) {
  806. o[key] = [o[key], value];
  807. }
  808. else {
  809. o[key] = value;
  810. }
  811. }
  812. // extend the aliases list with inferred aliases.
  813. function extendAliases(...args) {
  814. args.forEach(function (obj) {
  815. Object.keys(obj || {}).forEach(function (key) {
  816. // short-circuit if we've already added a key
  817. // to the aliases array, for example it might
  818. // exist in both 'opts.default' and 'opts.key'.
  819. if (flags.aliases[key])
  820. return;
  821. flags.aliases[key] = [].concat(aliases[key] || []);
  822. // For "--option-name", also set argv.optionName
  823. flags.aliases[key].concat(key).forEach(function (x) {
  824. if (/-/.test(x) && configuration['camel-case-expansion']) {
  825. const c = camelCase(x);
  826. if (c !== key && flags.aliases[key].indexOf(c) === -1) {
  827. flags.aliases[key].push(c);
  828. newAliases[c] = true;
  829. }
  830. }
  831. });
  832. // For "--optionName", also set argv['option-name']
  833. flags.aliases[key].concat(key).forEach(function (x) {
  834. if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) {
  835. const c = decamelize(x, '-');
  836. if (c !== key && flags.aliases[key].indexOf(c) === -1) {
  837. flags.aliases[key].push(c);
  838. newAliases[c] = true;
  839. }
  840. }
  841. });
  842. flags.aliases[key].forEach(function (x) {
  843. flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) {
  844. return x !== y;
  845. }));
  846. });
  847. });
  848. });
  849. }
  850. function checkAllAliases(key, flag) {
  851. const toCheck = [].concat(flags.aliases[key] || [], key);
  852. const keys = Object.keys(flag);
  853. const setAlias = toCheck.find(key => keys.includes(key));
  854. return setAlias ? flag[setAlias] : false;
  855. }
  856. function hasAnyFlag(key) {
  857. const flagsKeys = Object.keys(flags);
  858. const toCheck = [].concat(flagsKeys.map(k => flags[k]));
  859. return toCheck.some(function (flag) {
  860. return Array.isArray(flag) ? flag.includes(key) : flag[key];
  861. });
  862. }
  863. function hasFlagsMatching(arg, ...patterns) {
  864. const toCheck = [].concat(...patterns);
  865. return toCheck.some(function (pattern) {
  866. const match = arg.match(pattern);
  867. return match && hasAnyFlag(match[1]);
  868. });
  869. }
  870. // based on a simplified version of the short flag group parsing logic
  871. function hasAllShortFlags(arg) {
  872. // if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group
  873. if (arg.match(negative) || !arg.match(/^-[^-]+/)) {
  874. return false;
  875. }
  876. let hasAllFlags = true;
  877. let next;
  878. const letters = arg.slice(1).split('');
  879. for (let j = 0; j < letters.length; j++) {
  880. next = arg.slice(j + 2);
  881. if (!hasAnyFlag(letters[j])) {
  882. hasAllFlags = false;
  883. break;
  884. }
  885. if ((letters[j + 1] && letters[j + 1] === '=') ||
  886. next === '-' ||
  887. (/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) ||
  888. (letters[j + 1] && letters[j + 1].match(/\W/))) {
  889. break;
  890. }
  891. }
  892. return hasAllFlags;
  893. }
  894. function isUnknownOptionAsArg(arg) {
  895. return configuration['unknown-options-as-args'] && isUnknownOption(arg);
  896. }
  897. function isUnknownOption(arg) {
  898. arg = arg.replace(/^-{3,}/, '--');
  899. // ignore negative numbers
  900. if (arg.match(negative)) {
  901. return false;
  902. }
  903. // if this is a short option group and all of them are configured, it isn't unknown
  904. if (hasAllShortFlags(arg)) {
  905. return false;
  906. }
  907. // e.g. '--count=2'
  908. const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/;
  909. // e.g. '-a' or '--arg'
  910. const normalFlag = /^-+([^=]+?)$/;
  911. // e.g. '-a-'
  912. const flagEndingInHyphen = /^-+([^=]+?)-$/;
  913. // e.g. '-abc123'
  914. const flagEndingInDigits = /^-+([^=]+?\d+)$/;
  915. // e.g. '-a/usr/local'
  916. const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/;
  917. // check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method
  918. return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters);
  919. }
  920. // make a best effort to pick a default value
  921. // for an option based on name and type.
  922. function defaultValue(key) {
  923. if (!checkAllAliases(key, flags.bools) &&
  924. !checkAllAliases(key, flags.counts) &&
  925. `${key}` in defaults) {
  926. return defaults[key];
  927. }
  928. else {
  929. return defaultForType(guessType(key));
  930. }
  931. }
  932. // return a default value, given the type of a flag.,
  933. function defaultForType(type) {
  934. const def = {
  935. [DefaultValuesForTypeKey.BOOLEAN]: true,
  936. [DefaultValuesForTypeKey.STRING]: '',
  937. [DefaultValuesForTypeKey.NUMBER]: undefined,
  938. [DefaultValuesForTypeKey.ARRAY]: []
  939. };
  940. return def[type];
  941. }
  942. // given a flag, enforce a default type.
  943. function guessType(key) {
  944. let type = DefaultValuesForTypeKey.BOOLEAN;
  945. if (checkAllAliases(key, flags.strings))
  946. type = DefaultValuesForTypeKey.STRING;
  947. else if (checkAllAliases(key, flags.numbers))
  948. type = DefaultValuesForTypeKey.NUMBER;
  949. else if (checkAllAliases(key, flags.bools))
  950. type = DefaultValuesForTypeKey.BOOLEAN;
  951. else if (checkAllAliases(key, flags.arrays))
  952. type = DefaultValuesForTypeKey.ARRAY;
  953. return type;
  954. }
  955. function isUndefined(num) {
  956. return num === undefined;
  957. }
  958. // check user configuration settings for inconsistencies
  959. function checkConfiguration() {
  960. // count keys should not be set as array/narg
  961. Object.keys(flags.counts).find(key => {
  962. if (checkAllAliases(key, flags.arrays)) {
  963. error = Error(__('Invalid configuration: %s, opts.count excludes opts.array.', key));
  964. return true;
  965. }
  966. else if (checkAllAliases(key, flags.nargs)) {
  967. error = Error(__('Invalid configuration: %s, opts.count excludes opts.narg.', key));
  968. return true;
  969. }
  970. return false;
  971. });
  972. }
  973. return {
  974. aliases: Object.assign({}, flags.aliases),
  975. argv: Object.assign(argvReturn, argv),
  976. configuration: configuration,
  977. defaulted: Object.assign({}, defaulted),
  978. error: error,
  979. newAliases: Object.assign({}, newAliases)
  980. };
  981. }
  982. }
  983. // if any aliases reference each other, we should
  984. // merge them together.
  985. function combineAliases(aliases) {
  986. const aliasArrays = [];
  987. const combined = Object.create(null);
  988. let change = true;
  989. // turn alias lookup hash {key: ['alias1', 'alias2']} into
  990. // a simple array ['key', 'alias1', 'alias2']
  991. Object.keys(aliases).forEach(function (key) {
  992. aliasArrays.push([].concat(aliases[key], key));
  993. });
  994. // combine arrays until zero changes are
  995. // made in an iteration.
  996. while (change) {
  997. change = false;
  998. for (let i = 0; i < aliasArrays.length; i++) {
  999. for (let ii = i + 1; ii < aliasArrays.length; ii++) {
  1000. const intersect = aliasArrays[i].filter(function (v) {
  1001. return aliasArrays[ii].indexOf(v) !== -1;
  1002. });
  1003. if (intersect.length) {
  1004. aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]);
  1005. aliasArrays.splice(ii, 1);
  1006. change = true;
  1007. break;
  1008. }
  1009. }
  1010. }
  1011. }
  1012. // map arrays back to the hash-lookup (de-dupe while
  1013. // we're at it).
  1014. aliasArrays.forEach(function (aliasArray) {
  1015. aliasArray = aliasArray.filter(function (v, i, self) {
  1016. return self.indexOf(v) === i;
  1017. });
  1018. const lastAlias = aliasArray.pop();
  1019. if (lastAlias !== undefined && typeof lastAlias === 'string') {
  1020. combined[lastAlias] = aliasArray;
  1021. }
  1022. });
  1023. return combined;
  1024. }
  1025. // this function should only be called when a count is given as an arg
  1026. // it is NOT called to set a default value
  1027. // thus we can start the count at 1 instead of 0
  1028. function increment(orig) {
  1029. return orig !== undefined ? orig + 1 : 1;
  1030. }
  1031. // TODO(bcoe): in the next major version of yargs, switch to
  1032. // Object.create(null) for dot notation:
  1033. function sanitizeKey(key) {
  1034. if (key === '__proto__')
  1035. return '___proto___';
  1036. return key;
  1037. }