Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor help option implementation to hold actual Option #2006

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0a2f822
Refactor help option support to use actual Option as store for config…
shadowspawn Sep 2, 2023
1c21b8a
Lazy create help option, getter, addHelpOption() for symmetry with ad…
shadowspawn Sep 3, 2023
33f7150
Create helpOption in copyInherited so shared, and don't create multip…
shadowspawn Oct 7, 2023
48f9fd9
Bump @types/jest from 29.5.6 to 29.5.7 (#2064)
dependabot[bot] Nov 11, 2023
902124d
Bump eslint from 8.52.0 to 8.53.0 (#2061)
dependabot[bot] Nov 11, 2023
c20a71d
Bump @types/node from 20.8.9 to 20.8.10 (#2063)
dependabot[bot] Nov 11, 2023
6b74d26
Bump @typescript-eslint/eslint-plugin from 6.9.0 to 6.9.1 (#2060)
dependabot[bot] Nov 11, 2023
2978456
Bump @typescript-eslint/parser from 6.9.0 to 6.9.1 (#2062)
dependabot[bot] Nov 11, 2023
883d326
Bump @types/jest from 29.5.7 to 29.5.8 (#2076)
dependabot[bot] Nov 14, 2023
f234c25
Bump @types/node from 20.8.10 to 20.9.0 (#2074)
dependabot[bot] Nov 14, 2023
c1b2526
Bump @typescript-eslint/eslint-plugin from 6.9.1 to 6.10.0 (#2072)
dependabot[bot] Nov 14, 2023
47a48c9
Bump @typescript-eslint/parser from 6.9.1 to 6.10.0 (#2073)
dependabot[bot] Nov 14, 2023
8eed8c2
Bump eslint-plugin-n from 16.2.0 to 16.3.1 (#2075)
dependabot[bot] Nov 14, 2023
02ba904
Bump eslint from 8.53.0 to 8.54.0 (#2082)
dependabot[bot] Nov 29, 2023
158c3c7
Bump eslint-config-standard-with-typescript from 39.1.1 to 40.0.0 (#2…
dependabot[bot] Nov 30, 2023
d8a0938
Bump @typescript-eslint/parser from 6.10.0 to 6.13.1 (#2091)
dependabot[bot] Dec 1, 2023
d97b828
Merge branch 'develop' into feature/help-option-as-property
shadowspawn Dec 16, 2023
1d0fe63
Refactor _hasHelpOption, add explicit lazy getter
shadowspawn Dec 16, 2023
5a1864c
Update comment
shadowspawn Dec 16, 2023
878df5a
Rework lazy help option creation, and better handle null help option
shadowspawn Dec 16, 2023
4b4ba8c
Remove debug hack
shadowspawn Dec 16, 2023
ae6d563
Merge remote-tracking branch 'upstream/release/12.x' into feature/hel…
shadowspawn Dec 16, 2023
7dddfd8
Merge remote-tracking branch 'upstream/release/12.x' into feature/hel…
shadowspawn Dec 16, 2023
8f64745
Fill out .addHelpOption coverage
shadowspawn Dec 17, 2023
6b14397
Add internal comment
shadowspawn Dec 17, 2023
361b0ef
Merge remote-tracking branch 'upstream/release/12.x' into feature/hel…
shadowspawn Jan 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,8 @@ program
.helpOption('-e, --HELP', 'read more information');
```

(Or use `.addHelpOption()` to add an option you construct yourself.)

### .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 `.helpCommand(true)` and `.helpCommand(false)`.
Expand Down
82 changes: 55 additions & 27 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const process = require('process');
const { Argument, humanReadableArgName } = require('./argument.js');
const { CommanderError } = require('./error.js');
const { Help } = require('./help.js');
const { Option, splitOptionFlags, DualOptions } = require('./option.js');
const { Option, DualOptions } = require('./option.js');
const { suggestSimilar } = require('./suggestSimilar');

class Command extends EventEmitter {
Expand Down Expand Up @@ -66,11 +66,8 @@ class Command extends EventEmitter {
};

this._hidden = false;
this._hasHelpOption = true;
this._helpFlags = '-h, --help';
this._helpDescription = 'display help for command';
this._helpShortFlag = '-h';
this._helpLongFlag = '--help';
/** @type {(Option | null | undefined)} */
this._helpOption = undefined; // Lazy created on demand. May be null if help option is disabled.
this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
/** @type {Command} */
this._helpCommand = undefined; // lazy initialised, inherited
Expand All @@ -87,11 +84,7 @@ class Command extends EventEmitter {
*/
copyInheritedSettings(sourceCommand) {
this._outputConfiguration = sourceCommand._outputConfiguration;
this._hasHelpOption = sourceCommand._hasHelpOption;
this._helpFlags = sourceCommand._helpFlags;
this._helpDescription = sourceCommand._helpDescription;
this._helpShortFlag = sourceCommand._helpShortFlag;
this._helpLongFlag = sourceCommand._helpLongFlag;
this._helpOption = sourceCommand._helpOption;
this._helpCommand = sourceCommand._helpCommand;
this._helpConfiguration = sourceCommand._helpConfiguration;
this._exitCallback = sourceCommand._exitCallback;
Expand Down Expand Up @@ -1189,7 +1182,7 @@ Expecting one of '${allowedValues.join("', '")}'`);

// Fallback to parsing the help flag to invoke the help.
return this._dispatchSubcommand(subcommandName, [], [
this._helpLongFlag || this._helpShortFlag
this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help'
]);
}

Expand Down Expand Up @@ -2001,7 +1994,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
return humanReadableArgName(arg);
});
return [].concat(
(this.options.length || this._hasHelpOption ? '[options]' : []),
(this.options.length || (this._helpOption !== null) ? '[options]' : []),
(this.commands.length ? '[command]' : []),
(this.registeredArguments.length ? args : [])
).join(' ');
Expand Down Expand Up @@ -2122,35 +2115,69 @@ Expecting one of '${allowedValues.join("', '")}'`);
}
context.write(helpInformation);

if (this._helpLongFlag) {
this.emit(this._helpLongFlag); // deprecated
if (this._getHelpOption()?.long) {
this.emit(this._getHelpOption().long); // deprecated
}
this.emit('afterHelp', context);
this._getCommandAndAncestors().forEach(command => command.emit('afterAllHelp', context));
}

/**
* You can pass in flags and a description to override the help
* flags and help description for your command. Pass in false to
* disable the built-in help option.
* You can pass in flags and a description to customise the built-in help option.
* Pass in false to disable the built-in help option.
*
* @param {(string | boolean)} [flags]
* @example
* program.helpOption('-?, --help' 'show help'); // customise
* program.helpOption(false); // disable
*
* @param {(string | boolean)} flags
* @param {string} [description]
* @return {Command} `this` command for chaining
*/

helpOption(flags, description) {
// Support disabling built-in help option.
if (typeof flags === 'boolean') {
this._hasHelpOption = flags;
if (flags) {
this._helpOption = this._helpOption ?? undefined; // preserve existing option
} else {
this._helpOption = null; // disable
}
return this;
}
this._helpFlags = flags || this._helpFlags;
this._helpDescription = description || this._helpDescription;

const helpFlags = splitOptionFlags(this._helpFlags);
this._helpShortFlag = helpFlags.shortFlag;
this._helpLongFlag = helpFlags.longFlag;
// Customise flags and description.
flags = flags ?? '-h, --help';
description = description ?? 'display help for command';
this._helpOption = this.createOption(flags, description);

return this;
}

/**
* Lazy create help option.
* Returns null if has been disabled with .helpOption(false).
*
* @returns {(Option | null)} the help option
* @package internal use only
*/
_getHelpOption() {
// Lazy create help option on demand.
if (this._helpOption === undefined) {
this.helpOption(undefined, undefined);
}
return this._helpOption;
}

/**
* Supply your own option to use for the built-in help option.
* This is an alternative to using helpOption() to customise the flags and description etc.
*
* @param {Option} option
* @return {Command} `this` command for chaining
*/
addHelpOption(option) {
this._helpOption = option;
return this;
}

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

_outputHelpIfRequested(args) {
const helpOption = this._hasHelpOption && args.find(arg => arg === this._helpLongFlag || arg === this._helpShortFlag);
if (helpOption) {
const helpOption = this._getHelpOption();
const helpRequested = helpOption && args.find(arg => helpOption.is(arg));
if (helpRequested) {
this.outputHelp();
// (Do not have all displayed text available so only passing placeholder.)
this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
Expand Down
24 changes: 12 additions & 12 deletions lib/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ class Help {

visibleOptions(cmd) {
const visibleOptions = cmd.options.filter((option) => !option.hidden);
// Implicit help
const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
if (showShortHelpFlag || showLongHelpFlag) {
let helpOption;
if (!showShortHelpFlag) {
helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
} else if (!showLongHelpFlag) {
helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
} else {
helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
// Built-in help option.
const helpOption = cmd._getHelpOption();
if (helpOption && !helpOption.hidden) {
// Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
if (!removeShort && !removeLong) {
visibleOptions.push(helpOption); // no changes needed
} else if (helpOption.long && !removeLong) {
visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
} else if (helpOption.short && !removeShort) {
visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
}
visibleOptions.push(helpOption);
}
if (this.sortOptions) {
visibleOptions.sort(this.compareOptions);
Expand Down
1 change: 0 additions & 1 deletion lib/option.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,5 +324,4 @@ function splitOptionFlags(flags) {
}

exports.Option = Option;
exports.splitOptionFlags = splitOptionFlags;
exports.DualOptions = DualOptions;
70 changes: 35 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@typescript-eslint/parser": "^6.7.5",
"eslint": "^8.30.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^39.1.1",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-n": "^16.2.0",
Expand Down