const { InvalidArgumentError } = require('./error.js'); // @ts-check class Argument { /** * Initialize a new command argument with the given name and description. * The default is that the argument is required, and you can explicitly * indicate this with <> around the name. Put [] around the name for an optional argument. * * @param {string} name * @param {string} [description] */ constructor(name, description) { this.description = description || ''; this.variadic = false; this.parseArg = undefined; this.defaultValue = undefined; this.defaultValueDescription = undefined; this.argChoices = undefined; switch (name[0]) { case '<': // e.g. this.required = true; this._name = name.slice(1, -1); break; case '[': // e.g. [optional] this.required = false; this._name = name.slice(1, -1); break; default: this.required = true; this._name = name; break; } if (this._name.length > 3 && this._name.slice(-3) === '...') { this.variadic = true; this._name = this._name.slice(0, -3); } } /** * Return argument name. * * @return {string} */ name() { return this._name; } /** * @api private */ _concatValue(value, previous) { if (previous === this.defaultValue || !Array.isArray(previous)) { return [value]; } return previous.concat(value); } /** * Set the default value, and optionally supply the description to be displayed in the help. * * @param {any} value * @param {string} [description] * @return {Argument} */ default(value, description) { this.defaultValue = value; this.defaultValueDescription = description; return this; } /** * Set the custom handler for processing CLI command arguments into argument values. * * @param {Function} [fn] * @return {Argument} */ argParser(fn) { this.parseArg = fn; return this; } /** * Only allow argument value to be one of choices. * * @param {string[]} values * @return {Argument} */ choices(values) { this.argChoices = values.slice(); this.parseArg = (arg, previous) => { if (!this.argChoices.includes(arg)) { throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(', ')}.`); } if (this.variadic) { return this._concatValue(arg, previous); } return arg; }; return this; } /** * Make argument required. */ argRequired() { this.required = true; return this; } /** * Make argument optional. */ argOptional() { this.required = false; return this; } } /** * Takes an argument and returns its human readable equivalent for help usage. * * @param {Argument} arg * @return {string} * @api private */ function humanReadableArgName(arg) { const nameOutput = arg.name() + (arg.variadic === true ? '...' : ''); return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']'; } exports.Argument = Argument; exports.humanReadableArgName = humanReadableArgName;