You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

349 lines
13 KiB

"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseArguments = exports.parseFreeFormArguments = exports.ParseArgumentException = void 0;
const core_1 = require("@angular-devkit/core");
const interface_1 = require("./interface");
class ParseArgumentException extends core_1.BaseException {
constructor(comments, parsed, ignored) {
super(`One or more errors occurred while parsing arguments:\n ${comments.join('\n ')}`);
this.comments = comments;
this.parsed = parsed;
this.ignored = ignored;
}
}
exports.ParseArgumentException = ParseArgumentException;
function _coerceType(str, type, v) {
switch (type) {
case interface_1.OptionType.Any:
if (Array.isArray(v)) {
return v.concat(str || '');
}
return _coerceType(str, interface_1.OptionType.Boolean, v) !== undefined
? _coerceType(str, interface_1.OptionType.Boolean, v)
: _coerceType(str, interface_1.OptionType.Number, v) !== undefined
? _coerceType(str, interface_1.OptionType.Number, v)
: _coerceType(str, interface_1.OptionType.String, v);
case interface_1.OptionType.String:
return str || '';
case interface_1.OptionType.Boolean:
switch (str) {
case 'false':
return false;
case undefined:
case '':
case 'true':
return true;
default:
return undefined;
}
case interface_1.OptionType.Number:
if (str === undefined) {
return 0;
}
else if (str === '') {
return undefined;
}
else if (Number.isFinite(+str)) {
return +str;
}
else {
return undefined;
}
case interface_1.OptionType.Array:
return Array.isArray(v)
? v.concat(str || '')
: v === undefined
? [str || '']
: [v + '', str || ''];
default:
return undefined;
}
}
function _coerce(str, o, v) {
if (!o) {
return _coerceType(str, interface_1.OptionType.Any, v);
}
else {
const types = o.types || [o.type];
// Try all the types one by one and pick the first one that returns a value contained in the
// enum. If there's no enum, just return the first one that matches.
for (const type of types) {
const maybeResult = _coerceType(str, type, v);
if (maybeResult !== undefined && (!o.enum || o.enum.includes(maybeResult))) {
return maybeResult;
}
}
return undefined;
}
}
function _getOptionFromName(name, options) {
const camelName = /(-|_)/.test(name) ? core_1.strings.camelize(name) : name;
for (const option of options) {
if (option.name === name || option.name === camelName) {
return option;
}
if (option.aliases.some((x) => x === name || x === camelName)) {
return option;
}
}
return undefined;
}
function _removeLeadingDashes(key) {
const from = key.startsWith('--') ? 2 : key.startsWith('-') ? 1 : 0;
return key.substr(from);
}
function _assignOption(arg, nextArg, { options, parsedOptions, leftovers, ignored, errors, warnings, }) {
const from = arg.startsWith('--') ? 2 : 1;
let consumedNextArg = false;
let key = arg.substr(from);
let option = null;
let value = '';
const i = arg.indexOf('=');
// If flag is --no-abc AND there's no equal sign.
if (i == -1) {
if (key.startsWith('no')) {
// Only use this key if the option matching the rest is a boolean.
const from = key.startsWith('no-') ? 3 : 2;
const maybeOption = _getOptionFromName(core_1.strings.camelize(key.substr(from)), options);
if (maybeOption && maybeOption.type == 'boolean') {
value = 'false';
option = maybeOption;
}
}
if (option === null) {
// Set it to true if it's a boolean and the next argument doesn't match true/false.
const maybeOption = _getOptionFromName(key, options);
if (maybeOption) {
value = nextArg;
let shouldShift = true;
if (value && value.startsWith('-') && _coerce(undefined, maybeOption) !== undefined) {
// Verify if not having a value results in a correct parse, if so don't shift.
shouldShift = false;
}
// Only absorb it if it leads to a better value.
if (shouldShift && _coerce(value, maybeOption) !== undefined) {
consumedNextArg = true;
}
else {
value = '';
}
option = maybeOption;
}
}
}
else {
key = arg.substring(0, i);
option = _getOptionFromName(_removeLeadingDashes(key), options) || null;
if (option) {
value = arg.substring(i + 1);
}
}
if (option === null) {
if (nextArg && !nextArg.startsWith('-')) {
leftovers.push(arg, nextArg);
consumedNextArg = true;
}
else {
leftovers.push(arg);
}
}
else {
const v = _coerce(value, option, parsedOptions[option.name]);
if (v !== undefined) {
if (parsedOptions[option.name] !== v) {
if (parsedOptions[option.name] !== undefined && option.type !== interface_1.OptionType.Array) {
warnings.push(`Option ${JSON.stringify(option.name)} was already specified with value ` +
`${JSON.stringify(parsedOptions[option.name])}. The new value ${JSON.stringify(v)} ` +
`will override it.`);
}
parsedOptions[option.name] = v;
}
}
else {
let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
if (option.enum) {
error += ` Valid values are: ${option.enum.map((x) => JSON.stringify(x)).join(', ')}.`;
}
else {
error += `Valid type(s) is: ${(option.types || [option.type]).join(', ')}`;
}
errors.push(error);
ignored.push(arg);
}
if (/^[a-z]+[A-Z]/.test(key)) {
warnings.push('Support for camel case arguments has been deprecated and will be removed in a future major version.\n' +
`Use '--${core_1.strings.dasherize(key)}' instead of '--${key}'.`);
}
}
return consumedNextArg;
}
/**
* Parse the arguments in a consistent way, but without having any option definition. This tries
* to assess what the user wants in a free form. For example, using `--name=false` will set the
* name properties to a boolean type.
* This should only be used when there's no schema available or if a schema is "true" (anything is
* valid).
*
* @param args Argument list to parse.
* @returns An object that contains a property per flags from the args.
*/
function parseFreeFormArguments(args) {
const parsedOptions = {};
const leftovers = [];
for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
if (arg == '--') {
leftovers.push(...args);
break;
}
if (arg.startsWith('--')) {
const eqSign = arg.indexOf('=');
let name;
let value;
if (eqSign !== -1) {
name = arg.substring(2, eqSign);
value = arg.substring(eqSign + 1);
}
else {
name = arg.substr(2);
value = args.shift();
}
const v = _coerce(value, null, parsedOptions[name]);
if (v !== undefined) {
parsedOptions[name] = v;
}
}
else if (arg.startsWith('-')) {
arg.split('').forEach((x) => (parsedOptions[x] = true));
}
else {
leftovers.push(arg);
}
}
if (leftovers.length) {
parsedOptions['--'] = leftovers;
}
return parsedOptions;
}
exports.parseFreeFormArguments = parseFreeFormArguments;
/**
* Parse the arguments in a consistent way, from a list of standardized options.
* The result object will have a key per option name, with the `_` key reserved for positional
* arguments, and `--` will contain everything that did not match. Any key that don't have an
* option will be pushed back in `--` and removed from the object. If you need to validate that
* there's no additionalProperties, you need to check the `--` key.
*
* @param args The argument array to parse.
* @param options List of supported options. {@see Option}.
* @param logger Logger to use to warn users.
* @returns An object that contains a property per option.
*/
function parseArguments(args, options, logger) {
if (options === null) {
options = [];
}
const leftovers = [];
const positionals = [];
const parsedOptions = {};
const ignored = [];
const errors = [];
const warnings = [];
const state = { options, parsedOptions, positionals, leftovers, ignored, errors, warnings };
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const arg = args[argIndex];
let consumedNextArg = false;
if (arg == '--') {
// If we find a --, we're done.
leftovers.push(...args.slice(argIndex + 1));
break;
}
if (arg.startsWith('--')) {
consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
}
else if (arg.startsWith('-')) {
// Argument is of form -abcdef. Starts at 1 because we skip the `-`.
for (let i = 1; i < arg.length; i++) {
const flag = arg[i];
// If the next character is an '=', treat it as a long flag.
if (arg[i + 1] == '=') {
const f = '-' + flag + arg.slice(i + 1);
consumedNextArg = _assignOption(f, args[argIndex + 1], state);
break;
}
// Treat the last flag as `--a` (as if full flag but just one letter). We do this in
// the loop because it saves us a check to see if the arg is just `-`.
if (i == arg.length - 1) {
const arg = '-' + flag;
consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
}
else {
const maybeOption = _getOptionFromName(flag, options);
if (maybeOption) {
const v = _coerce(undefined, maybeOption, parsedOptions[maybeOption.name]);
if (v !== undefined) {
parsedOptions[maybeOption.name] = v;
}
}
}
}
}
else {
positionals.push(arg);
}
if (consumedNextArg) {
argIndex++;
}
}
// Deal with positionals.
// TODO(hansl): this is by far the most complex piece of code in this file. Try to refactor it
// simpler.
if (positionals.length > 0) {
let pos = 0;
for (let i = 0; i < positionals.length;) {
let found = false;
let incrementPos = false;
let incrementI = true;
// We do this with a found flag because more than 1 option could have the same positional.
for (const option of options) {
// If any option has this positional and no value, AND fit the type, we need to remove it.
if (option.positional === pos) {
const coercedValue = _coerce(positionals[i], option, parsedOptions[option.name]);
if (parsedOptions[option.name] === undefined && coercedValue !== undefined) {
parsedOptions[option.name] = coercedValue;
found = true;
}
else {
incrementI = false;
}
incrementPos = true;
}
}
if (found) {
positionals.splice(i--, 1);
}
if (incrementPos) {
pos++;
}
if (incrementI) {
i++;
}
}
}
if (positionals.length > 0 || leftovers.length > 0) {
parsedOptions['--'] = [...positionals, ...leftovers];
}
if (warnings.length > 0 && logger) {
warnings.forEach((message) => logger.warn(message));
}
if (errors.length > 0) {
throw new ParseArgumentException(errors, parsedOptions, ignored);
}
return parsedOptions;
}
exports.parseArguments = parseArguments;