UNPKG

@adonisjs/ace

Version:

Commandline apps framework used by AdonisJs

288 lines (287 loc) 9.33 kB
"use strict"; /* * @adonisjs/ace * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; const getopts_1 = __importDefault(require("getopts")); const Exceptions_1 = require("../Exceptions"); /** * The job of the parser is to parse the command line values by taking * the command `args`, `flags` and `globalFlags` into account. */ class Parser { constructor(registeredFlags) { this.registeredFlags = registeredFlags; } /** * Validate all the flags against the flags registered by the command * or as global flags and disallow unknown flags. */ scanForUnknownFlags(parsed, flagsAndAliases) { Object.keys(parsed).forEach((key) => { if (key === '_') { return; } const hasFlag = flagsAndAliases.find((value) => value === key); if (!hasFlag) { throw Exceptions_1.UnknownFlagException.invoke(key); } }); } /** * Processes ace command flag to set the options for `getopts`. * We just define the `alias` with getopts coz their default, * string and boolean options produces the behavior we don't * want. */ preProcessFlag(flag, options) { /** * Register alias (when exists) */ if (flag.alias) { options.alias[flag.alias] = flag.name; } } /** * Casts a flag value to a boolean. The casting logic is driven * by the behavior of "getopts" */ castToBoolean(value) { if (typeof value === 'boolean') { return value; } if (value === 'true' || value === '=true') { return true; } return undefined; } /** * Cast the value to a string. The casting logic is driven * by the behavior of "getopts" * * - Convert numbers to string * - Do not convert boolean to a string, since a flag without a value * gets a boolean value, which is invalid */ castToString(value) { if (typeof value === 'number') { value = String(value); } if (typeof value === 'string' && value.trim()) { return value; } return undefined; } /** * Cast value to an array of string. The casting logic is driven * by the behavior of "getopts" * * - Numeric values are converted to string of array * - A string value is splitted by comma and trimmed. * - An array is casted to an array of string values */ castToArray(value) { if (typeof value === 'number') { value = String(value); } if (typeof value === 'string') { return value.split(',').filter((prop) => prop.trim()); } if (Array.isArray(value)) { /** * This will also convert numeric values to a string. The behavior * is same as string flag type. */ return value.map((prop) => String(prop)); } return undefined; } /** * Cast value to an array of numbers. The casting logic is driven * by the behavior of "getopts". * * - Numeric values are wrapped to an array. * - String is splitted by comma and each value is casted to a number * - Each array value is casted to a number. */ castToNumArray(value) { if (typeof value === 'number') { return [value]; } if (typeof value === 'string') { return value.split(',').map((one) => Number(one)); } if (Array.isArray(value)) { return value.map((prop) => Number(prop)); } return undefined; } /** * Cast value to a number. The casting logic is driven * by the behavior of "getopts" * * - Boolean values are not allowed * - A string is converted to a number */ castToNumer(value) { if (typeof value === 'number') { return value; } if (typeof value === 'string') { // Possibility of NaN here return Number(value); } return undefined; } /** * Casts value of a flag to it's expected data type. These values * are then later validated to ensure that casting was successful. */ processFlag(flag, parsed, command) { let value = parsed[flag.name]; /** * Check for the value with the alias, if it undefined * by the name */ if (value === undefined && flag.alias) { value = parsed[flag.alias]; } /** * Still undefined?? * * It is fine. Flags are optional anyways */ if (value === undefined) { return; } /** * Handle boolean values. It should be a valid boolean * data type or a string value of `'true'`. */ if (flag.type === 'boolean') { value = this.castToBoolean(value); if (value === undefined) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } } /** * Handle string value. It should be a valid and not empty. * Either remove the flag or provide a value */ if (flag.type === 'string') { value = this.castToString(value); if (value === undefined) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } } /** * Handle numeric values. The flag should have a value and * a valid number. */ if (flag.type === 'number') { value = this.castToNumer(value); if (value === undefined || isNaN(value)) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } } /** * Parse the value to be an array of strings */ if (flag.type === 'array') { value = this.castToArray(value); if (!value || !value.length) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } } /** * Parse the value to be an array of numbers */ if (flag.type === 'numArray') { value = this.castToNumArray(value); if (!value || !value.length) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } /** * Find if array has NaN values */ if (value.findIndex((one) => isNaN(one)) > -1) { throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command); } } parsed[flag.name] = value; if (flag.alias) { parsed[flag.alias] = value; } } /** * Validates the value to ensure that values are defined for * required arguments. */ validateArg(arg, index, parsed, command) { const value = parsed._[index]; if (value === undefined && arg.required) { throw Exceptions_1.MissingArgumentException.invoke(arg.name, command); } } /** * Parses argv and executes the command and global flags handlers */ parse(argv, command) { let options = { alias: {}, boolean: [], default: {}, string: [] }; const flagsAndAliases = []; const globalFlags = Object.keys(this.registeredFlags).map((name) => this.registeredFlags[name]); /** * Build options from global flags */ globalFlags.forEach((flag) => { this.preProcessFlag(flag, options); flagsAndAliases.push(flag.name); flag.alias && flagsAndAliases.push(flag.alias); }); /** * Build options from command flags */ if (command) { command.flags.forEach((flag) => { this.preProcessFlag(flag, options); flagsAndAliases.push(flag.name); flag.alias && flagsAndAliases.push(flag.alias); }); } /** * Parsing argv with the previously built options */ const parsed = (0, getopts_1.default)(argv, options); /** * Scan and report unknown flag as exception */ if (command) { this.scanForUnknownFlags(parsed, flagsAndAliases); } /** * Validating global flags (if any) */ globalFlags.forEach((flag) => { this.processFlag(flag, parsed); }); /** * Validating command flags (if command is defined) */ if (command) { command.flags.forEach((flag) => { this.processFlag(flag, parsed); }); } return parsed; } } exports.Parser = Parser;