Skip to content

Commit

Permalink
Refactor help option
Browse files Browse the repository at this point in the history
Eliminates outputHelpIfRequested() and incorporates help option parsing
into .parseOptions() instead for consistency with other options.

Additionally, improvements to the .parseOptions() code have been made,
first and foremost to the comments.
  • Loading branch information
aweebit committed Aug 3, 2023
1 parent 5128d5f commit 5b93a70
Showing 1 changed file with 66 additions and 60 deletions.
126 changes: 66 additions & 60 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,21 +1271,26 @@ Expecting one of '${allowedValues.join("', '")}'`);
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
// Run the help for default command from parent rather than passing to default command
const helpOptionProvided = this._hasHelpOption && unknown.find(
arg => arg === this._helpOption.long || arg === this._helpOption.short
);
if (helpOptionProvided) {
this.emit('option:' + this._helpOption.name());
}
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 });
}

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) {
if (unknown.length > 0) {
this.unknownOption(parsed.unknown[0]);
}
};
Expand Down Expand Up @@ -1445,6 +1450,7 @@ Expecting one of '${allowedValues.join("', '")}'`);

// parse options
let activeVariadicOption = null;
let subcommandEncountered = false;
while (args.length) {
const arg = args.shift();

Expand All @@ -1462,25 +1468,30 @@ Expecting one of '${allowedValues.join("', '")}'`);
activeVariadicOption = null;

if (maybeOption(arg)) {
const option = this._findOption(arg);
// recognised option, call listener to assign value with possible custom processing
if (option) {
if (option.required) {
const value = args.shift();
if (value === undefined) this.optionMissingArgument(option);
this.emit(`option:${option.name()}`, value);
} else if (option.optional) {
let value = null;
// historical behaviour is optional value is following arg unless an option
if (args.length > 0 && !maybeOption(args[0])) {
value = args.shift();
const isHelpOption = this._hasHelpOption && this._helpOption.is(arg);
// Help option is always positional, skip when encountered after subcommand.
if (!(isHelpOption && subcommandEncountered)) {
// Options added via .option() / .addOption() have precedence over help option.
const option = this._findOption(arg) ?? (isHelpOption && this._helpOption);
// recognised option, call listener to assign value with possible custom processing
if (option) {
if (option.required) {
const value = args.shift();
if (value === undefined) this.optionMissingArgument(option);
this.emit(`option:${option.name()}`, value);
} else if (option.optional) {
let value = null;
// historical behaviour is optional value is following arg unless an option
if (args.length > 0 && !maybeOption(args[0])) {
value = args.shift();
}
this.emit(`option:${option.name()}`, value);
} else { // boolean flag
this.emit(`option:${option.name()}`);
}
this.emit(`option:${option.name()}`, value);
} else { // boolean flag
this.emit(`option:${option.name()}`);
activeVariadicOption = option.variadic ? option : null;
continue;
}
activeVariadicOption = option.variadic ? option : null;
continue;
}
}

Expand Down Expand Up @@ -1511,34 +1522,43 @@ Expecting one of '${allowedValues.join("', '")}'`);
}

// Not a recognised option by this command.
// Might be a command-argument, or subcommand option, or unknown option, or help command or option.

// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
if (maybeOption(arg)) {
dest = unknown;
}

// If using positionalOptions, stop processing our options at subcommand.
if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
// Might be a subcommand, or command-argument, or help option encountered after subcommand, or subcommand option, or unknown option.

const allArgsConsumedAsOptionsSoFar = operands.length === 0 && unknown.length === 0;
if (!subcommandEncountered && allArgsConsumedAsOptionsSoFar) {
subcommandEncountered = true; // reset to false later if arg is not a subcommand
const stopAtSubcommand = (
this._enablePositionalOptions || this._passThroughOptions
);
if (this._findCommand(arg)) {
operands.push(arg);
if (args.length > 0) unknown.push(...args);
break;
if (stopAtSubcommand) {
operands.push(arg);
unknown.push(...args);
break;
}
} else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
operands.push(arg);
if (args.length > 0) operands.push(...args);
break;
if (stopAtSubcommand) {
operands.push(arg, ...args);
break;
}
} else if (this._defaultCommandName) {
unknown.push(arg);
if (args.length > 0) unknown.push(...args);
break;
if (stopAtSubcommand) {
unknown.push(arg, ...args);
break;
}
} else {
subcommandEncountered = false;
}
}

// If using passThroughOptions, stop processing options at first command-argument.
// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
if (maybeOption(arg)) {
dest = unknown;
}

// If using passThroughOptions, stop processing options at first command-argument / unknown option.
if (this._passThroughOptions) {
dest.push(arg);
if (args.length > 0) dest.push(...args);
dest.push(arg, ...args);
break;
}

Expand Down Expand Up @@ -2059,9 +2079,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
this._hasHelpOption = true;
this._helpFlags = flags = flags || this._helpFlags;
this._helpDescription = description = description || this._helpDescription;

this._helpOption = this.createOption(flags, description);

this.on('option:' + this._helpOption.name(), () => {
this.outputHelp();
// (Do not have all displayed text available so only passing placeholder.)
this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
});
return this;
}

Expand Down Expand Up @@ -2116,23 +2139,6 @@ Expecting one of '${allowedValues.join("', '")}'`);
}
}

/**
* Output help information if help flags specified
*
* @param {Command} cmd - command to output help for
* @param {Array} args - array of options to search for help flags
* @api private
*/

function outputHelpIfRequested(cmd, args) {
const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpOption.long || arg === cmd._helpOption.short);
if (helpOption) {
cmd.outputHelp();
// (Do not have all displayed text available so only passing placeholder.)
cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
}
}

/**
* Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
*
Expand Down

0 comments on commit 5b93a70

Please sign in to comment.