Skip to content

Commit

Permalink
Make user-supplied option values non-persistent while parsing
Browse files Browse the repository at this point in the history
Relevant for setOptionValue() and setOptionValueWithSource() calls in
hooks and actions.

_asyncParsing is stored to avoid redundant property when merged with
tj#1915 or tj#1917.
  • Loading branch information
aweebit committed Jul 29, 2023
1 parent 52d5885 commit 94e439d
Showing 1 changed file with 78 additions and 66 deletions.
144 changes: 78 additions & 66 deletions lib/command.js
Expand Up @@ -74,6 +74,9 @@ class Command extends EventEmitter {
this._persistentOptionValues = {};
this._persistentOptionValueSources = {};
this.resetParseState();

/** @type {boolean | undefined} */
this._asyncParsing = undefined;
}

resetParseState() {
Expand Down Expand Up @@ -804,7 +807,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/

setOptionValueWithSource(key, value, source) {
this._setPersistentOptionValueWithSource(key, value, source);
const set = this._asyncParsing === undefined
? this._setPersistentOptionValueWithSource
: this._setNonPersistentOptionValueWithSource;
set(key, value, source);
return this;
}

Expand Down Expand Up @@ -1307,81 +1313,87 @@ Expecting one of '${allowedValues.join("', '")}'`);
* @api private
*/

_parseCommand(operands, unknown) {
const parsed = this.parseOptions(unknown);
this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
this._parseOptionsImplied();
operands = operands.concat(parsed.operands);
unknown = parsed.unknown;
this.args = operands.concat(unknown);

if (operands && this._findCommand(operands[0])) {
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
}
if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
return this._dispatchHelpCommand(operands[1]);
}
if (this._defaultCommandName) {
outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
}
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
// probably missing subcommand and no handler, user needs help (and exit)
this.help({ error: true });
}
_parseCommand(operands, unknown, async) {
this._asyncParsing = async;

outputHelpIfRequested(this, parsed.unknown);
this._checkForMissingMandatoryOptions();
this._checkForConflictingOptions();
try {
const parsed = this.parseOptions(unknown);
this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
this._parseOptionsImplied();
operands = operands.concat(parsed.operands);
unknown = parsed.unknown;
this.args = operands.concat(unknown);

// We do not always call this check to avoid masking a "better" error, like unknown command.
const checkForUnknownOptions = () => {
if (parsed.unknown.length > 0) {
this.unknownOption(parsed.unknown[0]);
if (operands && this._findCommand(operands[0])) {
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
}
};

const commandEvent = `command:${this.name()}`;
if (this._actionHandler) {
checkForUnknownOptions();
this._processArguments();

let actionResult;
actionResult = this._chainOrCallHooks(actionResult, 'preAction');
actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs));
if (this.parent) {
actionResult = this._chainOrCall(actionResult, () => {
this.parent.emit(commandEvent, operands, unknown); // legacy
});
if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
return this._dispatchHelpCommand(operands[1]);
}
actionResult = this._chainOrCallHooks(actionResult, 'postAction');
return actionResult;
}
if (this.parent && this.parent.listenerCount(commandEvent)) {
checkForUnknownOptions();
this._processArguments();
this.parent.emit(commandEvent, operands, unknown); // legacy
} else if (operands.length) {
if (this._findCommand('*')) { // legacy default command
return this._dispatchSubcommand('*', operands, unknown);
if (this._defaultCommandName) {
outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
}
if (this.listenerCount('command:*')) {
// skip option check, emit event for possible misspelling suggestion
this.emit('command:*', operands, unknown);
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
// probably missing subcommand and no handler, user needs help (and exit)
this.help({ error: true });
}

outputHelpIfRequested(this, parsed.unknown);
this._checkForMissingMandatoryOptions();
this._checkForConflictingOptions();

// We do not always call this check to avoid masking a "better" error, like unknown command.
const checkForUnknownOptions = () => {
if (parsed.unknown.length > 0) {
this.unknownOption(parsed.unknown[0]);
}
};

const commandEvent = `command:${this.name()}`;
if (this._actionHandler) {
checkForUnknownOptions();
this._processArguments();

let actionResult;
actionResult = this._chainOrCallHooks(actionResult, 'preAction');
actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs));
if (this.parent) {
actionResult = this._chainOrCall(actionResult, () => {
this.parent.emit(commandEvent, operands, unknown); // legacy
});
}
actionResult = this._chainOrCallHooks(actionResult, 'postAction');
return actionResult;
}
if (this.parent && this.parent.listenerCount(commandEvent)) {
checkForUnknownOptions();
this._processArguments();
this.parent.emit(commandEvent, operands, unknown); // legacy
} else if (operands.length) {
if (this._findCommand('*')) { // legacy default command
return this._dispatchSubcommand('*', operands, unknown);
}
if (this.listenerCount('command:*')) {
// skip option check, emit event for possible misspelling suggestion
this.emit('command:*', operands, unknown);
} else if (this.commands.length) {
this.unknownCommand();
} else {
checkForUnknownOptions();
this._processArguments();
}
} else if (this.commands.length) {
this.unknownCommand();
checkForUnknownOptions();
// This command has subcommands and nothing hooked up at this level, so display help (and exit).
this.help({ error: true });
} else {
checkForUnknownOptions();
this._processArguments();
// fall through for caller to handle after calling .parse()
}
} else if (this.commands.length) {
checkForUnknownOptions();
// This command has subcommands and nothing hooked up at this level, so display help (and exit).
this.help({ error: true });
} else {
checkForUnknownOptions();
this._processArguments();
// fall through for caller to handle after calling .parse()
} finally {
this._asyncParsing = undefined;
}
}

Expand Down

0 comments on commit 94e439d

Please sign in to comment.