Skip to content

Commit ff08a02

Browse files
authored
Refactor help command implementation to hold actual Command (#2087)
1 parent 32c05a8 commit ff08a02

9 files changed

+295
-142
lines changed

Readme.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
3737
- [.usage](#usage)
3838
- [.description and .summary](#description-and-summary)
3939
- [.helpOption(flags, description)](#helpoptionflags-description)
40-
- [.addHelpCommand()](#addhelpcommand)
40+
- [.helpCommand()](#helpcommand)
4141
- [More configuration](#more-configuration-2)
4242
- [Custom event listeners](#custom-event-listeners)
4343
- [Bits and pieces](#bits-and-pieces)
@@ -904,16 +904,18 @@ program
904904
.helpOption('-e, --HELP', 'read more information');
905905
```
906906

907-
### .addHelpCommand()
907+
### .helpCommand()
908908

909-
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)`.
909+
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)`.
910910

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

913913
```js
914-
program.addHelpCommand('assist [command]', 'show assistance');
914+
program.helpCommand('assist [command]', 'show assistance');
915915
```
916916

917+
(Or use `.addHelpCommand()` to add a command you construct yourself.)
918+
917919
### More configuration
918920

919921
The built-in help is formatted using the Help class.

docs/deprecated.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ They are currently still available for backwards compatibility, but should not b
1515
- [Short option flag longer than a single character](#short-option-flag-longer-than-a-single-character)
1616
- [Import from `commander/esm.mjs`](#import-from-commanderesmmjs)
1717
- [cmd.\_args](#cmd_args)
18+
- [.addHelpCommand(string|boolean|undefined)](#addhelpcommandstringbooleanundefined)
1819
- [Removed](#removed)
1920
- [Default import of global Command object](#default-import-of-global-command-object)
2021

@@ -205,6 +206,27 @@ const registeredArguments = program.registeredArguments;
205206

206207
Deprecated from Commander v11.
207208

209+
### .addHelpCommand(string|boolean|undefined)
210+
211+
This was originally used with a variety of parameters, but not by passing a Command object despite the "add" name.
212+
213+
```js
214+
program.addHelpCommand('assist [command]');
215+
program.addHelpCommand('assist', 'show assistance');
216+
program.addHelpCommand(false);
217+
218+
```
219+
220+
In new code you configure the help command with `.helpCommand()`. Or use `.addHelpCommand()` which now takes a Command object, like `.addCommand()`.
221+
222+
```js
223+
program.helpCommand('assist [command]');
224+
program.helpCommand('assist', 'show assistance');
225+
program.helpCommand(false);
226+
227+
program.addHelpCommand(new Command('assist').argument('[command]').description('show assistance'));
228+
229+
```
208230
## Removed
209231

210232
### Default import of global Command object

lib/command.js

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,9 @@ class Command extends EventEmitter {
7171
this._helpDescription = 'display help for command';
7272
this._helpShortFlag = '-h';
7373
this._helpLongFlag = '--help';
74-
this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
75-
this._helpCommandName = 'help';
76-
this._helpCommandnameAndArgs = 'help [command]';
77-
this._helpCommandDescription = 'display help for command';
74+
this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
75+
/** @type {Command} */
76+
this._helpCommand = undefined; // lazy initialised, inherited
7877
this._helpConfiguration = {};
7978
}
8079

@@ -93,9 +92,7 @@ class Command extends EventEmitter {
9392
this._helpDescription = sourceCommand._helpDescription;
9493
this._helpShortFlag = sourceCommand._helpShortFlag;
9594
this._helpLongFlag = sourceCommand._helpLongFlag;
96-
this._helpCommandName = sourceCommand._helpCommandName;
97-
this._helpCommandnameAndArgs = sourceCommand._helpCommandnameAndArgs;
98-
this._helpCommandDescription = sourceCommand._helpCommandDescription;
95+
this._helpCommand = sourceCommand._helpCommand;
9996
this._helpConfiguration = sourceCommand._helpConfiguration;
10097
this._exitCallback = sourceCommand._exitCallback;
10198
this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
@@ -369,39 +366,76 @@ class Command extends EventEmitter {
369366
}
370367

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

381-
addHelpCommand(enableOrNameAndArgs, description) {
382-
if (enableOrNameAndArgs === false) {
383-
this._addImplicitHelpCommand = false;
384-
} else {
385-
this._addImplicitHelpCommand = true;
386-
if (typeof enableOrNameAndArgs === 'string') {
387-
this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
388-
this._helpCommandnameAndArgs = enableOrNameAndArgs;
389-
}
390-
this._helpCommandDescription = description || this._helpCommandDescription;
381+
helpCommand(enableOrNameAndArgs, description) {
382+
if (typeof enableOrNameAndArgs === 'boolean') {
383+
this._addImplicitHelpCommand = enableOrNameAndArgs;
384+
return this;
391385
}
386+
387+
enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
388+
const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
389+
const helpDescription = description ?? 'display help for command';
390+
391+
const helpCommand = this.createCommand(helpName);
392+
helpCommand.helpOption(false);
393+
if (helpArgs) helpCommand.arguments(helpArgs);
394+
if (helpDescription) helpCommand.description(helpDescription);
395+
396+
this._addImplicitHelpCommand = true;
397+
this._helpCommand = helpCommand;
398+
392399
return this;
393400
}
394401

395402
/**
396-
* @return {boolean}
397-
* @package internal use only
403+
* Add prepared custom help command.
404+
*
405+
* @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`
406+
* @param {string} [deprecatedDescription] - deprecated custom description used with custom name only
407+
* @return {Command} `this` command for chaining
398408
*/
409+
addHelpCommand(helpCommand, deprecatedDescription) {
410+
// If not passed an object, call through to helpCommand for backwards compatibility,
411+
// as addHelpCommand was originally used like helpCommand is now.
412+
if (typeof helpCommand !== 'object') {
413+
this.helpCommand(helpCommand, deprecatedDescription);
414+
return this;
415+
}
416+
417+
this._addImplicitHelpCommand = true;
418+
this._helpCommand = helpCommand;
419+
return this;
420+
}
399421

400-
_hasImplicitHelpCommand() {
401-
if (this._addImplicitHelpCommand === undefined) {
402-
return this.commands.length && !this._actionHandler && !this._findCommand('help');
422+
/**
423+
* Lazy create help command.
424+
*
425+
* @return {(Command|null)}
426+
* @package
427+
*/
428+
_getHelpCommand() {
429+
const hasImplicitHelpCommand = this._addImplicitHelpCommand ??
430+
(this.commands.length && !this._actionHandler && !this._findCommand('help'));
431+
432+
if (hasImplicitHelpCommand) {
433+
if (this._helpCommand === undefined) {
434+
this.helpCommand(undefined, undefined); // use default name and description
435+
}
436+
return this._helpCommand;
403437
}
404-
return this._addImplicitHelpCommand;
438+
return null;
405439
}
406440

407441
/**
@@ -1315,7 +1349,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
13151349
if (operands && this._findCommand(operands[0])) {
13161350
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
13171351
}
1318-
if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
1352+
if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
13191353
return this._dispatchHelpCommand(operands[1]);
13201354
}
13211355
if (this._defaultCommandName) {
@@ -1572,7 +1606,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
15721606
operands.push(arg);
15731607
if (args.length > 0) unknown.push(...args);
15741608
break;
1575-
} else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
1609+
} else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
15761610
operands.push(arg);
15771611
if (args.length > 0) operands.push(...args);
15781612
break;

lib/help.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,8 @@ class Help {
2626

2727
visibleCommands(cmd) {
2828
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
29-
if (cmd._hasImplicitHelpCommand()) {
30-
// Create a command matching the implicit help command.
31-
const [, helpName, helpArgs] = cmd._helpCommandnameAndArgs.match(/([^ ]+) *(.*)/);
32-
const helpCommand = cmd.createCommand(helpName)
33-
.helpOption(false);
34-
helpCommand.description(cmd._helpCommandDescription);
35-
if (helpArgs) helpCommand.arguments(helpArgs);
29+
const helpCommand = cmd._getHelpCommand();
30+
if (helpCommand && !helpCommand._hidden) {
3631
visibleCommands.push(helpCommand);
3732
}
3833
if (this.sortSubcommands) {

0 commit comments

Comments
 (0)