From c9cc1cc55464024095a908afa746ceefd88b4c47 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 16 May 2019 14:32:56 -0700 Subject: [PATCH 1/6] Add a way to override help and version help message Close #47. Add override for the help message by calling program.help(message). Add override for the version message by calling program.version(version, flag, message); --- Readme.md | 15 +++++++++++++ index.js | 15 ++++++++++--- test/test.command.helpInformation.custom.js | 21 ++++++++++++++++++ ...t.options.version.customHelpDescription.js | 22 +++++++++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 test/test.command.helpInformation.custom.js create mode 100644 test/test.options.version.customHelpDescription.js diff --git a/Readme.md b/Readme.md index 115ff995f..c81a4bf56 100644 --- a/Readme.md +++ b/Readme.md @@ -224,6 +224,12 @@ You may specify custom flags by passing an additional parameter to the `version` program.version('0.0.1', '-v, --version'); ``` +You can also override the help description for the version command. + +```js +program.version('0.0.1', '-v, --version', 'output the current version'); +``` + ## Command-specific options @@ -433,6 +439,15 @@ function make_red(txt) { } ``` +## .help(str) + + Override the default help description. + +```js +program + .help('read more information'); +``` + ## .help(cb) Output help information and exit immediately. diff --git a/index.js b/index.js index da594e9cd..390e98e03 100644 --- a/index.js +++ b/index.js @@ -852,11 +852,12 @@ Command.prototype.variadicArgNotLast = function(name) { * @api public */ -Command.prototype.version = function(str, flags) { +Command.prototype.version = function(str, flags, description) { if (arguments.length === 0) return this._version; this._version = str; flags = flags || '-V, --version'; - var versionOption = new Option(flags, 'output the version number'); + description = description || 'output the version number'; + var versionOption = new Option(flags, description); this._versionOptionName = versionOption.long.substr(2) || 'version'; this.options.push(versionOption); this.on('option:' + this._versionOptionName, function() { @@ -1043,12 +1044,13 @@ Command.prototype.padWidth = function() { Command.prototype.optionHelp = function() { var width = this.padWidth(); + var description = this._helpDescription || 'output usage information'; // Append the help information return this.options.map(function(option) { return pad(option.flags, width) + ' ' + option.description + ((option.bool && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); - }).concat([pad('-h, --help', width) + ' ' + 'output usage information']) + }).concat([pad('-h, --help', width) + ' ' + description]) .join('\n'); }; @@ -1151,10 +1153,17 @@ Command.prototype.outputHelp = function(cb) { /** * Output help information and exit. * + * @param {Function|String} cb + * @return {Command?} * @api public */ Command.prototype.help = function(cb) { + if (typeof cb === 'string') { + this._helpDescription = cb; + return this; + } + this.outputHelp(cb); process.exit(); }; diff --git a/test/test.command.helpInformation.custom.js b/test/test.command.helpInformation.custom.js new file mode 100644 index 000000000..2c148d1eb --- /dev/null +++ b/test/test.command.helpInformation.custom.js @@ -0,0 +1,21 @@ +var program = require('../') + , sinon = require('sinon').sandbox.create() + , should = require('should'); + +program.help('custom help output'); +program.command('somecommand'); +program.command('anothercommand [options]'); + +var expectedHelpInformation = [ + 'Usage: [options] [command]', + '', + 'Options:', + ' -h, --help custom help output', + '', + 'Commands:', + ' somecommand', + ' anothercommand [options]', + '' +].join('\n'); + +program.helpInformation().should.equal(expectedHelpInformation); diff --git a/test/test.options.version.customHelpDescription.js b/test/test.options.version.customHelpDescription.js new file mode 100644 index 000000000..7471e7310 --- /dev/null +++ b/test/test.options.version.customHelpDescription.js @@ -0,0 +1,22 @@ +var program = require('../') + , sinon = require('sinon').sandbox.create() + , should = require('should'); + +program.version('1.0.0', undefined, 'custom version output'); +program.command('somecommand'); +program.command('anothercommand [options]'); + +var expectedHelpInformation = [ + 'Usage: [options] [command]', + '', + 'Options:', + ' -V, --version custom version output', + ' -h, --help output usage information', + '', + 'Commands:', + ' somecommand', + ' anothercommand [options]', + '' +].join('\n'); + +program.helpInformation().should.equal(expectedHelpInformation); From c3984bb054cb51435b31b4d0d61fa6082728a91e Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 May 2019 15:59:11 -0700 Subject: [PATCH 2/6] Add override for help flags, add examples and typings --- Readme.md | 6 ++-- examples/custom-help-description | 13 +++++++ examples/custom-version | 13 +++++++ index.js | 39 ++++++++++++++------- test/test.command.helpInformation.custom.js | 29 +++++++++++++-- typings/index.d.ts | 9 +++-- 6 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 examples/custom-help-description create mode 100644 examples/custom-version diff --git a/Readme.md b/Readme.md index c81a4bf56..1a4c36fc3 100644 --- a/Readme.md +++ b/Readme.md @@ -439,13 +439,13 @@ function make_red(txt) { } ``` -## .help(str) +## .help(flags, description) - Override the default help description. + Override the default help flags and description. ```js program - .help('read more information'); + .help('-e, --HELP', 'read more information'); ``` ## .help(cb) diff --git a/examples/custom-help-description b/examples/custom-help-description new file mode 100644 index 000000000..d4a2bc2db --- /dev/null +++ b/examples/custom-help-description @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program + .help('-c, --HELP', 'custom help message') + .option('-s, --sessions', 'add session support') + .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade') + .parse(process.argv); diff --git a/examples/custom-version b/examples/custom-version new file mode 100644 index 000000000..11043a06f --- /dev/null +++ b/examples/custom-version @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program + .version('0.0.1', '-v, --VERSION', 'new version message') + .option('-s, --sessions', 'add session support') + .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade') + .parse(process.argv); diff --git a/index.js b/index.js index 390e98e03..4db60dd22 100644 --- a/index.js +++ b/index.js @@ -103,6 +103,11 @@ function Command(name) { this._allowUnknownOption = false; this._args = []; this._name = name || ''; + + this._helpFlags = '-h, --help'; + this._helpDescription = 'output usage information'; + this._helpShortFlag = '-h'; + this._helpLongFlag = '--help'; } /** @@ -466,7 +471,7 @@ Command.prototype.parse = function(argv) { // github-style sub-commands with no sub-command if (this.executables && argv.length < 3 && !this.defaultExecutable) { // this user needs help - argv.push('--help'); + argv.push(this._helpLongFlag); } // process argv @@ -519,7 +524,7 @@ Command.prototype.executeSubCommand = function(argv, args, unknown) { // --help if (args[0] === 'help') { args[0] = args[1]; - args[1] = '--help'; + args[1] = this._helpLongFlag; } // executable @@ -848,6 +853,7 @@ Command.prototype.variadicArgNotLast = function(name) { * * @param {String} str * @param {String} [flags] + * @param {String} [description] * @return {Command} for chaining * @api public */ @@ -991,8 +997,9 @@ Command.prototype.largestCommandLength = function() { Command.prototype.largestOptionLength = function() { var options = [].slice.call(this.options); options.push({ - flags: '-h, --help' + flags: this._helpFlags, }); + return options.reduce(function(max, option) { return Math.max(max, option.flags.length); }, 0); @@ -1044,13 +1051,12 @@ Command.prototype.padWidth = function() { Command.prototype.optionHelp = function() { var width = this.padWidth(); - var description = this._helpDescription || 'output usage information'; // Append the help information return this.options.map(function(option) { return pad(option.flags, width) + ' ' + option.description + ((option.bool && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); - }).concat([pad('-h, --help', width) + ' ' + description]) + }).concat([pad(this._helpFlags, width) + ' ' + this._helpDescription]) .join('\n'); }; @@ -1147,7 +1153,7 @@ Command.prototype.outputHelp = function(cb) { throw new Error('outputHelp callback must return a string or a Buffer'); } process.stdout.write(cbOutput); - this.emit('--help'); + this.emit(this._helpLongFlag); }; /** @@ -1158,14 +1164,20 @@ Command.prototype.outputHelp = function(cb) { * @api public */ -Command.prototype.help = function(cb) { - if (typeof cb === 'string') { - this._helpDescription = cb; - return this; +Command.prototype.help = function(flagsOrCb, description) { + if (typeof cb === 'function' || arguments.length === 0) { + this.outputHelp(flagsOrCb); + process.exit(); } - this.outputHelp(cb); - process.exit(); + this._helpFlags = flagsOrCb || this._helpFlags; + this._helpDescription = description || this._helpDescription; + + var flags = this._helpFlags.split(/[ ,|]+/); + if (flags.length > 1 && !/^[[<]/.test(flags[1])) this._helpShortFlag = flags.shift(); + this._helpLongFlag = flags.shift(); + + return this; }; /** @@ -1206,8 +1218,9 @@ function pad(str, width) { function outputHelpIfNecessary(cmd, options) { options = options || []; + for (var i = 0; i < options.length; i++) { - if (options[i] === '--help' || options[i] === '-h') { + if (options[i] === cmd._helpLongFlag || options[i] === cmd._helpShortFlag) { cmd.outputHelp(); process.exit(0); } diff --git a/test/test.command.helpInformation.custom.js b/test/test.command.helpInformation.custom.js index 2c148d1eb..878d60aff 100644 --- a/test/test.command.helpInformation.custom.js +++ b/test/test.command.helpInformation.custom.js @@ -2,7 +2,10 @@ var program = require('../') , sinon = require('sinon').sandbox.create() , should = require('should'); -program.help('custom help output'); +sinon.stub(process, 'exit'); +sinon.stub(process.stdout, 'write'); + +program.help('-c, --HELP', 'custom help output'); program.command('somecommand'); program.command('anothercommand [options]'); @@ -10,7 +13,7 @@ var expectedHelpInformation = [ 'Usage: [options] [command]', '', 'Options:', - ' -h, --help custom help output', + ' -c, --HELP custom help output', '', 'Commands:', ' somecommand', @@ -19,3 +22,25 @@ var expectedHelpInformation = [ ].join('\n'); program.helpInformation().should.equal(expectedHelpInformation); + +// Test arguments +var expectedCommandHelpInformation = [ + 'Usage: test [options] [command]', + '', + 'Options:', + ' -c, --HELP custom help output', + '', + 'Commands:', + ' somecommand', + ' anothercommand [options]', + '' +].join('\n'); + +program.parse(['node', 'test', '--HELP']); + +process.stdout.write.called.should.equal(true); + +var output = process.stdout.write.args[0]; +output[0].should.equal(expectedCommandHelpInformation); + +sinon.restore(); diff --git a/typings/index.d.ts b/typings/index.d.ts index bcda2771e..2e0fb5d9c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -45,7 +45,7 @@ declare namespace local { * @param {string} [flags] * @returns {Command} for chaining */ - version(str: string, flags?: string): Command; + version(str: string, flags?: string, description?: string): Command; /** * Add command `name`. @@ -274,13 +274,16 @@ declare namespace local { /** Output help information and exit. * - * @param {(str: string) => string} [cb] + * @param {string | (str: string) => string} [flagsOrCb] + * @param {string} [description] */ - help(cb?: (str: string) => string): never; + help(flagsOrCb?: string | callback, description?: string): never; } } +type callback = (str: string) => string; + declare namespace commander { type Command = local.Command From b2e397c2dc19ad7a95b88015eaf549b49dec6efc Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 May 2019 16:02:26 -0700 Subject: [PATCH 3/6] Remove comma dangle --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 4db60dd22..8115866e6 100644 --- a/index.js +++ b/index.js @@ -997,7 +997,7 @@ Command.prototype.largestCommandLength = function() { Command.prototype.largestOptionLength = function() { var options = [].slice.call(this.options); options.push({ - flags: this._helpFlags, + flags: this._helpFlags }); return options.reduce(function(max, option) { From a7eb49aa1c9a83ff154f0fff7adaeabd9321df3b Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 17 May 2019 16:05:51 -0700 Subject: [PATCH 4/6] Add missing JSDoc for help --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 8115866e6..2b941e404 100644 --- a/index.js +++ b/index.js @@ -1160,6 +1160,7 @@ Command.prototype.outputHelp = function(cb) { * Output help information and exit. * * @param {Function|String} cb + * @param {String} description * @return {Command?} * @api public */ From f204dfdaf1dfa0579d16fe21ef423bc499641853 Mon Sep 17 00:00:00 2001 From: Ivan Montiel Date: Thu, 6 Jun 2019 10:14:15 -0700 Subject: [PATCH 5/6] Fix sub-command help flags, add documentation on help flags Sub-commands now inherit the help flags of their parent. These can be overriden by calling ".help" on the sub-commands. Added a test to cover these cases. Updated the example to also demonstrate this feature. Improved documentation on help and outputHelp functions for both JS and TypeScript. --- examples/custom-help-description | 12 +++- index.js | 23 +++++-- ...test.command.helpSubCommand.customFlags.js | 65 +++++++++++++++++++ typings/index.d.ts | 14 +++- 4 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 test/test.command.helpSubCommand.customFlags.js diff --git a/examples/custom-help-description b/examples/custom-help-description index d4a2bc2db..e136f50b2 100644 --- a/examples/custom-help-description +++ b/examples/custom-help-description @@ -9,5 +9,13 @@ var program = require('../'); program .help('-c, --HELP', 'custom help message') .option('-s, --sessions', 'add session support') - .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade') - .parse(process.argv); + .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade'); + +program + .command('child') + .option('--gender', 'specific gender of child') + .action((cmd) => { + console.log('Childsubcommand...'); + }); + +program.parse(process.argv); diff --git a/index.js b/index.js index 2b941e404..f3a0cfb26 100644 --- a/index.js +++ b/index.js @@ -187,6 +187,10 @@ Command.prototype.command = function(name, desc, opts) { if (opts.isDefault) this.defaultExecutable = cmd._name; } cmd._noHelp = !!opts.noHelp; + cmd._helpFlags = this._helpFlags; + cmd._helpDescription = this._helpDescription; + cmd._helpShortFlag = this._helpShortFlag; + cmd._helpLongFlag = this._helpLongFlag; this.commands.push(cmd); cmd.parseExpectedArgs(args); cmd.parent = this; @@ -1137,7 +1141,10 @@ Command.prototype.helpInformation = function() { }; /** - * Output help information for this command + * Output help information for this command. + * + * When listener(s) are available for the helpLongFlag + * those callbacks are invoked. * * @api public */ @@ -1157,10 +1164,14 @@ Command.prototype.outputHelp = function(cb) { }; /** - * Output help information and exit. + * When called with no arguments, or with a callback, this will + * output help information and exit. * - * @param {Function|String} cb - * @param {String} description + * You can pass in flags and a description to override the help + * flags and help description for your command. + * + * @param {Function|String} [flagsOrCb] + * @param {String} [description] * @return {Command?} * @api public */ @@ -1175,7 +1186,9 @@ Command.prototype.help = function(flagsOrCb, description) { this._helpDescription = description || this._helpDescription; var flags = this._helpFlags.split(/[ ,|]+/); - if (flags.length > 1 && !/^[[<]/.test(flags[1])) this._helpShortFlag = flags.shift(); + + if (flags.length > 1) this._helpShortFlag = flags.shift(); + this._helpLongFlag = flags.shift(); return this; diff --git a/test/test.command.helpSubCommand.customFlags.js b/test/test.command.helpSubCommand.customFlags.js new file mode 100644 index 000000000..a617cc674 --- /dev/null +++ b/test/test.command.helpSubCommand.customFlags.js @@ -0,0 +1,65 @@ +var program = require('../'), + sinon = require('sinon').sandbox.create(), + should = require('should'); + +sinon.stub(process, 'exit'); +sinon.stub(process.stdout, 'write'); + +// Test that subcommands inherit the help flags +// but can also override help flags +program + .help('-i, --ihelp', 'foo foo'); + +program + .command('child') + .option('--gender', 'specific gender of child') + .action((cmd) => { + console.log('Childsubcommand...'); + }); + +program + .command('family') + .help('-h, --help') + .action((cmd) => { + console.log('Familysubcommand...'); + }); + +// Test arguments +var expectedCommandHelpInformation = [ + 'Usage: child [options]', + '', + 'Options:', + ' --gender specific gender of child', + ' -i, --ihelp foo foo', + '' +].join('\n'); + +program.parse(['node', 'test', 'child', '-i']); + +process.stdout.write.called.should.equal(true); + +var output = process.stdout.write.args[0]; +output[0].should.equal(expectedCommandHelpInformation); + +// Test other command +sinon.restore(); + +sinon.stub(process, 'exit'); +sinon.stub(process.stdout, 'write'); + +var expectedFamilyCommandHelpInformation = [ + 'Usage: family [options]', + '', + 'Options:', + ' -h, --help foo foo', + '' +].join('\n'); + +program.parse(['node', 'test', 'family', '-h']); + +process.stdout.write.called.should.equal(true); + +var output2 = process.stdout.write.args[0]; +output2[0].should.equal(expectedFamilyCommandHelpInformation); + +sinon.restore(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 2e0fb5d9c..c430f2c54 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -268,21 +268,29 @@ declare namespace local { /** * Output help information for this command. * + * When listener(s) are available for the helpLongFlag + * those callbacks are invoked. + * * @param {(str: string) => string} [cb] */ outputHelp(cb?: (str: string) => string): void; - /** Output help information and exit. + /** + * When called with no arguments, or with a callback, this will + * output help information and exit. + * + * You can pass in flags and a description to override the help + * flags and help description for your command. * * @param {string | (str: string) => string} [flagsOrCb] * @param {string} [description] */ - help(flagsOrCb?: string | callback, description?: string): never; + help(flagsOrCb?: string | helpCallback, description?: string): never; } } -type callback = (str: string) => string; +type helpCallback = (str: string) => string; declare namespace commander { From 14f54c507948d936a46750a06e1ddd1f44984943 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 25 Jun 2019 10:36:31 -0700 Subject: [PATCH 6/6] Keep help as-is, add helpOption method --- Readme.md | 4 +-- examples/custom-help-description | 2 +- index.js | 36 ++++++++++--------- test/test.command.helpInformation.custom.js | 2 +- ...test.command.helpSubCommand.customFlags.js | 4 +-- typings/index.d.ts | 12 +++++-- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Readme.md b/Readme.md index 1a4c36fc3..f89088a75 100644 --- a/Readme.md +++ b/Readme.md @@ -439,13 +439,13 @@ function make_red(txt) { } ``` -## .help(flags, description) +## .helpOption(flags, description) Override the default help flags and description. ```js program - .help('-e, --HELP', 'read more information'); + .helpOption('-e, --HELP', 'read more information'); ``` ## .help(cb) diff --git a/examples/custom-help-description b/examples/custom-help-description index e136f50b2..9892a912a 100644 --- a/examples/custom-help-description +++ b/examples/custom-help-description @@ -7,7 +7,7 @@ var program = require('../'); program - .help('-c, --HELP', 'custom help message') + .helpOption('-c, --HELP', 'custom help message') .option('-s, --sessions', 'add session support') .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade'); diff --git a/index.js b/index.js index f3a0cfb26..58e6bd113 100644 --- a/index.js +++ b/index.js @@ -1164,36 +1164,40 @@ Command.prototype.outputHelp = function(cb) { }; /** - * When called with no arguments, or with a callback, this will - * output help information and exit. - * * You can pass in flags and a description to override the help * flags and help description for your command. * - * @param {Function|String} [flagsOrCb] - * @param {String} [description] - * @return {Command?} + * @param {String} flags + * @param {String} description + * @return {Command} * @api public */ -Command.prototype.help = function(flagsOrCb, description) { - if (typeof cb === 'function' || arguments.length === 0) { - this.outputHelp(flagsOrCb); - process.exit(); - } - - this._helpFlags = flagsOrCb || this._helpFlags; +Command.prototype.helpOption = function(flags, description) { + this._helpFlags = flags || this._helpFlags; this._helpDescription = description || this._helpDescription; - var flags = this._helpFlags.split(/[ ,|]+/); + var splitFlags = this._helpFlags.split(/[ ,|]+/); - if (flags.length > 1) this._helpShortFlag = flags.shift(); + if (splitFlags.length > 1) this._helpShortFlag = splitFlags.shift(); - this._helpLongFlag = flags.shift(); + this._helpLongFlag = splitFlags.shift(); return this; }; +/** + * Output help information and exit. + * + * @param {Function} cb + * @api public + */ + +Command.prototype.help = function(cb) { + this.outputHelp(cb); + process.exit(); +}; + /** * Camel-case the given `flag` * diff --git a/test/test.command.helpInformation.custom.js b/test/test.command.helpInformation.custom.js index 878d60aff..50ad9ab91 100644 --- a/test/test.command.helpInformation.custom.js +++ b/test/test.command.helpInformation.custom.js @@ -5,7 +5,7 @@ var program = require('../') sinon.stub(process, 'exit'); sinon.stub(process.stdout, 'write'); -program.help('-c, --HELP', 'custom help output'); +program.helpOption('-c, --HELP', 'custom help output'); program.command('somecommand'); program.command('anothercommand [options]'); diff --git a/test/test.command.helpSubCommand.customFlags.js b/test/test.command.helpSubCommand.customFlags.js index a617cc674..c2d7575d0 100644 --- a/test/test.command.helpSubCommand.customFlags.js +++ b/test/test.command.helpSubCommand.customFlags.js @@ -8,7 +8,7 @@ sinon.stub(process.stdout, 'write'); // Test that subcommands inherit the help flags // but can also override help flags program - .help('-i, --ihelp', 'foo foo'); + .helpOption('-i, --ihelp', 'foo foo'); program .command('child') @@ -19,7 +19,7 @@ program program .command('family') - .help('-h, --help') + .helpOption('-h, --help') .action((cmd) => { console.log('Familysubcommand...'); }); diff --git a/typings/index.d.ts b/typings/index.d.ts index c430f2c54..8ff3a9edc 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -282,10 +282,18 @@ declare namespace local { * You can pass in flags and a description to override the help * flags and help description for your command. * - * @param {string | (str: string) => string} [flagsOrCb] + * @param {string} [flags] + * @param {string} [description] + * @return {Command} + */ + helpOption(flags?: string, description?: string): Command; + + /** Output help information and exit. + * + * @param {(str: string) => string} [cb] * @param {string} [description] */ - help(flagsOrCb?: string | helpCallback, description?: string): never; + help(cb: helpCallback, description?: string): never; } }