Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

145 lines
3.8KB

  1. const flagSymbol = Symbol('arg flag');
  2. function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPositional = false} = {}) {
  3. if (!opts) {
  4. throw new Error('Argument specification object is required');
  5. }
  6. const result = {_: []};
  7. const aliases = {};
  8. const handlers = {};
  9. for (const key of Object.keys(opts)) {
  10. if (!key) {
  11. throw new TypeError('Argument key cannot be an empty string');
  12. }
  13. if (key[0] !== '-') {
  14. throw new TypeError(`Argument key must start with '-' but found: '${key}'`);
  15. }
  16. if (key.length === 1) {
  17. throw new TypeError(`Argument key must have a name; singular '-' keys are not allowed: ${key}`);
  18. }
  19. if (typeof opts[key] === 'string') {
  20. aliases[key] = opts[key];
  21. continue;
  22. }
  23. let type = opts[key];
  24. let isFlag = false;
  25. if (Array.isArray(type) && type.length === 1 && typeof type[0] === 'function') {
  26. const [fn] = type;
  27. type = (value, name, prev = []) => {
  28. prev.push(fn(value, name, prev[prev.length - 1]));
  29. return prev;
  30. };
  31. isFlag = fn === Boolean || fn[flagSymbol] === true;
  32. } else if (typeof type === 'function') {
  33. isFlag = type === Boolean || type[flagSymbol] === true;
  34. } else {
  35. throw new TypeError(`Type missing or not a function or valid array type: ${key}`);
  36. }
  37. if (key[1] !== '-' && key.length > 2) {
  38. throw new TypeError(`Short argument keys (with a single hyphen) must have only one character: ${key}`);
  39. }
  40. handlers[key] = [type, isFlag];
  41. }
  42. for (let i = 0, len = argv.length; i < len; i++) {
  43. const wholeArg = argv[i];
  44. if (stopAtPositional && result._.length > 0) {
  45. result._ = result._.concat(argv.slice(i));
  46. break;
  47. }
  48. if (wholeArg === '--') {
  49. result._ = result._.concat(argv.slice(i + 1));
  50. break;
  51. }
  52. if (wholeArg.length > 1 && wholeArg[0] === '-') {
  53. /* eslint-disable operator-linebreak */
  54. const separatedArguments = (wholeArg[1] === '-' || wholeArg.length === 2)
  55. ? [wholeArg]
  56. : wholeArg.slice(1).split('').map(a => `-${a}`);
  57. /* eslint-enable operator-linebreak */
  58. for (let j = 0; j < separatedArguments.length; j++) {
  59. const arg = separatedArguments[j];
  60. const [originalArgName, argStr] = arg[1] === '-' ? arg.split(/=(.*)/, 2) : [arg, undefined];
  61. let argName = originalArgName;
  62. while (argName in aliases) {
  63. argName = aliases[argName];
  64. }
  65. if (!(argName in handlers)) {
  66. if (permissive) {
  67. result._.push(arg);
  68. continue;
  69. } else {
  70. const err = new Error(`Unknown or unexpected option: ${originalArgName}`);
  71. err.code = 'ARG_UNKNOWN_OPTION';
  72. throw err;
  73. }
  74. }
  75. const [type, isFlag] = handlers[argName];
  76. if (!isFlag && ((j + 1) < separatedArguments.length)) {
  77. throw new TypeError(`Option requires argument (but was followed by another short argument): ${originalArgName}`);
  78. }
  79. if (isFlag) {
  80. result[argName] = type(true, argName, result[argName]);
  81. } else if (argStr === undefined) {
  82. if (
  83. argv.length < i + 2 ||
  84. (
  85. argv[i + 1].length > 1 &&
  86. (argv[i + 1][0] === '-') &&
  87. !(
  88. argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) &&
  89. (
  90. type === Number ||
  91. // eslint-disable-next-line no-undef
  92. (typeof BigInt !== 'undefined' && type === BigInt)
  93. )
  94. )
  95. )
  96. ) {
  97. const extended = originalArgName === argName ? '' : ` (alias for ${argName})`;
  98. throw new Error(`Option requires argument: ${originalArgName}${extended}`);
  99. }
  100. result[argName] = type(argv[i + 1], argName, result[argName]);
  101. ++i;
  102. } else {
  103. result[argName] = type(argStr, argName, result[argName]);
  104. }
  105. }
  106. } else {
  107. result._.push(wholeArg);
  108. }
  109. }
  110. return result;
  111. }
  112. arg.flag = fn => {
  113. fn[flagSymbol] = true;
  114. return fn;
  115. };
  116. // Utility types
  117. arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1);
  118. module.exports = arg;