diff --git a/Readme.md b/Readme.md index 1b43a9613..44acc2314 100644 --- a/Readme.md +++ b/Readme.md @@ -479,7 +479,9 @@ program ### Custom argument processing -You may specify a function to do custom processing of command-arguments before they are passed to the action handler. +You may specify a function to do custom processing of command-arguments. The processed argument values are +passed to the action handler, and saved as `.processedArgs`. + The callback function receives two parameters, the user specified command-argument and the previous value for the argument. It returns the new value for the argument. diff --git a/index.js b/index.js index 3294e9f2b..633701541 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,7 @@ class Help { } if (this.sortSubcommands) { visibleCommands.sort((a, b) => { + // @ts-ignore: overloaded return type return a.name().localeCompare(b.name()); }); } @@ -656,13 +657,19 @@ class Command extends EventEmitter { constructor(name) { super(); + /** @type {Command[]} */ this.commands = []; + /** @type {Option[]} */ this.options = []; this.parent = null; this._allowUnknownOption = false; this._allowExcessArguments = true; + /** @type {Argument[]} */ this._args = []; + /** @type {string[]} */ + this.args = null; // cli args with options removed this.rawArgs = null; + this.processedArgs = null; // like .args but after custom processing and collecting variadic this._scriptPath = null; this._name = name || ''; this._optionValues = {}; @@ -1613,13 +1620,34 @@ Expecting one of '${allowedValues.join("', '")}'`); }; /** - * Package arguments (this.args) for passing to action handler based - * on declared arguments (this._args). + * Check this.args against expected this._args. * * @api private */ - _getActionArguments() { + _checkNumberOfArguments() { + // too few + this._args.forEach((arg, i) => { + if (arg.required && this.args[i] == null) { + this.missingArgument(arg.name()); + } + }); + // too many + if (this._args.length > 0 && this._args[this._args.length - 1].variadic) { + return; + } + if (this.args.length > this._args.length) { + this._excessArguments(this.args); + } + }; + + /** + * Process this.args using this._args and save as this.processedArgs! + * + * @api private + */ + + _processArguments() { const myParseArg = (argument, value, previous) => { // Extra processing for nice error message on parsing failure. let parsedValue = value; @@ -1637,7 +1665,9 @@ Expecting one of '${allowedValues.join("', '")}'`); return parsedValue; }; - const actionArgs = []; + this._checkNumberOfArguments(); + + const processedArgs = []; this._args.forEach((declaredArg, index) => { let value = declaredArg.defaultValue; if (declaredArg.variadic) { @@ -1658,9 +1688,9 @@ Expecting one of '${allowedValues.join("', '")}'`); value = myParseArg(declaredArg, value, declaredArg.defaultValue); } } - actionArgs[index] = value; + processedArgs[index] = value; }); - return actionArgs; + this.processedArgs = processedArgs; } /** @@ -1752,37 +1782,22 @@ Expecting one of '${allowedValues.join("', '")}'`); this.unknownOption(parsed.unknown[0]); } }; - const checkNumberOfArguments = () => { - // too few - this._args.forEach((arg, i) => { - if (arg.required && this.args[i] == null) { - this.missingArgument(arg.name()); - } - }); - // too many - if (this._args.length > 0 && this._args[this._args.length - 1].variadic) { - return; - } - if (this.args.length > this._args.length) { - this._excessArguments(this.args); - } - }; const commandEvent = `command:${this.name()}`; if (this._actionHandler) { checkForUnknownOptions(); - checkNumberOfArguments(); + this._processArguments(); let actionResult; actionResult = this._chainOrCallHooks(actionResult, 'preAction'); - actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this._getActionArguments())); + actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs)); if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy actionResult = this._chainOrCallHooks(actionResult, 'postAction'); return actionResult; } if (this.parent && this.parent.listenerCount(commandEvent)) { checkForUnknownOptions(); - checkNumberOfArguments(); + this._processArguments(); this.parent.emit(commandEvent, operands, unknown); // legacy } else if (operands.length) { if (this._findCommand('*')) { // legacy default command @@ -1795,14 +1810,14 @@ Expecting one of '${allowedValues.join("', '")}'`); this.unknownCommand(); } else { checkForUnknownOptions(); - checkNumberOfArguments(); + this._processArguments(); } } else if (this.commands.length) { // This command has subcommands and nothing hooked up at this level, so display help (and exit). this.help({ error: true }); } else { checkForUnknownOptions(); - checkNumberOfArguments(); + this._processArguments(); // fall through for caller to handle after calling .parse() } }; @@ -2150,6 +2165,7 @@ Expecting one of '${allowedValues.join("', '")}'`); alias(alias) { if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility + /** @type {Command} */ let command = this; if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) { // assume adding alias for last added executable subcommand, rather than this diff --git a/typings/index.d.ts b/typings/index.d.ts index abeab2be9..d6002a3bd 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -201,6 +201,7 @@ export interface OptionValues { export class Command { args: string[]; + processedArgs: any[]; commands: Command[]; parent: Command | null; diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index a5edb6cf4..dcc3c3f3f 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -25,6 +25,8 @@ expectType(commander.createArgument('')); // Command properties expectType(program.args); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +expectType(program.processedArgs); expectType(program.commands); expectType(program.parent);