UNPKG

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