UNPKG

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