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

Allow disabling the built-in help option #1325

Merged
merged 5 commits into from Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion Readme.md
Expand Up @@ -559,7 +559,7 @@ from `--help` listeners.)

### .helpOption(flags, description)

Override the default help flags and description.
Override the default help flags and description. Pass false to disable the built-in help option.

```js
program
Expand Down
41 changes: 27 additions & 14 deletions index.js
Expand Up @@ -129,6 +129,7 @@ class Command extends EventEmitter {
this._aliases = [];

this._hidden = false;
this._hasHelpOption = true;
this._helpFlags = '-h, --help';
this._helpDescription = 'display help for command';
this._helpShortFlag = '-h';
Expand Down Expand Up @@ -184,6 +185,7 @@ class Command extends EventEmitter {
if (opts.isDefault) this._defaultCommandName = cmd._name;

cmd._hidden = !!(opts.noHelp || opts.hidden);
cmd._hasHelpOption = this._hasHelpOption;
cmd._helpFlags = this._helpFlags;
cmd._helpDescription = this._helpDescription;
cmd._helpShortFlag = this._helpShortFlag;
Expand Down Expand Up @@ -1236,7 +1238,8 @@ Read more on https://git.io/JJc0W`);
partCommands.unshift(parentCmd.name());
}
const fullCommand = partCommands.join(' ');
const message = `error: unknown command '${this.args[0]}'. See '${fullCommand} ${this._helpLongFlag}'.`;
const message = `error: unknown command '${this.args[0]}'.` +
(this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
console.error(message);
this._exit(1, 'commander.unknownCommand', message);
};
Expand Down Expand Up @@ -1345,9 +1348,11 @@ Read more on https://git.io/JJc0W`);
const args = this._args.map((arg) => {
return humanReadableArgName(arg);
});
return '[options]' +
(this.commands.length ? ' [command]' : '') +
(this._args.length ? ' ' + args.join(' ') : '');
return [].concat(
(this.options.length || this._hasHelpOption ? '[options]' : []),
(this.commands.length ? '[command]' : []),
(this._args.length ? args : [])
).join(' ');
}

this._usage = str;
Expand Down Expand Up @@ -1490,8 +1495,8 @@ Read more on https://git.io/JJc0W`);
});

// Implicit help
const showShortHelpFlag = this._helpShortFlag && !this._findOption(this._helpShortFlag);
const showLongHelpFlag = !this._findOption(this._helpLongFlag);
const showShortHelpFlag = this._hasHelpOption && this._helpShortFlag && !this._findOption(this._helpShortFlag);
const showLongHelpFlag = this._hasHelpOption && !this._findOption(this._helpLongFlag);
if (showShortHelpFlag || showLongHelpFlag) {
let helpFlags = this._helpFlags;
if (!showShortHelpFlag) {
Expand Down Expand Up @@ -1577,11 +1582,14 @@ Read more on https://git.io/JJc0W`);
const commandHelp = this.commandHelp();
if (commandHelp) cmds = [commandHelp];

const options = [
'Options:',
'' + this.optionHelp().replace(/^/gm, ' '),
''
];
let options = [];
if (this._hasHelpOption || this.options.length > 0) {
options = [
'Options:',
'' + this.optionHelp().replace(/^/gm, ' '),
''
];
}

return usage
.concat(desc)
Expand Down Expand Up @@ -1615,15 +1623,20 @@ Read more on https://git.io/JJc0W`);

/**
* You can pass in flags and a description to override the help
* flags and help description for your command.
* flags and help description for your command. Pass in false to
* disable the built-in help option.
*
* @param {string} [flags]
* @param {string | boolean} [flags]
* @param {string} [description]
* @return {Command} `this` command for chaining
* @api public
*/

helpOption(flags, description) {
if (typeof flags === 'boolean') {
this._hasHelpOption = flags;
return this;
}
this._helpFlags = flags || this._helpFlags;
this._helpDescription = description || this._helpDescription;

Expand Down Expand Up @@ -1756,7 +1769,7 @@ function optionalWrap(str, width, indent) {
*/

function outputHelpIfRequested(cmd, args) {
const helpOption = args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
if (helpOption) {
cmd.outputHelp();
// (Do not have all displayed text available so only passing placeholder.)
Expand Down
9 changes: 9 additions & 0 deletions tests/command.help.test.js
Expand Up @@ -130,3 +130,12 @@ test('when both help flags masked then not displayed in helpInformation', () =>
const helpInformation = program.helpInformation();
expect(helpInformation).not.toMatch('display help');
});

test('when no options then Options not includes in helpInformation', () => {
const program = new commander.Command();
// No custom options, no version option, no help option
program
.helpOption(false);
const helpInformation = program.helpInformation();
expect(helpInformation).not.toMatch('Options');
});
53 changes: 52 additions & 1 deletion tests/command.helpOption.test.js
Expand Up @@ -2,18 +2,22 @@ const commander = require('../');

describe('helpOption', () => {
let writeSpy;
let consoleErrorSpy;

beforeAll(() => {
// Optional. Suppress normal output to keep test output clean.
// Optional. Suppress expected output to keep test output clean.
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
});

afterEach(() => {
writeSpy.mockClear();
consoleErrorSpy.mockClear();
});

afterAll(() => {
writeSpy.mockRestore();
consoleErrorSpy.mockRestore();
});

test('when helpOption has custom flags then custom short flag invokes help', () => {
Expand Down Expand Up @@ -63,4 +67,51 @@ describe('helpOption', () => {
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch(/-C,--custom-help +custom help output/);
});

test('when helpOption(false) then helpInformation does not include --help', () => {
const program = new commander.Command();
program
.helpOption(false);
const helpInformation = program.helpInformation();
expect(helpInformation).not.toMatch('--help');
});

test('when helpOption(false) then --help is an unknown option', () => {
const program = new commander.Command();
program
.exitOverride()
.helpOption(false);
let caughtErr;
try {
program.parse(['--help'], { from: 'user' });
} catch (err) {
caughtErr = err;
}
expect(caughtErr.code).toBe('commander.unknownOption');
});

test('when helpOption(false) then -h is an unknown option', () => {
const program = new commander.Command();
program
.exitOverride()
.helpOption(false);
let caughtErr;
try {
program.parse(['-h'], { from: 'user' });
} catch (err) {
caughtErr = err;
}
expect(caughtErr.code).toBe('commander.unknownOption');
});

test('when helpOption(false) then unknown command error does not suggest --help', () => {
const program = new commander.Command();
program
.exitOverride()
.helpOption(false)
.command('foo');
expect(() => {
program.parse(['UNKNOWN'], { from: 'user' });
}).toThrow("error: unknown command 'UNKNOWN'.");
});
});
53 changes: 53 additions & 0 deletions tests/command.usage.test.js
Expand Up @@ -44,3 +44,56 @@ test('when custom usage and check subcommand help then starts with custom usage

expect(helpInformation).toMatch(new RegExp(`^Usage: test info ${myUsage}`));
});

test('when has option then [options] included in usage', () => {
const program = new commander.Command();

program
.option('--foo');

expect(program.usage()).toMatch('[options]');
});

test('when no options then [options] not included in usage', () => {
const program = new commander.Command();

program
.helpOption(false);

expect(program.usage()).not.toMatch('[options]');
});

test('when has command then [command] included in usage', () => {
const program = new commander.Command();

program
.command('foo');

expect(program.usage()).toMatch('[command]');
});

test('when no commands then [command] not included in usage', () => {
const program = new commander.Command();

expect(program.usage()).not.toMatch('[command]');
});

test('when arguments then arguments included in usage', () => {
const program = new commander.Command();

program
.arguments('<file>');

expect(program.usage()).toMatch('<file>');
});

test('when options and command and arguments then all three included in usage', () => {
const program = new commander.Command();

program
.arguments('<file>')
.option('--alpha')
.command('beta');

expect(program.usage()).toEqual('[options] [command] <file>');
});
1 change: 1 addition & 0 deletions typings/commander-tests.ts
Expand Up @@ -196,6 +196,7 @@ const helpInformnationValue: string = program.helpInformation();
const helpOptionThis1: commander.Command = program.helpOption('-h,--help');
const helpOptionThis2: commander.Command = program.helpOption('-h,--help', 'custom description');
const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom description');
const helpOptionThis4: commander.Command = program.helpOption(false);

// on
const onThis: commander.Command = program.on('--help', () => {
Expand Down
5 changes: 3 additions & 2 deletions typings/index.d.ts
Expand Up @@ -335,9 +335,10 @@ declare namespace commander {

/**
* You can pass in flags and a description to override the help
* flags and help description for your command.
* flags and help description for your command. Pass in false
* to disable the built-in help option.
*/
helpOption(flags?: string, description?: string): this;
helpOption(flags?: string | boolean, description?: string): this;

/**
* Output help information and exit.
Expand Down