UNPKG

4.46 kBJavaScriptView Raw
1const flagSymbol = Symbol('arg flag');
2
3class 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
13function 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
184arg.flag = (fn) => {
185 fn[flagSymbol] = true;
186 return fn;
187};
188
189// Utility types
190arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1);
191
192// Expose error class
193arg.ArgError = ArgError;
194
195module.exports = arg;