mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 02:54:21 +08:00
305 lines
8.4 KiB
TypeScript
305 lines
8.4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. 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
|
|
*
|
|
*/
|
|
import { strings } from '@angular-devkit/core';
|
|
import { Arguments, Option, OptionType, Value } from './interface';
|
|
|
|
|
|
function _coerceType(str: string | undefined, type: OptionType, v?: Value): Value | undefined {
|
|
switch (type) {
|
|
case 'any':
|
|
if (Array.isArray(v)) {
|
|
return v.concat(str || '');
|
|
}
|
|
|
|
return _coerceType(str, OptionType.Boolean, v) !== undefined
|
|
? _coerceType(str, OptionType.Boolean, v)
|
|
: _coerceType(str, OptionType.Number, v) !== undefined
|
|
? _coerceType(str, OptionType.Number, v)
|
|
: _coerceType(str, OptionType.String, v);
|
|
|
|
case 'string':
|
|
return str || '';
|
|
|
|
case 'boolean':
|
|
switch (str) {
|
|
case 'false':
|
|
return false;
|
|
|
|
case undefined:
|
|
case '':
|
|
case 'true':
|
|
return true;
|
|
|
|
default:
|
|
return undefined;
|
|
}
|
|
|
|
case 'number':
|
|
if (str === undefined) {
|
|
return 0;
|
|
} else if (Number.isFinite(+str)) {
|
|
return +str;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
|
|
case 'array':
|
|
return Array.isArray(v) ? v.concat(str || '') : [str || ''];
|
|
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function _coerce(str: string | undefined, o: Option | null, v?: Value): Value | undefined {
|
|
if (!o) {
|
|
return _coerceType(str, OptionType.Any, v);
|
|
} else if (o.type == 'suboption') {
|
|
return _coerceType(str, OptionType.String, v);
|
|
} else {
|
|
return _coerceType(str, o.type, v);
|
|
}
|
|
}
|
|
|
|
|
|
function _getOptionFromName(name: string, options: Option[]): Option | undefined {
|
|
const cName = strings.camelize(name);
|
|
|
|
for (const option of options) {
|
|
if (option.name == name || option.name == cName) {
|
|
return option;
|
|
}
|
|
|
|
if (option.aliases.some(x => x == name || x == cName)) {
|
|
return option;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
|
|
function _assignOption(
|
|
arg: string,
|
|
args: string[],
|
|
options: Option[],
|
|
parsedOptions: Arguments,
|
|
_positionals: string[],
|
|
leftovers: string[],
|
|
) {
|
|
let key = arg.substr(2);
|
|
let option: Option | null = 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 maybeOption = _getOptionFromName(key.substr(3), options);
|
|
if (maybeOption && maybeOption.type == 'boolean') {
|
|
value = 'false';
|
|
option = maybeOption;
|
|
}
|
|
} else if (key.startsWith('no')) {
|
|
// Only use this key if the option matching the rest is a boolean.
|
|
const maybeOption = _getOptionFromName(key.substr(2), 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) {
|
|
// Not of type boolean, consume the next value.
|
|
value = args[0];
|
|
// Only absorb it if it leads to a value.
|
|
if (_coerce(value, maybeOption) !== undefined) {
|
|
args.shift();
|
|
} else {
|
|
value = '';
|
|
}
|
|
option = maybeOption;
|
|
}
|
|
}
|
|
} else {
|
|
key = arg.substring(0, i);
|
|
option = _getOptionFromName(key, options) || null;
|
|
if (option) {
|
|
value = arg.substring(i + 1);
|
|
if (option.type === 'boolean' && _coerce(value, option) === undefined) {
|
|
value = 'true';
|
|
}
|
|
}
|
|
}
|
|
if (option === null) {
|
|
if (args[0] && !args[0].startsWith('--')) {
|
|
leftovers.push(arg, args[0]);
|
|
args.shift();
|
|
} else {
|
|
leftovers.push(arg);
|
|
}
|
|
} else {
|
|
const v = _coerce(value, option, parsedOptions[option.name]);
|
|
if (v !== undefined) {
|
|
parsedOptions[option.name] = v;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export function parseFreeFormArguments(args: string[]): Arguments {
|
|
const parsedOptions: Arguments = {};
|
|
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: string;
|
|
let value: string | undefined;
|
|
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);
|
|
}
|
|
}
|
|
|
|
parsedOptions['--'] = leftovers;
|
|
|
|
return parsedOptions;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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}.
|
|
* @returns An object that contains a property per option.
|
|
*/
|
|
export function parseArguments(args: string[], options: Option[] | null): Arguments {
|
|
if (options === null) {
|
|
options = [];
|
|
}
|
|
|
|
const leftovers: string[] = [];
|
|
const positionals: string[] = [];
|
|
const parsedOptions: Arguments = {};
|
|
|
|
for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
|
|
if (!arg) {
|
|
break;
|
|
}
|
|
|
|
if (arg == '--') {
|
|
// If we find a --, we're done.
|
|
leftovers.push(...args);
|
|
break;
|
|
}
|
|
|
|
if (arg.startsWith('--')) {
|
|
_assignOption(arg, args, options, parsedOptions, positionals, leftovers);
|
|
} 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];
|
|
// 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) {
|
|
_assignOption('--' + flag, args, options, parsedOptions, positionals, leftovers);
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
// Deal with positionals.
|
|
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, we need to remove it.
|
|
if (option.positional === pos) {
|
|
if (parsedOptions[option.name] === undefined) {
|
|
parsedOptions[option.name] = positionals[i];
|
|
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];
|
|
}
|
|
|
|
return parsedOptions;
|
|
}
|