Skip to content
Merged
9 changes: 9 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
- [.description and .summary](#description-and-summary)
- [.helpOption(flags, description)](#helpoptionflags-description)
- [.helpCommand()](#helpcommand)
- [Help Groups](#help-groups)
- [More configuration](#more-configuration-2)
- [Custom event listeners](#custom-event-listeners)
- [Bits and pieces](#bits-and-pieces)
Expand Down Expand Up @@ -926,6 +927,14 @@ program.helpCommand('assist [command]', 'show assistance');

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

### Help Groups

The help by default lists options under the the heading `Options:` and commands under `Commands:`. You can create your own groups
with different headings. The high-level way is to set the desired group heading while adding the options and commands,
using `.optionsGroup()` and `.commandsGroup()`. The low-level way is using `.helpGroup()` on an individual `Option` or `Command`

Example file: [help-groups.js](./examples/help-groups.js)

### More configuration

The built-in help is formatted using the Help class.
Expand Down
75 changes: 75 additions & 0 deletions examples/help-groups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const { Command, Option } = require('commander');

// Show the two approaches for adding help groups, and how to customise the built-in help and version.

const program = new Command();
const devOptionsHeading = 'Development Options:';
const managementCommandsHeading = 'Management Commands:';

// The high-level approach is use .optionsGroup() and .commandsGroup() before adding the options/commands.
const docker1 = program
.command('docker1')
.description('help groups created using .optionsGroup() and .commandsGroup()')
.addOption(new Option('-h, --hostname <name>', 'container host name'))
.addOption(new Option('-p, --port <number>', 'container port number'))
.optionsGroup(devOptionsHeading)
.option('-d, --debug', 'add extra trace information')
.option('-w, --watch', 'run and relaunch service on file changes');

docker1
.command('run')
.description('create and run a new container from an image');
docker1.command('exec').description('execute a command in a running container');

docker1.commandsGroup(managementCommandsHeading);
docker1.command('images').description('manage images');
docker1.command('volumes').description('manage volumes');

// The low-level approach is using .helpGroup() on the Option or Command.
const docker2 = program
.command('docker2')
.description('help groups created using .helpGroup()')
.addOption(new Option('-h, --hostname <name>', 'container host name'))
.addOption(new Option('-p, --port <number>', 'container port number'))
.addOption(
new Option('-d, --debug', 'add extra trace information').helpGroup(
devOptionsHeading,
),
)
.addOption(
new Option(
'-w, --watch',
'run and relaunch service on file changes',
).helpGroup(devOptionsHeading),
);

docker2
.command('run')
.description('create and run a new container from an image');
docker2.command('exec').description('execute a command in a running container');

docker2
.command('images')
.description('manage images')
.helpGroup(managementCommandsHeading);
docker2
.command('volumes')
.description('manage volumes')
.helpGroup(managementCommandsHeading);

// Customise group for built-ins by configuring them with default group set.
program
.command('built-in')
.description('help groups for help and version')
.optionsGroup('Built-in Options:')
.version('v2.3.4')
.helpOption('-h, --help') // or .helpOption(true) to use default flags
.commandsGroup('Built-in Commands:')
.helpCommand('help [command]'); // or .helpCommand(true) to use default name

program.parse();

// Try the following:
// node help-groups.js help docker1
// node help-groups.js help docker2
// node help-groups.js help built-in
108 changes: 99 additions & 9 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ class Command extends EventEmitter {
/** @type {Command} */
this._helpCommand = undefined; // lazy initialised, inherited
this._helpConfiguration = {};
/** @type {string | undefined} */
this._helpGroupHeading = undefined; // soft initialised when added to parent
/** @type {string | undefined} */
this._defaultCommandGroup = undefined;
/** @type {string | undefined} */
this._defaultOptionGroup = undefined;
}

/**
Expand Down Expand Up @@ -400,11 +406,15 @@ class Command extends EventEmitter {
helpCommand(enableOrNameAndArgs, description) {
if (typeof enableOrNameAndArgs === 'boolean') {
this._addImplicitHelpCommand = enableOrNameAndArgs;
if (enableOrNameAndArgs && this._defaultCommandGroup) {
// make the command to store the group
this._initCommandGroup(this._getHelpCommand());
}
return this;
}

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

const helpCommand = this.createCommand(helpName);
Expand All @@ -414,6 +424,8 @@ class Command extends EventEmitter {

this._addImplicitHelpCommand = true;
this._helpCommand = helpCommand;
// init group unless lazy create
if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand);

return this;
}
Expand All @@ -435,6 +447,7 @@ class Command extends EventEmitter {

this._addImplicitHelpCommand = true;
this._helpCommand = helpCommand;
this._initCommandGroup(helpCommand);
return this;
}

Expand Down Expand Up @@ -613,6 +626,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
- already used by option '${matchingOption.flags}'`);
}

this._initOptionGroup(option);
this.options.push(option);
}

Expand Down Expand Up @@ -640,6 +654,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
);
}

this._initCommandGroup(command);
this.commands.push(command);
}

Expand Down Expand Up @@ -2294,6 +2309,75 @@ Expecting one of '${allowedValues.join("', '")}'`);
return this;
}

/**
* Set/get the help group heading for this subcommand in parent command's help.
*
* @param {string} [heading]
* @return {Command | string}
*/

helpGroup(heading) {
if (heading === undefined) return this._helpGroupHeading ?? '';
this._helpGroupHeading = heading;
return this;
}

/**
* Set/get the default help group heading for subcommands added to this command.
* (This does not override a group set directly on the subcommand using .helpGroup().)
*
* @example
* program.commandsGroup('Development Commands:);
* program.command('watch')...
* program.command('lint')...
* ...
*
* @param {string} [heading]
* @returns {Command | string}
*/
commandsGroup(heading) {
if (heading === undefined) return this._defaultCommandGroup ?? '';
this._defaultCommandGroup = heading;
return this;
}

/**
* Set/get the default help group heading for options added to this command.
* (This does not override a group set directly on the option using .helpGroup().)
*
* @example
* program
* .optionsGroup('Development Options:')
* .option('-d, --debug', 'output extra debugging')
* .option('-p, --profile', 'output profiling information')
*
* @param {string} [heading]
* @returns {Command | string}
*/
optionsGroup(heading) {
if (heading === undefined) return this._defaultOptionGroup ?? '';
this._defaultOptionGroup = heading;
return this;
}

/**
* @param {Option} option
* @private
*/
_initOptionGroup(option) {
if (this._defaultOptionGroup && !option.helpGroupHeading)
option.helpGroup(this._defaultOptionGroup);
}

/**
* @param {Command} cmd
* @private
*/
_initCommandGroup(cmd) {
if (this._defaultCommandGroup && !cmd.helpGroup())
cmd.helpGroup(this._defaultCommandGroup);
}

/**
* Set the name of the command from script filename, such as process.argv[1],
* or require.main.filename, or __filename.
Expand Down Expand Up @@ -2448,22 +2532,27 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/

helpOption(flags, description) {
// Support disabling built-in help option.
// Support enabling/disabling built-in help option.
if (typeof flags === 'boolean') {
// true is not an expected value. Do something sensible but no unit-test.
// istanbul ignore if
if (flags) {
this._helpOption = this._helpOption ?? undefined; // preserve existing option
if (this._helpOption === null) this._helpOption = undefined; // reenable
if (this._defaultOptionGroup) {
// make the option to store the group
this._initOptionGroup(this._getHelpOption());
}
} else {
this._helpOption = null; // disable
}
return this;
}

// Customise flags and description.
flags = flags ?? '-h, --help';
description = description ?? 'display help for command';
this._helpOption = this.createOption(flags, description);
this._helpOption = this.createOption(
flags ?? '-h, --help',
description ?? 'display help for command',
);
// init group unless lazy create
if (flags || description) this._initOptionGroup(this._helpOption);

return this;
}
Expand Down Expand Up @@ -2492,6 +2581,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/
addHelpOption(option) {
this._helpOption = option;
this._initOptionGroup(option);
return this;
}

Expand Down
Loading
Loading