8000 Add support for groups of options and commands in the help (#2328) · tj/commander.js@c324ea3 · GitHub
[go: up one dir, main page]

Skip to content

Commit c324ea3

Browse files
authored
Add support for groups of options and commands in the help (#2328)
1 parent 00af603 commit c324ea3

File tree

9 files changed

+556
-47
lines changed

9 files changed

+556
-47
lines changed

Readme.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
3838
- [.description and .summary](#description-and-summary)
3939
- [.helpOption(flags, description)](#helpoptionflags-description)
4040
- [.helpCommand()](#helpcommand)
41+
- [Help Groups](#help-groups)
4142
- [More configuration](#more-configuration-2)
4243
- [Custom event listeners](#custom-event-listeners)
4344
- [Bits and pieces](#bits-and-pieces)
@@ -927,6 +928,14 @@ program.helpCommand('assist [command]', 'show assistance');
927928

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

931+
### Help Groups
932+
933+
The help by default lists options under the the heading `Options:` and commands under `Commands:`. You can create your own groups
934+
with different headings. The high-level way is to set the desired group heading while adding the options and commands,
935+
using `.optionsGroup()` and `.commandsGroup()`. The low-level way is using `.helpGroup()` on an individual `Option` or `Command`
936+
937+
Example file: [help-groups.js](./examples/help-groups.js)
938+
930939
### More configuration
931940

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

examples/help-groups.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const { Command, Option } = require('commander');
2+
3+
// Show the two approaches for adding help groups, and how to customise the built-in help and version.
4+
5+
const program = new Command();
6+
const devOptionsHeading = 'Development Options:';
7+
const managementCommandsHeading = 'Management Commands:';
8+
9+
// The high-level approach is use .optionsGroup() and .commandsGroup() before adding the options/commands.
10+
const docker1 = program
11+
.command('docker1')
12+
.description('help groups created using .optionsGroup() and .commandsGroup()')
13+
.addOption(new Option('-h, --hostname <name>', 'container host name'))
14+
.addOption(new Option('-p, --port <number>', 'container port number'))
15+
.optionsGroup(devOptionsHeading)
16+
.option('-d, --debug', 'add extra trace information')
17+
.option('-w, --watch', 'run and relaunch service on file changes');
18+
19+
docker1
20+
.command('run')
21+
.description('create and run a new container from an image');
22+
docker1.command('exec').description('execute a command in a running container');
23+
24+
docker1.commandsGroup(managementCommandsHeading);
25+
docker1.command('images').description('manage images');
26+
docker1.command('volumes').description('manage volumes');
27+
28+
// The low-level approach is using .helpGroup() on the Option or Command.
29+
const docker2 = program
30+
.command('docker2')
31+
.description('help groups created using .helpGroup()')
32+
.addOption(new Option('-h, --hostname <name>', 'container host name'))
33+
.addOption(new Option('-p, --port <number>', 'container port number'))
34+
.addOption(
35+
new Option('-d, --debug', 'add extra trace information').helpGroup(
36+
devOptionsHeading,
37+
),
38+
)
39+
.addOption(
40+
new Option(
41+
'-w, --watch',
42+
'run and relaunch service on file changes',
43+
).helpGroup(devOptionsHeading),
44+
);
45+
46+
docker2
47+
.command('run')
48+
.description('create and run a new container from an image');
49+
docker2.command('exec').description('execute a command in a running container');
50+
51+
docker2
52+
.command('images')
53+
.description('manage images')
54+
.helpGroup(managementCommandsHeading);
55+
docker2
56+
.command('volumes')
57+
.description('manage volumes')
58+
.helpGroup(managementCommandsHeading);
59+
60+
// Customise group for built-ins by configuring them with default group set.
61+
program
62+
.command('built-in')
63+
.description('help groups for help and version')
64+
.optionsGroup('Built-in Options:')
65+
.version('v2.3.4')
66+
.helpOption('-h, --help') // or .helpOption(true) to use default flags
67+
.commandsGroup('Built-in Commands:')
68+
.helpCommand('help [command]'); // or .helpCommand(true) to use default name
69+
70+
program.parse();
71+
72+
// Try the following:
73+
// node help-groups.js help docker1
74+
// node help-groups.js help docker2
75+
// node help-groups.js help built-in

lib/command.js

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ class Command extends EventEmitter {
8080
/** @type {Command} */
8181
this._helpCommand = undefined; // lazy initialised, inherited
8282
this._helpConfiguration = {};
83+
/** @type {string | undefined} */
84+
this._helpGroupHeading = undefined; // soft initialised when added to parent
85+
/** @type {string | undefined} */
86+
this._defaultCommandGroup = undefined;
87+
/** @type {string | undefined} */
88+
this._defaultOptionGroup = undefined;
8389
}
8490

8591
/**
@@ -404,11 +410,15 @@ class Command extends EventEmitter {
404410
helpCommand(enableOrNameAndArgs, description) {
405411
if (typeof enableOrNameAndArgs === 'boolean') {
406412
this._addImplicitHelpCommand = enableOrNameAndArgs;
413+
if (enableOrNameAndArgs && this._defaultCommandGroup) {
414+
// make the command to store the group
415+
this._initCommandGroup(this._getHelpCommand());
416+
}
407417
return this;
408418
}
409419

410-
enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
411-
const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
420+
const nameAndArgs = enableOrNameAndArgs ?? 'help [command]';
421+
const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
412422
const helpDescription = description ?? 'display help for command';
413423

414424
const helpCommand = this.createCommand(helpName);
@@ -418,6 +428,8 @@ class Command extends EventEmitter {
418428

419429
this._addImplicitHelpCommand = true;
420430
this._helpCommand = helpCommand;
431+
// init group unless lazy create
432+
if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand);
421433

422434
return this;
423435
}
@@ -439,6 +451,7 @@ class Command extends EventEmitter {
439451

440452
this._addImplicitHelpCommand = true;
441453
this._helpCommand = helpCommand;
454+
this._initCommandGroup(helpCommand);
442455
return this;
443456
}
444457

@@ -617,6 +630,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
617630
- already used by option '${matchingOption.flags}'`);
618631
}
619632

633+
this._initOptionGroup(option);
620634
this.options.push(option);
621635
}
622636

@@ -644,6 +658,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
644658
);
645659
}
646660

661+
this._initCommandGroup(command);
647662
this.commands.push(command);
648663
}
649664

@@ -2320,6 +2335,75 @@ Expecting one of '${allowedValues.join("', '")}'`);
23202335
return this;
23212336
}
23222337

2338+
/**
2339+
* Set/get the help group heading for this subcommand in parent command's help.
2340+
*
2341+
* @param {string} [heading]
2342+
* @return {Command | string}
2343+
*/
2344+
2345+
helpGroup(heading) {
2346+
if (heading === undefined) return this._helpGroupHeading ?? '';
2347+
this._helpGroupHeading = heading;
2348+
return this;
2349+
}
2350+
2351+
/**
2352+
* Set/get the default help group heading for subcommands added to this command.
2353+
* (This does not override a group set directly on the subcommand using .helpGroup().)
2354+
*
2355+
* @example
2356+
* program.commandsGroup('Development Commands:);
2357+
* program.command('watch')...
2358+
* program.command('lint')...
2359+
* ...
2360+
*
2361+
* @param {string} [heading]
2362+
* @returns {Command | string}
2363+
*/
2364+
commandsGroup(heading) {
2365+
if (heading === undefined) return this._defaultCommandGroup ?? '';
2366+
this._defaultCommandGroup = heading;
2367+
return this;
2368+
}
2369+
2370+
/**
2371+
* Set/get the default help group heading for options added to this command.
2372+
* (This does not override a group set directly on the option using .helpGroup().)
2373+
*
2374+
* @example
2375+
* program
2376+
* .optionsGroup('Development Options:')
2377+
* .option('-d, --debug', 'output extra debugging')
2378+
* .option('-p, --profile', 'output profiling information')
2379+
*
2380+
* @param {string} [heading]
2381+
* @returns {Command | string}
2382+
*/
2383+
optionsGroup(heading) {
2384+
if (heading === undefined) return this._defaultOptionGroup ?? '';
2385+
this._defaultOptionGroup = heading;
2386+
return this;
2387+
}
2388+
2389+
/**
2390+
* @param {Option} option
2391+
* @private
2392+
*/
2393+
_initOptionGroup(option) {
2394+
if (this._defaultOptionGroup && !option.helpGroupHeading)
2395+
option.helpGroup(this._defaultOptionGroup);
2396+
}
2397+
2398+
/**
2399+
* @param {Command} cmd
2400+
* @private
2401+
*/
2402+
_initCommandGroup(cmd) {
2403+
if (this._defaultCommandGroup && !cmd.helpGroup())
2404+
cmd.helpGroup(this._defaultCommandGroup);
2405+
}
2406+
23232407
/**
23242408
* Set the name of the command from script filename, such as process.argv[1],
23252409
* or require.main.filename, or __filename.
@@ -2474,22 +2558,27 @@ Expecting one of '${allowedValues.join("', '")}'`);
24742558
*/
24752559

24762560
helpOption(flags, description) {
2477-
// Support disabling built-in help option.
2561+
// Support enabling/disabling built-in help option.
24782562
if (typeof flags === 'boolean') {
2479-
// true is not an expected value. Do something sensible but no unit-test.
2480-
// istanbul ignore if
24812563
if (flags) {
2482-
this._helpOption = this._helpOption ?? undefined; // preserve existing option
2564+
if (this._helpOption === null) this._helpOption = undefined; // reenable
2565+
if (this._defaultOptionGroup) {
2566+
// make the option to store the group
2567+
this._initOptionGroup(this._getHelpOption());
2568+
}
24832569
} else {
24842570
this._helpOption = null; // disable
24852571
}
24862572
return this;
24872573
}
24882574

24892575
// Customise flags and description.
2490-
flags = flags ?? '-h, --help';
2491-
description = description ?? 'display help for command';
2492-
this._helpOption = this.createOption(flags, description);
2576+
this._helpOption = this.createOption(
2577+
flags ?? '-h, --help',
2578+
description ?? 'display help for command',
2579+
);
2580+
// init group unless lazy create
2581+
if (flags || description) this._initOptionGroup(this._helpOption);
24932582

24942583
return this;
24952584
}
@@ -2518,6 +2607,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
25182607
*/
25192608
addHelpOption(option) {
25202609
this._helpOption = option;
2610+
this._initOptionGroup(option);
25212611
return this;
25222612
}
25232613

0 commit comments

Comments
 (0)
0