Skip to content

Commit

Permalink
Refactor help command implementation to hold actual Command (#2087)
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowspawn committed Jan 11, 2024
1 parent 32c05a8 commit ff08a02
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 142 deletions.
10 changes: 6 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
- [.usage](#usage)
- [.description and .summary](#description-and-summary)
- [.helpOption(flags, description)](#helpoptionflags-description)
- [.addHelpCommand()](#addhelpcommand)
- [.helpCommand()](#helpcommand)
- [More configuration](#more-configuration-2)
- [Custom event listeners](#custom-event-listeners)
- [Bits and pieces](#bits-and-pieces)
Expand Down Expand Up @@ -904,16 +904,18 @@ program
.helpOption('-e, --HELP', 'read more information');
```

### .addHelpCommand()
### .helpCommand()

A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`.
A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.helpCommand(true)` and `.helpCommand(false)`.

You can both turn on and customise the help command by supplying the name and description:

```js
program.addHelpCommand('assist [command]', 'show assistance');
program.helpCommand('assist [command]', 'show assistance');
```

(Or use `.addHelpCommand()` to add a command you construct yourself.)

### More configuration

The built-in help is formatted using the Help class.
Expand Down
22 changes: 22 additions & 0 deletions docs/deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ They are currently still available for backwards compatibility, but should not b
- [Short option flag longer than a single character](#short-option-flag-longer-than-a-single-character)
- [Import from `commander/esm.mjs`](#import-from-commanderesmmjs)
- [cmd.\_args](#cmd_args)
- [.addHelpCommand(string|boolean|undefined)](#addhelpcommandstringbooleanundefined)
- [Removed](#removed)
- [Default import of global Command object](#default-import-of-global-command-object)

Expand Down Expand Up @@ -205,6 +206,27 @@ const registeredArguments = program.registeredArguments;

Deprecated from Commander v11.

### .addHelpCommand(string|boolean|undefined)

This was originally used with a variety of parameters, but not by passing a Command object despite the "add" name.

```js
program.addHelpCommand('assist [command]');
program.addHelpCommand('assist', 'show assistance');
program.addHelpCommand(false);

```

In new code you configure the help command with `.helpCommand()`. Or use `.addHelpCommand()` which now takes a Command object, like `.addCommand()`.

```js
program.helpCommand('assist [command]');
program.helpCommand('assist', 'show assistance');
program.helpCommand(false);

program.addHelpCommand(new Command('assist').argument('[command]').description('show assistance'));

```
## Removed

### Default import of global Command object
Expand Down
92 changes: 63 additions & 29 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ class Command extends EventEmitter {
this._helpDescription = 'display help for command';
this._helpShortFlag = '-h';
this._helpLongFlag = '--help';
this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
this._helpCommandName = 'help';
this._helpCommandnameAndArgs = 'help [command]';
this._helpCommandDescription = 'display help for command';
this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
/** @type {Command} */
this._helpCommand = undefined; // lazy initialised, inherited
this._helpConfiguration = {};
}

Expand All @@ -93,9 +92,7 @@ class Command extends EventEmitter {
this._helpDescription = sourceCommand._helpDescription;
this._helpShortFlag = sourceCommand._helpShortFlag;
this._helpLongFlag = sourceCommand._helpLongFlag;
this._helpCommandName = sourceCommand._helpCommandName;
this._helpCommandnameAndArgs = sourceCommand._helpCommandnameAndArgs;
this._helpCommandDescription = sourceCommand._helpCommandDescription;
this._helpCommand = sourceCommand._helpCommand;
this._helpConfiguration = sourceCommand._helpConfiguration;
this._exitCallback = sourceCommand._exitCallback;
this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
Expand Down Expand Up @@ -369,39 +366,76 @@ class Command extends EventEmitter {
}

/**
* Override default decision whether to add implicit help command.
* Customise or override default help command. By default a help command is automatically added if your command has subcommands.
*
* addHelpCommand() // force on
* addHelpCommand(false); // force off
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
* program.helpCommand('help [cmd]');
* program.helpCommand('help [cmd]', 'show help');
* program.helpCommand(false); // suppress default help command
* program.helpCommand(true); // add help command even if no subcommands
*
* @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added
* @param {string} [description] - custom description
* @return {Command} `this` command for chaining
*/

addHelpCommand(enableOrNameAndArgs, description) {
if (enableOrNameAndArgs === false) {
this._addImplicitHelpCommand = false;
} else {
this._addImplicitHelpCommand = true;
if (typeof enableOrNameAndArgs === 'string') {
this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
this._helpCommandnameAndArgs = enableOrNameAndArgs;
}
this._helpCommandDescription = description || this._helpCommandDescription;
helpCommand(enableOrNameAndArgs, description) {
if (typeof enableOrNameAndArgs === 'boolean') {
this._addImplicitHelpCommand = enableOrNameAndArgs;
return this;
}

enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
const helpDescription = description ?? 'display help for command';

const helpCommand = this.createCommand(helpName);
helpCommand.helpOption(false);
if (helpArgs) helpCommand.arguments(helpArgs);
if (helpDescription) helpCommand.description(helpDescription);

this._addImplicitHelpCommand = true;
this._helpCommand = helpCommand;

return this;
}

/**
* @return {boolean}
* @package internal use only
* Add prepared custom help command.
*
* @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`
* @param {string} [deprecatedDescription] - deprecated custom description used with custom name only
* @return {Command} `this` command for chaining
*/
addHelpCommand(helpCommand, deprecatedDescription) {
// If not passed an object, call through to helpCommand for backwards compatibility,
// as addHelpCommand was originally used like helpCommand is now.
if (typeof helpCommand !== 'object') {
this.helpCommand(helpCommand, deprecatedDescription);
return this;
}

this._addImplicitHelpCommand = true;
this._helpCommand = helpCommand;
return this;
}

_hasImplicitHelpCommand() {
if (this._addImplicitHelpCommand === undefined) {
return this.commands.length && !this._actionHandler && !this._findCommand('help');
/**
* Lazy create help command.
*
* @return {(Command|null)}
* @package
*/
_getHelpCommand() {
const hasImplicitHelpCommand = this._addImplicitHelpCommand ??
(this.commands.length && !this._actionHandler && !this._findCommand('help'));

if (hasImplicitHelpCommand) {
if (this._helpCommand === undefined) {
this.helpCommand(undefined, undefined); // use default name and description
}
return this._helpCommand;
}
return this._addImplicitHelpCommand;
return null;
}

/**
Expand Down Expand Up @@ -1315,7 +1349,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
if (operands && this._findCommand(operands[0])) {
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
}
if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
return this._dispatchHelpCommand(operands[1]);
}
if (this._defaultCommandName) {
Expand Down Expand Up @@ -1572,7 +1606,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
operands.push(arg);
if (args.length > 0) unknown.push(...args);
break;
} else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
} else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
operands.push(arg);
if (args.length > 0) operands.push(...args);
break;
Expand Down
9 changes: 2 additions & 7 deletions lib/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,8 @@ class Help {

visibleCommands(cmd) {
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
if (cmd._hasImplicitHelpCommand()) {
// Create a command matching the implicit help command.
const [, helpName, helpArgs] = cmd._helpCommandnameAndArgs.match(/([^ ]+) *(.*)/);
const helpCommand = cmd.createCommand(helpName)
.helpOption(false);
helpCommand.description(cmd._helpCommandDescription);
if (helpArgs) helpCommand.arguments(helpArgs);
const helpCommand = cmd._getHelpCommand();
if (helpCommand && !helpCommand._hidden) {
visibleCommands.push(helpCommand);
}
if (this.sortSubcommands) {
Expand Down

0 comments on commit ff08a02

Please sign in to comment.