forked from pkgjs/parseargs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
210 lines (185 loc) · 6.88 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
'use strict';
const {
ArrayPrototypeConcat,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
ArrayPrototypePush,
ObjectHasOwn,
ObjectEntries,
StringPrototypeCharAt,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = require('./primordials');
const {
validateArray,
validateObject,
validateString,
validateUnion,
validateBoolean,
} = require('./validators');
const {
codes: {
ERR_UNKNOWN_OPTION,
},
} = require('./errors');
function getMainArgs() {
// This function is a placeholder for proposed process.mainArgs.
// Work out where to slice process.argv for user supplied arguments.
// Electron is an interested example, with work-arounds implemented in
// Commander and Yargs. Hopefully Electron would support process.mainArgs
// itself and render this work-around moot.
//
// In a bundled Electron app, the user CLI args directly
// follow executable. (No special processing required for unbundled.)
// 1) process.versions.electron is either set by electron, or undefined
// see https://github.com/electron/electron/blob/master/docs/api/process.md#processversionselectron-readonly
// 2) process.defaultApp is undefined in a bundled Electron app, and set
// in an unbundled Electron app
// see https://github.com/electron/electron/blob/master/docs/api/process.md#processversionselectron-readonly
// (Not included in tests as hopefully temporary example.)
/* c8 ignore next 3 */
if (process.versions && process.versions.electron && !process.defaultApp) {
return ArrayPrototypeSlice(process.argv, 1);
}
// Check node options for scenarios where user CLI args follow executable.
const execArgv = process.execArgv;
if (StringPrototypeIncludes(execArgv, '-e') ||
StringPrototypeIncludes(execArgv, '--eval') ||
StringPrototypeIncludes(execArgv, '-p') ||
StringPrototypeIncludes(execArgv, '--print')) {
return ArrayPrototypeSlice(process.argv, 1);
}
// Normally first two arguments are executable and script, then CLI arguments
return ArrayPrototypeSlice(process.argv, 2);
}
function storeOptionValue(strict, options, option, value, result) {
const hasOptionConfig = ObjectHasOwn(options, option);
if (strict && !hasOptionConfig) {
throw new ERR_UNKNOWN_OPTION(option);
}
const optionConfig = hasOptionConfig ? options[option] : {};
// Flags
result.flags[option] = true;
// Values
if (optionConfig.multiples) {
// Always store value in array, including for flags.
// result.values[option] starts out not present,
// first value is added as new array [newValue],
// subsequent values are pushed to existing array.
const usedAsFlag = value === undefined;
const newValue = usedAsFlag ? true : value;
if (result.values[option] !== undefined)
ArrayPrototypePush(result.values[option], newValue);
else
result.values[option] = [newValue];
} else {
result.values[option] = value;
}
}
const parseArgs = ({
argv = getMainArgs(),
strict = false,
options = {}
} = {}) => {
validateArray(argv, 'argv');
validateBoolean(strict, 'strict');
validateObject(options, 'options');
for (const [option, optionConfig] of ObjectEntries(options)) {
validateObject(optionConfig, `options.${option}`);
if (ObjectHasOwn(optionConfig, 'type')) {
validateUnion(optionConfig.type, `options.${option}.type`, ['string', 'boolean']);
}
if (ObjectHasOwn(optionConfig, 'short')) {
validateString(optionConfig.short, `options.${option}.short`);
}
if (ObjectHasOwn(optionConfig, 'multiples')) {
validateBoolean(optionConfig.multiples, `options.${option}.multiples`);
}
}
const result = {
flags: {},
values: {},
positionals: []
};
let pos = 0;
while (pos < argv.length) {
let arg = argv[pos];
if (StringPrototypeStartsWith(arg, '-')) {
if (arg === '-') {
// '-' commonly used to represent stdin/stdout, treat as positional
result.positionals = ArrayPrototypeConcat(result.positionals, '-');
++pos;
continue;
} else if (arg === '--') {
// Everything after a bare '--' is considered a positional argument
// and is returned verbatim
result.positionals = ArrayPrototypeConcat(
result.positionals,
ArrayPrototypeSlice(argv, ++pos)
);
return result;
} else if (StringPrototypeCharAt(arg, 1) !== '-') {
// Look for shortcodes: -fXzy and expand them to -f -X -z -y:
if (arg.length > 2) {
for (let i = 2; i < arg.length; i++) {
const short = StringPrototypeCharAt(arg, i);
// Add 'i' to 'pos' such that short options are parsed in order
// of definition:
ArrayPrototypeSplice(argv, pos + (i - 1), 0, `-${short}`);
}
}
arg = StringPrototypeCharAt(arg, 1); // short
for (const [option, optionConfig] of ObjectEntries(options)) {
if (optionConfig.short === arg) {
arg = option; // now long!
break;
}
}
// ToDo: later code tests for `=` in arg and wrong for shorts
} else {
arg = StringPrototypeSlice(arg, 2); // remove leading --
}
if (StringPrototypeIncludes(arg, '=')) {
// Store option=value same way independent of `type: "string"` as:
// - looks like a value, store as a value
// - match the intention of the user
// - preserve information for author to process further
const index = StringPrototypeIndexOf(arg, '=');
storeOptionValue(
strict,
options,
StringPrototypeSlice(arg, 0, index),
StringPrototypeSlice(arg, index + 1),
result);
} else if (pos + 1 < argv.length &&
!StringPrototypeStartsWith(argv[pos + 1], '-')
) {
// `type: "string"` option should also support setting values when '='
// isn't used ie. both --foo=b and --foo b should work
// If `type: "string"` option is specified, take next position argument
// as value and then increment pos so that we don't re-evaluate that
// arg, else set value as undefined ie. --foo b --bar c, after setting
// b as the value for foo, evaluate --bar next and skip 'b'
const val = options[arg] && options[arg].type === 'string' ?
argv[++pos] :
undefined;
storeOptionValue(strict, options, arg, val, result);
} else {
// Cases when an arg is specified without a value, example
// '--foo --bar' <- 'foo' and 'bar' flags should be set to true and
// save value as undefined
storeOptionValue(strict, options, arg, undefined, result);
}
} else {
// Arguments without a dash prefix are considered "positional"
ArrayPrototypePush(result.positionals, arg);
}
pos++;
}
return result;
};
module.exports = {
parseArgs
};