yargs-parser.js 46 KB

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