[6a3a178] | 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;
|
---|