From 109adc47f5f6c29287c99cfcd5d0d3aae08ec813 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 17:00:44 +1200 Subject: [PATCH 1/5] Allow disabling the built-in help option --- Readme.md | 2 +- index.js | 20 ++++++++---- tests/command.helpOption.test.js | 53 +++++++++++++++++++++++++++++++- typings/commander-tests.ts | 1 + typings/index.d.ts | 5 +-- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/Readme.md b/Readme.md index 7f9e110e0..a1d389b82 100644 --- a/Readme.md +++ b/Readme.md @@ -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 diff --git a/index.js b/index.js index f02281101..754dc4df2 100644 --- a/index.js +++ b/index.js @@ -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'; @@ -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; @@ -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); }; @@ -1490,8 +1493,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) { @@ -1615,15 +1618,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; @@ -1756,7 +1764,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.) diff --git a/tests/command.helpOption.test.js b/tests/command.helpOption.test.js index 8cda1f20b..e7cec7984 100644 --- a/tests/command.helpOption.test.js +++ b/tests/command.helpOption.test.js @@ -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', () => { @@ -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'."); + }); }); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 57392c667..7cceaad83 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -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', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index c8061ffb3..03bf5a1a3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -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. From 0b1249a1f35492142b0f064895d2d3d52e8010f1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 17:28:23 +1200 Subject: [PATCH 2/5] Suppress Options section of help if no options --- index.js | 13 ++++++++----- tests/command.help.test.js | 9 +++++++++ tests/command.helpOption.test.js | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 754dc4df2..e030a7d70 100644 --- a/index.js +++ b/index.js @@ -1580,11 +1580,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) diff --git a/tests/command.help.test.js b/tests/command.help.test.js index d6e0e899a..c2ed34b3c 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -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'); +}); diff --git a/tests/command.helpOption.test.js b/tests/command.helpOption.test.js index e7cec7984..35cff54ee 100644 --- a/tests/command.helpOption.test.js +++ b/tests/command.helpOption.test.js @@ -73,7 +73,7 @@ describe('helpOption', () => { program .helpOption(false); const helpInformation = program.helpInformation(); - expect(helpInformation).not.toMatch(/--help/); + expect(helpInformation).not.toMatch('--help'); }); test('when helpOption(false) then --help is an unknown option', () => { From ef56b7a4356b99b84cc35d7bf60562eb8db87dce Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 17:41:14 +1200 Subject: [PATCH 3/5] Fix suppressed options --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index e030a7d70..1ae54f206 100644 --- a/index.js +++ b/index.js @@ -1580,7 +1580,7 @@ Read more on https://git.io/JJc0W`); const commandHelp = this.commandHelp(); if (commandHelp) cmds = [commandHelp]; - let options = ''; + let options = []; if (this._hasHelpOption || this.options.length > 0) { options = [ 'Options:', From 717d20ad18605345681cea0950d1c5ceb186083b Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 17:59:19 +1200 Subject: [PATCH 4/5] Leave [options] out of usage if no options --- index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 1ae54f206..4b1a7eb9b 100644 --- a/index.js +++ b/index.js @@ -1348,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; From 9547976dba67826232f917861cacd1f769ec5aec Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 18:27:01 +1200 Subject: [PATCH 5/5] Expand usage tests --- tests/command.usage.test.js | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/command.usage.test.js b/tests/command.usage.test.js index 6140ec93b..748404dad 100644 --- a/tests/command.usage.test.js +++ b/tests/command.usage.test.js @@ -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(''); + + expect(program.usage()).toMatch(''); +}); + +test('when options and command and arguments then all three included in usage', () => { + const program = new commander.Command(); + + program + .arguments('') + .option('--alpha') + .command('beta'); + + expect(program.usage()).toEqual('[options] [command] '); +});