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 |
|
108 | const separatedArguments =
|
109 | wholeArg[1] === '-' || wholeArg.length === 2
|
110 | ? [wholeArg]
|
111 | : wholeArg
|
112 | .slice(1)
|
113 | .split('')
|
114 | .map((a) => `-${a}`);
|
115 |
|
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 |
|
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 |
|
190 | arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1);
|
191 |
|
192 |
|
193 | arg.ArgError = ArgError;
|
194 |
|
195 | module.exports = arg;
|