1 | "use strict";
|
---|
2 | /**
|
---|
3 | * @license
|
---|
4 | * Copyright Google LLC All Rights Reserved.
|
---|
5 | *
|
---|
6 | * Use of this source code is governed by an MIT-style license that can be
|
---|
7 | * found in the LICENSE file at https://angular.io/license
|
---|
8 | */
|
---|
9 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
10 | exports.parseArguments = exports.parseFreeFormArguments = exports.ParseArgumentException = void 0;
|
---|
11 | const core_1 = require("@angular-devkit/core");
|
---|
12 | const interface_1 = require("./interface");
|
---|
13 | class ParseArgumentException extends core_1.BaseException {
|
---|
14 | constructor(comments, parsed, ignored) {
|
---|
15 | super(`One or more errors occurred while parsing arguments:\n ${comments.join('\n ')}`);
|
---|
16 | this.comments = comments;
|
---|
17 | this.parsed = parsed;
|
---|
18 | this.ignored = ignored;
|
---|
19 | }
|
---|
20 | }
|
---|
21 | exports.ParseArgumentException = ParseArgumentException;
|
---|
22 | function _coerceType(str, type, v) {
|
---|
23 | switch (type) {
|
---|
24 | case interface_1.OptionType.Any:
|
---|
25 | if (Array.isArray(v)) {
|
---|
26 | return v.concat(str || '');
|
---|
27 | }
|
---|
28 | return _coerceType(str, interface_1.OptionType.Boolean, v) !== undefined
|
---|
29 | ? _coerceType(str, interface_1.OptionType.Boolean, v)
|
---|
30 | : _coerceType(str, interface_1.OptionType.Number, v) !== undefined
|
---|
31 | ? _coerceType(str, interface_1.OptionType.Number, v)
|
---|
32 | : _coerceType(str, interface_1.OptionType.String, v);
|
---|
33 | case interface_1.OptionType.String:
|
---|
34 | return str || '';
|
---|
35 | case interface_1.OptionType.Boolean:
|
---|
36 | switch (str) {
|
---|
37 | case 'false':
|
---|
38 | return false;
|
---|
39 | case undefined:
|
---|
40 | case '':
|
---|
41 | case 'true':
|
---|
42 | return true;
|
---|
43 | default:
|
---|
44 | return undefined;
|
---|
45 | }
|
---|
46 | case interface_1.OptionType.Number:
|
---|
47 | if (str === undefined) {
|
---|
48 | return 0;
|
---|
49 | }
|
---|
50 | else if (str === '') {
|
---|
51 | return undefined;
|
---|
52 | }
|
---|
53 | else if (Number.isFinite(+str)) {
|
---|
54 | return +str;
|
---|
55 | }
|
---|
56 | else {
|
---|
57 | return undefined;
|
---|
58 | }
|
---|
59 | case interface_1.OptionType.Array:
|
---|
60 | return Array.isArray(v)
|
---|
61 | ? v.concat(str || '')
|
---|
62 | : v === undefined
|
---|
63 | ? [str || '']
|
---|
64 | : [v + '', str || ''];
|
---|
65 | default:
|
---|
66 | return undefined;
|
---|
67 | }
|
---|
68 | }
|
---|
69 | function _coerce(str, o, v) {
|
---|
70 | if (!o) {
|
---|
71 | return _coerceType(str, interface_1.OptionType.Any, v);
|
---|
72 | }
|
---|
73 | else {
|
---|
74 | const types = o.types || [o.type];
|
---|
75 | // Try all the types one by one and pick the first one that returns a value contained in the
|
---|
76 | // enum. If there's no enum, just return the first one that matches.
|
---|
77 | for (const type of types) {
|
---|
78 | const maybeResult = _coerceType(str, type, v);
|
---|
79 | if (maybeResult !== undefined && (!o.enum || o.enum.includes(maybeResult))) {
|
---|
80 | return maybeResult;
|
---|
81 | }
|
---|
82 | }
|
---|
83 | return undefined;
|
---|
84 | }
|
---|
85 | }
|
---|
86 | function _getOptionFromName(name, options) {
|
---|
87 | const camelName = /(-|_)/.test(name) ? core_1.strings.camelize(name) : name;
|
---|
88 | for (const option of options) {
|
---|
89 | if (option.name === name || option.name === camelName) {
|
---|
90 | return option;
|
---|
91 | }
|
---|
92 | if (option.aliases.some((x) => x === name || x === camelName)) {
|
---|
93 | return option;
|
---|
94 | }
|
---|
95 | }
|
---|
96 | return undefined;
|
---|
97 | }
|
---|
98 | function _removeLeadingDashes(key) {
|
---|
99 | const from = key.startsWith('--') ? 2 : key.startsWith('-') ? 1 : 0;
|
---|
100 | return key.substr(from);
|
---|
101 | }
|
---|
102 | function _assignOption(arg, nextArg, { options, parsedOptions, leftovers, ignored, errors, warnings, }) {
|
---|
103 | const from = arg.startsWith('--') ? 2 : 1;
|
---|
104 | let consumedNextArg = false;
|
---|
105 | let key = arg.substr(from);
|
---|
106 | let option = null;
|
---|
107 | let value = '';
|
---|
108 | const i = arg.indexOf('=');
|
---|
109 | // If flag is --no-abc AND there's no equal sign.
|
---|
110 | if (i == -1) {
|
---|
111 | if (key.startsWith('no')) {
|
---|
112 | // Only use this key if the option matching the rest is a boolean.
|
---|
113 | const from = key.startsWith('no-') ? 3 : 2;
|
---|
114 | const maybeOption = _getOptionFromName(core_1.strings.camelize(key.substr(from)), options);
|
---|
115 | if (maybeOption && maybeOption.type == 'boolean') {
|
---|
116 | value = 'false';
|
---|
117 | option = maybeOption;
|
---|
118 | }
|
---|
119 | }
|
---|
120 | if (option === null) {
|
---|
121 | // Set it to true if it's a boolean and the next argument doesn't match true/false.
|
---|
122 | const maybeOption = _getOptionFromName(key, options);
|
---|
123 | if (maybeOption) {
|
---|
124 | value = nextArg;
|
---|
125 | let shouldShift = true;
|
---|
126 | if (value && value.startsWith('-') && _coerce(undefined, maybeOption) !== undefined) {
|
---|
127 | // Verify if not having a value results in a correct parse, if so don't shift.
|
---|
128 | shouldShift = false;
|
---|
129 | }
|
---|
130 | // Only absorb it if it leads to a better value.
|
---|
131 | if (shouldShift && _coerce(value, maybeOption) !== undefined) {
|
---|
132 | consumedNextArg = true;
|
---|
133 | }
|
---|
134 | else {
|
---|
135 | value = '';
|
---|
136 | }
|
---|
137 | option = maybeOption;
|
---|
138 | }
|
---|
139 | }
|
---|
140 | }
|
---|
141 | else {
|
---|
142 | key = arg.substring(0, i);
|
---|
143 | option = _getOptionFromName(_removeLeadingDashes(key), options) || null;
|
---|
144 | if (option) {
|
---|
145 | value = arg.substring(i + 1);
|
---|
146 | }
|
---|
147 | }
|
---|
148 | if (option === null) {
|
---|
149 | if (nextArg && !nextArg.startsWith('-')) {
|
---|
150 | leftovers.push(arg, nextArg);
|
---|
151 | consumedNextArg = true;
|
---|
152 | }
|
---|
153 | else {
|
---|
154 | leftovers.push(arg);
|
---|
155 | }
|
---|
156 | }
|
---|
157 | else {
|
---|
158 | const v = _coerce(value, option, parsedOptions[option.name]);
|
---|
159 | if (v !== undefined) {
|
---|
160 | if (parsedOptions[option.name] !== v) {
|
---|
161 | if (parsedOptions[option.name] !== undefined && option.type !== interface_1.OptionType.Array) {
|
---|
162 | warnings.push(`Option ${JSON.stringify(option.name)} was already specified with value ` +
|
---|
163 | `${JSON.stringify(parsedOptions[option.name])}. The new value ${JSON.stringify(v)} ` +
|
---|
164 | `will override it.`);
|
---|
165 | }
|
---|
166 | parsedOptions[option.name] = v;
|
---|
167 | }
|
---|
168 | }
|
---|
169 | else {
|
---|
170 | let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
|
---|
171 | if (option.enum) {
|
---|
172 | error += ` Valid values are: ${option.enum.map((x) => JSON.stringify(x)).join(', ')}.`;
|
---|
173 | }
|
---|
174 | else {
|
---|
175 | error += `Valid type(s) is: ${(option.types || [option.type]).join(', ')}`;
|
---|
176 | }
|
---|
177 | errors.push(error);
|
---|
178 | ignored.push(arg);
|
---|
179 | }
|
---|
180 | if (/^[a-z]+[A-Z]/.test(key)) {
|
---|
181 | warnings.push('Support for camel case arguments has been deprecated and will be removed in a future major version.\n' +
|
---|
182 | `Use '--${core_1.strings.dasherize(key)}' instead of '--${key}'.`);
|
---|
183 | }
|
---|
184 | }
|
---|
185 | return consumedNextArg;
|
---|
186 | }
|
---|
187 | /**
|
---|
188 | * Parse the arguments in a consistent way, but without having any option definition. This tries
|
---|
189 | * to assess what the user wants in a free form. For example, using `--name=false` will set the
|
---|
190 | * name properties to a boolean type.
|
---|
191 | * This should only be used when there's no schema available or if a schema is "true" (anything is
|
---|
192 | * valid).
|
---|
193 | *
|
---|
194 | * @param args Argument list to parse.
|
---|
195 | * @returns An object that contains a property per flags from the args.
|
---|
196 | */
|
---|
197 | function parseFreeFormArguments(args) {
|
---|
198 | const parsedOptions = {};
|
---|
199 | const leftovers = [];
|
---|
200 | for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
|
---|
201 | if (arg == '--') {
|
---|
202 | leftovers.push(...args);
|
---|
203 | break;
|
---|
204 | }
|
---|
205 | if (arg.startsWith('--')) {
|
---|
206 | const eqSign = arg.indexOf('=');
|
---|
207 | let name;
|
---|
208 | let value;
|
---|
209 | if (eqSign !== -1) {
|
---|
210 | name = arg.substring(2, eqSign);
|
---|
211 | value = arg.substring(eqSign + 1);
|
---|
212 | }
|
---|
213 | else {
|
---|
214 | name = arg.substr(2);
|
---|
215 | value = args.shift();
|
---|
216 | }
|
---|
217 | const v = _coerce(value, null, parsedOptions[name]);
|
---|
218 | if (v !== undefined) {
|
---|
219 | parsedOptions[name] = v;
|
---|
220 | }
|
---|
221 | }
|
---|
222 | else if (arg.startsWith('-')) {
|
---|
223 | arg.split('').forEach((x) => (parsedOptions[x] = true));
|
---|
224 | }
|
---|
225 | else {
|
---|
226 | leftovers.push(arg);
|
---|
227 | }
|
---|
228 | }
|
---|
229 | if (leftovers.length) {
|
---|
230 | parsedOptions['--'] = leftovers;
|
---|
231 | }
|
---|
232 | return parsedOptions;
|
---|
233 | }
|
---|
234 | exports.parseFreeFormArguments = parseFreeFormArguments;
|
---|
235 | /**
|
---|
236 | * Parse the arguments in a consistent way, from a list of standardized options.
|
---|
237 | * The result object will have a key per option name, with the `_` key reserved for positional
|
---|
238 | * arguments, and `--` will contain everything that did not match. Any key that don't have an
|
---|
239 | * option will be pushed back in `--` and removed from the object. If you need to validate that
|
---|
240 | * there's no additionalProperties, you need to check the `--` key.
|
---|
241 | *
|
---|
242 | * @param args The argument array to parse.
|
---|
243 | * @param options List of supported options. {@see Option}.
|
---|
244 | * @param logger Logger to use to warn users.
|
---|
245 | * @returns An object that contains a property per option.
|
---|
246 | */
|
---|
247 | function parseArguments(args, options, logger) {
|
---|
248 | if (options === null) {
|
---|
249 | options = [];
|
---|
250 | }
|
---|
251 | const leftovers = [];
|
---|
252 | const positionals = [];
|
---|
253 | const parsedOptions = {};
|
---|
254 | const ignored = [];
|
---|
255 | const errors = [];
|
---|
256 | const warnings = [];
|
---|
257 | const state = { options, parsedOptions, positionals, leftovers, ignored, errors, warnings };
|
---|
258 | for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
---|
259 | const arg = args[argIndex];
|
---|
260 | let consumedNextArg = false;
|
---|
261 | if (arg == '--') {
|
---|
262 | // If we find a --, we're done.
|
---|
263 | leftovers.push(...args.slice(argIndex + 1));
|
---|
264 | break;
|
---|
265 | }
|
---|
266 | if (arg.startsWith('--')) {
|
---|
267 | consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
|
---|
268 | }
|
---|
269 | else if (arg.startsWith('-')) {
|
---|
270 | // Argument is of form -abcdef. Starts at 1 because we skip the `-`.
|
---|
271 | for (let i = 1; i < arg.length; i++) {
|
---|
272 | const flag = arg[i];
|
---|
273 | // If the next character is an '=', treat it as a long flag.
|
---|
274 | if (arg[i + 1] == '=') {
|
---|
275 | const f = '-' + flag + arg.slice(i + 1);
|
---|
276 | consumedNextArg = _assignOption(f, args[argIndex + 1], state);
|
---|
277 | break;
|
---|
278 | }
|
---|
279 | // Treat the last flag as `--a` (as if full flag but just one letter). We do this in
|
---|
280 | // the loop because it saves us a check to see if the arg is just `-`.
|
---|
281 | if (i == arg.length - 1) {
|
---|
282 | const arg = '-' + flag;
|
---|
283 | consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
|
---|
284 | }
|
---|
285 | else {
|
---|
286 | const maybeOption = _getOptionFromName(flag, options);
|
---|
287 | if (maybeOption) {
|
---|
288 | const v = _coerce(undefined, maybeOption, parsedOptions[maybeOption.name]);
|
---|
289 | if (v !== undefined) {
|
---|
290 | parsedOptions[maybeOption.name] = v;
|
---|
291 | }
|
---|
292 | }
|
---|
293 | }
|
---|
294 | }
|
---|
295 | }
|
---|
296 | else {
|
---|
297 | positionals.push(arg);
|
---|
298 | }
|
---|
299 | if (consumedNextArg) {
|
---|
300 | argIndex++;
|
---|
301 | }
|
---|
302 | }
|
---|
303 | // Deal with positionals.
|
---|
304 | // TODO(hansl): this is by far the most complex piece of code in this file. Try to refactor it
|
---|
305 | // simpler.
|
---|
306 | if (positionals.length > 0) {
|
---|
307 | let pos = 0;
|
---|
308 | for (let i = 0; i < positionals.length;) {
|
---|
309 | let found = false;
|
---|
310 | let incrementPos = false;
|
---|
311 | let incrementI = true;
|
---|
312 | // We do this with a found flag because more than 1 option could have the same positional.
|
---|
313 | for (const option of options) {
|
---|
314 | // If any option has this positional and no value, AND fit the type, we need to remove it.
|
---|
315 | if (option.positional === pos) {
|
---|
316 | const coercedValue = _coerce(positionals[i], option, parsedOptions[option.name]);
|
---|
317 | if (parsedOptions[option.name] === undefined && coercedValue !== undefined) {
|
---|
318 | parsedOptions[option.name] = coercedValue;
|
---|
319 | found = true;
|
---|
320 | }
|
---|
321 | else {
|
---|
322 | incrementI = false;
|
---|
323 | }
|
---|
324 | incrementPos = true;
|
---|
325 | }
|
---|
326 | }
|
---|
327 | if (found) {
|
---|
328 | positionals.splice(i--, 1);
|
---|
329 | }
|
---|
330 | if (incrementPos) {
|
---|
331 | pos++;
|
---|
332 | }
|
---|
333 | if (incrementI) {
|
---|
334 | i++;
|
---|
335 | }
|
---|
336 | }
|
---|
337 | }
|
---|
338 | if (positionals.length > 0 || leftovers.length > 0) {
|
---|
339 | parsedOptions['--'] = [...positionals, ...leftovers];
|
---|
340 | }
|
---|
341 | if (warnings.length > 0 && logger) {
|
---|
342 | warnings.forEach((message) => logger.warn(message));
|
---|
343 | }
|
---|
344 | if (errors.length > 0) {
|
---|
345 | throw new ParseArgumentException(errors, parsedOptions, ignored);
|
---|
346 | }
|
---|
347 | return parsedOptions;
|
---|
348 | }
|
---|
349 | exports.parseArguments = parseArguments;
|
---|