Skip to content

Commit

Permalink
Allow disabling the built-in help option (#1325)
Browse files Browse the repository at this point in the history
* Allow disabling the built-in help option

* Suppress Options section of help if no options

* Fix suppressed options

* Leave [options] out of usage if no options

* Expand usage tests
  • Loading branch information
shadowspawn committed Aug 11, 2020
1 parent a71d592 commit 6b2e42c
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 18 deletions.
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

0 comments on commit 6b2e42c

Please sign in to comment.