Skip to content

Commit 09244af

Browse files
authored
Refactor help option implementation to hold actual Option (#2006)
1 parent ff08a02 commit 09244af

11 files changed

+184
-84
lines changed

Readme.md

+2
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,8 @@ program
904904
.helpOption('-e, --HELP', 'read more information');
905905
```
906906

907+
(Or use `.addHelpOption()` to add an option you construct yourself.)
908+
907909
### .helpCommand()
908910

909911
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)`.

lib/command.js

+55-27
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const process = require('process');
77
const { Argument, humanReadableArgName } = require('./argument.js');
88
const { CommanderError } = require('./error.js');
99
const { Help } = require('./help.js');
10-
const { Option, splitOptionFlags, DualOptions } = require('./option.js');
10+
const { Option, DualOptions } = require('./option.js');
1111
const { suggestSimilar } = require('./suggestSimilar');
1212

1313
class Command extends EventEmitter {
@@ -66,11 +66,8 @@ class Command extends EventEmitter {
6666
};
6767

6868
this._hidden = false;
69-
this._hasHelpOption = true;
70-
this._helpFlags = '-h, --help';
71-
this._helpDescription = 'display help for command';
72-
this._helpShortFlag = '-h';
73-
this._helpLongFlag = '--help';
69+
/** @type {(Option | null | undefined)} */
70+
this._helpOption = undefined; // Lazy created on demand. May be null if help option is disabled.
7471
this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
7572
/** @type {Command} */
7673
this._helpCommand = undefined; // lazy initialised, inherited
@@ -87,11 +84,7 @@ class Command extends EventEmitter {
8784
*/
8885
copyInheritedSettings(sourceCommand) {
8986
this._outputConfiguration = sourceCommand._outputConfiguration;
90-
this._hasHelpOption = sourceCommand._hasHelpOption;
91-
this._helpFlags = sourceCommand._helpFlags;
92-
this._helpDescription = sourceCommand._helpDescription;
93-
this._helpShortFlag = sourceCommand._helpShortFlag;
94-
this._helpLongFlag = sourceCommand._helpLongFlag;
87+
this._helpOption = sourceCommand._helpOption;
9588
this._helpCommand = sourceCommand._helpCommand;
9689
this._helpConfiguration = sourceCommand._helpConfiguration;
9790
this._exitCallback = sourceCommand._exitCallback;
@@ -1189,7 +1182,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
11891182

11901183
// Fallback to parsing the help flag to invoke the help.
11911184
return this._dispatchSubcommand(subcommandName, [], [
1192-
this._helpLongFlag || this._helpShortFlag
1185+
this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help'
11931186
]);
11941187
}
11951188

@@ -2001,7 +1994,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
20011994
return humanReadableArgName(arg);
20021995
});
20031996
return [].concat(
2004-
(this.options.length || this._hasHelpOption ? '[options]' : []),
1997+
(this.options.length || (this._helpOption !== null) ? '[options]' : []),
20051998
(this.commands.length ? '[command]' : []),
20061999
(this.registeredArguments.length ? args : [])
20072000
).join(' ');
@@ -2122,35 +2115,69 @@ Expecting one of '${allowedValues.join("', '")}'`);
21222115
}
21232116
context.write(helpInformation);
21242117

2125-
if (this._helpLongFlag) {
2126-
this.emit(this._helpLongFlag); // deprecated
2118+
if (this._getHelpOption()?.long) {
2119+
this.emit(this._getHelpOption().long); // deprecated
21272120
}
21282121
this.emit('afterHelp', context);
21292122
this._getCommandAndAncestors().forEach(command => command.emit('afterAllHelp', context));
21302123
}
21312124

21322125
/**
2133-
* You can pass in flags and a description to override the help
2134-
* flags and help description for your command. Pass in false to
2135-
* disable the built-in help option.
2126+
* You can pass in flags and a description to customise the built-in help option.
2127+
* Pass in false to disable the built-in help option.
21362128
*
2137-
* @param {(string | boolean)} [flags]
2129+
* @example
2130+
* program.helpOption('-?, --help' 'show help'); // customise
2131+
* program.helpOption(false); // disable
2132+
*
2133+
* @param {(string | boolean)} flags
21382134
* @param {string} [description]
21392135
* @return {Command} `this` command for chaining
21402136
*/
21412137

21422138
helpOption(flags, description) {
2139+
// Support disabling built-in help option.
21432140
if (typeof flags === 'boolean') {
2144-
this._hasHelpOption = flags;
2141+
if (flags) {
2142+
this._helpOption = this._helpOption ?? undefined; // preserve existing option
2143+
} else {
2144+
this._helpOption = null; // disable
2145+
}
21452146
return this;
21462147
}
2147-
this._helpFlags = flags || this._helpFlags;
2148-
this._helpDescription = description || this._helpDescription;
21492148

2150-
const helpFlags = splitOptionFlags(this._helpFlags);
2151-
this._helpShortFlag = helpFlags.shortFlag;
2152-
this._helpLongFlag = helpFlags.longFlag;
2149+
// Customise flags and description.
2150+
flags = flags ?? '-h, --help';
2151+
description = description ?? 'display help for command';
2152+
this._helpOption = this.createOption(flags, description);
2153+
2154+
return this;
2155+
}
21532156

2157+
/**
2158+
* Lazy create help option.
2159+
* Returns null if has been disabled with .helpOption(false).
2160+
*
2161+
* @returns {(Option | null)} the help option
2162+
* @package internal use only
2163+
*/
2164+
_getHelpOption() {
2165+
// Lazy create help option on demand.
2166+
if (this._helpOption === undefined) {
2167+
this.helpOption(undefined, undefined);
2168+
}
2169+
return this._helpOption;
2170+
}
2171+
2172+
/**
2173+
* Supply your own option to use for the built-in help option.
2174+
* This is an alternative to using helpOption() to customise the flags and description etc.
2175+
*
2176+
* @param {Option} option
2177+
* @return {Command} `this` command for chaining
2178+
*/
2179+
addHelpOption(option) {
2180+
this._helpOption = option;
21542181
return this;
21552182
}
21562183

@@ -2212,8 +2239,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
22122239
*/
22132240

22142241
_outputHelpIfRequested(args) {
2215-
const helpOption = this._hasHelpOption && args.find(arg => arg === this._helpLongFlag || arg === this._helpShortFlag);
2216-
if (helpOption) {
2242+
const helpOption = this._getHelpOption();
2243+
const helpRequested = helpOption && args.find(arg => helpOption.is(arg));
2244+
if (helpRequested) {
22172245
this.outputHelp();
22182246
// (Do not have all displayed text available so only passing placeholder.)
22192247
this._exit(0, 'commander.helpDisplayed', '(outputHelp)');

lib/help.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,19 @@ class Help {
6363

6464
visibleOptions(cmd) {
6565
const visibleOptions = cmd.options.filter((option) => !option.hidden);
66-
// Implicit help
67-
const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
68-
const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
69-
if (showShortHelpFlag || showLongHelpFlag) {
70-
let helpOption;
71-
if (!showShortHelpFlag) {
72-
helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
73-
} else if (!showLongHelpFlag) {
74-
helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
75-
} else {
76-
helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
66+
// Built-in help option.
67+
const helpOption = cmd._getHelpOption();
68+
if (helpOption && !helpOption.hidden) {
69+
// Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.
70+
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
71+
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
72+
if (!removeShort && !removeLong) {
73+
visibleOptions.push(helpOption); // no changes needed
74+
} else if (helpOption.long && !removeLong) {
75+
visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
76+
} else if (helpOption.short && !removeShort) {
77+
visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
7778
}
78-
visibleOptions.push(helpOption);
7979
}
8080
if (this.sortOptions) {
8181
visibleOptions.sort(this.compareOptions);

lib/option.js

-1
Original file line numberDiff line numberDiff line change
@@ -324,5 +324,4 @@ function splitOptionFlags(flags) {
324324
}
325325

326326
exports.Option = Option;
327-
exports.splitOptionFlags = splitOptionFlags;
328327
exports.DualOptions = DualOptions;

package-lock.json

+35-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@typescript-eslint/parser": "^6.7.5",
6363
"eslint": "^8.30.0",
6464
"eslint-config-standard": "^17.0.0",
65-
"eslint-config-standard-with-typescript": "^39.1.1",
65+
"eslint-config-standard-with-typescript": "^40.0.0",
6666
"eslint-plugin-import": "^2.26.0",
6767
"eslint-plugin-jest": "^27.1.7",
6868
"eslint-plugin-n": "^16.2.0",

0 commit comments

Comments
 (0)