| 1 | const flagSymbol = Symbol('arg flag');
|
|---|
| 2 |
|
|---|
| 3 | class ArgError extends Error {
|
|---|
| 4 | constructor(msg, code) {
|
|---|
| 5 | super(msg);
|
|---|
| 6 | this.name = 'ArgError';
|
|---|
| 7 | this.code = code;
|
|---|
| 8 |
|
|---|
| 9 | Object.setPrototypeOf(this, ArgError.prototype);
|
|---|
| 10 | }
|
|---|
| 11 | }
|
|---|
| 12 |
|
|---|
| 13 | function arg(
|
|---|
| 14 | opts,
|
|---|
| 15 | {
|
|---|
| 16 | argv = process.argv.slice(2),
|
|---|
| 17 | permissive = false,
|
|---|
| 18 | stopAtPositional = false
|
|---|
| 19 | } = {}
|
|---|
| 20 | ) {
|
|---|
| 21 | if (!opts) {
|
|---|
| 22 | throw new ArgError(
|
|---|
| 23 | 'argument specification object is required',
|
|---|
| 24 | 'ARG_CONFIG_NO_SPEC'
|
|---|
| 25 | );
|
|---|
| 26 | }
|
|---|
| 27 |
|
|---|
| 28 | const result = { _: [] };
|
|---|
| 29 |
|
|---|
| 30 | const aliases = {};
|
|---|
| 31 | const handlers = {};
|
|---|
| 32 |
|
|---|
| 33 | for (const key of Object.keys(opts)) {
|
|---|
| 34 | if (!key) {
|
|---|
| 35 | throw new ArgError(
|
|---|
| 36 | 'argument key cannot be an empty string',
|
|---|
| 37 | 'ARG_CONFIG_EMPTY_KEY'
|
|---|
| 38 | );
|
|---|
| 39 | }
|
|---|
| 40 |
|
|---|
| 41 | if (key[0] !== '-') {
|
|---|
| 42 | throw new ArgError(
|
|---|
| 43 | `argument key must start with '-' but found: '${key}'`,
|
|---|
| 44 | 'ARG_CONFIG_NONOPT_KEY'
|
|---|
| 45 | );
|
|---|
| 46 | }
|
|---|
| 47 |
|
|---|
| 48 | if (key.length === 1) {
|
|---|
| 49 | throw new ArgError(
|
|---|
| 50 | `argument key must have a name; singular '-' keys are not allowed: ${key}`,
|
|---|
| 51 | 'ARG_CONFIG_NONAME_KEY'
|
|---|
| 52 | );
|
|---|
| 53 | }
|
|---|
| 54 |
|
|---|
| 55 | if (typeof opts[key] === 'string') {
|
|---|
| 56 | aliases[key] = opts[key];
|
|---|
| 57 | continue;
|
|---|
| 58 | }
|
|---|
| 59 |
|
|---|
| 60 | let type = opts[key];
|
|---|
| 61 | let isFlag = false;
|
|---|
| 62 |
|
|---|
| 63 | if (
|
|---|
| 64 | Array.isArray(type) &&
|
|---|
| 65 | type.length === 1 &&
|
|---|
| 66 | typeof type[0] === 'function'
|
|---|
| 67 | ) {
|
|---|
| 68 | const [fn] = type;
|
|---|
| 69 | type = (value, name, prev = []) => {
|
|---|
| 70 | prev.push(fn(value, name, prev[prev.length - 1]));
|
|---|
| 71 | return prev;
|
|---|
| 72 | };
|
|---|
| 73 | isFlag = fn === Boolean || fn[flagSymbol] === true;
|
|---|
| 74 | } else if (typeof type === 'function') {
|
|---|
| 75 | isFlag = type === Boolean || type[flagSymbol] === true;
|
|---|
| 76 | } else {
|
|---|
| 77 | throw new ArgError(
|
|---|
| 78 | `type missing or not a function or valid array type: ${key}`,
|
|---|
| 79 | 'ARG_CONFIG_VAD_TYPE'
|
|---|
| 80 | );
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | if (key[1] !== '-' && key.length > 2) {
|
|---|
| 84 | throw new ArgError(
|
|---|
| 85 | `short argument keys (with a single hyphen) must have only one character: ${key}`,
|
|---|
| 86 | 'ARG_CONFIG_SHORTOPT_TOOLONG'
|
|---|
| 87 | );
|
|---|
| 88 | }
|
|---|
| 89 |
|
|---|
| 90 | handlers[key] = [type, isFlag];
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | for (let i = 0, len = argv.length; i < len; i++) {
|
|---|
| 94 | const wholeArg = argv[i];
|
|---|
| 95 |
|
|---|
| 96 | if (stopAtPositional && result._.length > 0) {
|
|---|
| 97 | result._ = result._.concat(argv.slice(i));
|
|---|
| 98 | break;
|
|---|
| 99 | }
|
|---|
| 100 |
|
|---|
| 101 | if (wholeArg === '--') {
|
|---|
| 102 | result._ = result._.concat(argv.slice(i + 1));
|
|---|
| 103 | break;
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | if (wholeArg.length > 1 && wholeArg[0] === '-') {
|
|---|
| 107 | /* eslint-disable operator-linebreak */
|
|---|
| 108 | const separatedArguments =
|
|---|
| 109 | wholeArg[1] === '-' || wholeArg.length === 2
|
|---|
| 110 | ? [wholeArg]
|
|---|
| 111 | : wholeArg
|
|---|
| 112 | .slice(1)
|
|---|
| 113 | .split('')
|
|---|
| 114 | .map((a) => `-${a}`);
|
|---|
| 115 | /* eslint-enable operator-linebreak */
|
|---|
| 116 |
|
|---|
| 117 | for (let j = 0; j < separatedArguments.length; j++) {
|
|---|
| 118 | const arg = separatedArguments[j];
|
|---|
| 119 | const [originalArgName, argStr] =
|
|---|
| 120 | arg[1] === '-' ? arg.split(/=(.*)/, 2) : [arg, undefined];
|
|---|
| 121 |
|
|---|
| 122 | let argName = originalArgName;
|
|---|
| 123 | while (argName in aliases) {
|
|---|
| 124 | argName = aliases[argName];
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | if (!(argName in handlers)) {
|
|---|
| 128 | if (permissive) {
|
|---|
| 129 | result._.push(arg);
|
|---|
| 130 | continue;
|
|---|
| 131 | } else {
|
|---|
| 132 | throw new ArgError(
|
|---|
| 133 | `unknown or unexpected option: ${originalArgName}`,
|
|---|
| 134 | 'ARG_UNKNOWN_OPTION'
|
|---|
| 135 | );
|
|---|
| 136 | }
|
|---|
| 137 | }
|
|---|
| 138 |
|
|---|
| 139 | const [type, isFlag] = handlers[argName];
|
|---|
| 140 |
|
|---|
| 141 | if (!isFlag && j + 1 < separatedArguments.length) {
|
|---|
| 142 | throw new ArgError(
|
|---|
| 143 | `option requires argument (but was followed by another short argument): ${originalArgName}`,
|
|---|
| 144 | 'ARG_MISSING_REQUIRED_SHORTARG'
|
|---|
| 145 | );
|
|---|
| 146 | }
|
|---|
| 147 |
|
|---|
| 148 | if (isFlag) {
|
|---|
| 149 | result[argName] = type(true, argName, result[argName]);
|
|---|
| 150 | } else if (argStr === undefined) {
|
|---|
| 151 | if (
|
|---|
| 152 | argv.length < i + 2 ||
|
|---|
| 153 | (argv[i + 1].length > 1 &&
|
|---|
| 154 | argv[i + 1][0] === '-' &&
|
|---|
| 155 | !(
|
|---|
| 156 | argv[i + 1].match(/^-?\d*(\.(?=\d))?\d*$/) &&
|
|---|
| 157 | (type === Number ||
|
|---|
| 158 | // eslint-disable-next-line no-undef
|
|---|
| 159 | (typeof BigInt !== 'undefined' && type === BigInt))
|
|---|
| 160 | ))
|
|---|
| 161 | ) {
|
|---|
| 162 | const extended =
|
|---|
| 163 | originalArgName === argName ? '' : ` (alias for ${argName})`;
|
|---|
| 164 | throw new ArgError(
|
|---|
| 165 | `option requires argument: ${originalArgName}${extended}`,
|
|---|
| 166 | 'ARG_MISSING_REQUIRED_LONGARG'
|
|---|
| 167 | );
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | result[argName] = type(argv[i + 1], argName, result[argName]);
|
|---|
| 171 | ++i;
|
|---|
| 172 | } else {
|
|---|
| 173 | result[argName] = type(argStr, argName, result[argName]);
|
|---|
| 174 | }
|
|---|
| 175 | }
|
|---|
| 176 | } else {
|
|---|
| 177 | result._.push(wholeArg);
|
|---|
| 178 | }
|
|---|
| 179 | }
|
|---|
| 180 |
|
|---|
| 181 | return result;
|
|---|
| 182 | }
|
|---|
| 183 |
|
|---|
| 184 | arg.flag = (fn) => {
|
|---|
| 185 | fn[flagSymbol] = true;
|
|---|
| 186 | return fn;
|
|---|
| 187 | };
|
|---|
| 188 |
|
|---|
| 189 | // Utility types
|
|---|
| 190 | arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1);
|
|---|
| 191 |
|
|---|
| 192 | // Expose error class
|
|---|
| 193 | arg.ArgError = ArgError;
|
|---|
| 194 |
|
|---|
| 195 | module.exports = arg;
|
|---|