commander
Advanced tools
Comparing version 6.2.0 to 7.0.0-0
@@ -11,2 +11,31 @@ # Changelog | ||
## [7.0.0-0] (2020-10-25) | ||
### Added | ||
- use `.addHelpText()` to add text before or after the built-in help, for just current command or also for all subcommands ([#1296]) | ||
- enhance Option class ([#1331]) | ||
- allow hiding options from help | ||
- allow restricting option arguments to a list of choices | ||
- allow setting how default value is shown in help | ||
- refactor the code generating the help into a separate public Help class ([#1365]) | ||
- support sorting subcommands and options in help | ||
- support specifying wrap width (columns) | ||
- allow subclassing Help class | ||
- allow configuring Help class without subclassing | ||
### Fixed | ||
- wrapping bugs in help ([#1365]) | ||
- first line of command description was wrapping two characters early | ||
- pad width calculation was not including help option and help command | ||
- pad width calculation was including hidden options and commands | ||
### Changed | ||
- document and annotate deprecated routines ([#1349]) | ||
- deprecated callback parameter to `.help()` and `.outputHelp()` (removed from README) ([#1296]) | ||
- deprecate `.on('--help')` (removed from README) ([#1296]) | ||
- initialise the command description to empty string (previously undefined) ([#1365]) | ||
## [6.2.0] (2020-10-25) | ||
@@ -323,2 +352,3 @@ | ||
[#1275]: https://github.com/tj/commander.js/pull/1275 | ||
[#1296]: https://github.com/tj/commander.js/pull/1296 | ||
[#1301]: https://github.com/tj/commander.js/issues/1301 | ||
@@ -331,11 +361,14 @@ [#1306]: https://github.com/tj/commander.js/pull/1306 | ||
[#1326]: https://github.com/tj/commander.js/pull/1326 | ||
[#1331]: https://github.com/tj/commander.js/pull/1331 | ||
[#1332]: https://github.com/tj/commander.js/pull/1332 | ||
[#1349]: https://github.com/tj/commander.js/pull/1349 | ||
[#1353]: https://github.com/tj/commander.js/pull/1353 | ||
[#1360]: https://github.com/tj/commander.js/pull/1360 | ||
[#1361]: https://github.com/tj/commander.js/pull/1361 | ||
[#1365]: https://github.com/tj/commander.js/pull/1365 | ||
[#1368]: https://github.com/tj/commander.js/pull/1368 | ||
[#1375]: https://github.com/tj/commander.js/pull/1375 | ||
[Unreleased]: https://github.com/tj/commander.js/compare/master...develop | ||
[7.0.0-0]: https://github.com/tj/commander.js/compare/v6.2.0...v7.0.0-0 | ||
[6.2.0]: https://github.com/tj/commander.js/compare/v6.1.0..v6.2.0 | ||
@@ -342,0 +375,0 @@ [6.1.0]: https://github.com/tj/commander.js/compare/v6.0.0..v6.1.0 |
962
index.js
@@ -12,2 +12,334 @@ /** | ||
// Although this is a class, methods are static in style to allow override using subclass or just functions. | ||
class Help { | ||
constructor() { | ||
this.columns = process.stdout.columns || 80; | ||
this.sortSubcommands = false; | ||
this.sortOptions = false; | ||
} | ||
/** | ||
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. | ||
* | ||
* @param {Command} cmd | ||
* @returns {Command[]} | ||
*/ | ||
visibleCommands(cmd) { | ||
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden); | ||
if (cmd._hasImplicitHelpCommand()) { | ||
// Create a command matching the implicit help command. | ||
const args = cmd._helpCommandnameAndArgs.split(/ +/); | ||
const helpCommand = cmd.createCommand(args.shift()) | ||
.helpOption(false); | ||
helpCommand.description(cmd._helpCommandDescription); | ||
helpCommand._parseExpectedArgs(args); | ||
visibleCommands.push(helpCommand); | ||
} | ||
if (this.sortSubcommands) { | ||
visibleCommands.sort((a, b) => { | ||
return a.name().localeCompare(b.name()); | ||
}); | ||
} | ||
return visibleCommands; | ||
} | ||
/** | ||
* Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. | ||
* | ||
* @param {Command} cmd | ||
* @returns {Option[]} | ||
*/ | ||
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 = new Option(cmd._helpLongFlag, cmd._helpDescription); | ||
} else if (!showLongHelpFlag) { | ||
helpOption = new Option(cmd._helpShortFlag, cmd._helpDescription); | ||
} else { | ||
helpOption = new Option(cmd._helpFlags, cmd._helpDescription); | ||
} | ||
visibleOptions.push(helpOption); | ||
} | ||
if (this.sortOptions) { | ||
visibleOptions.sort((a, b) => { | ||
const compare = a.attributeName().localeCompare(b.attributeName()); | ||
if (compare === 0) { | ||
return (a.negate) ? +1 : -1; | ||
} | ||
return compare; | ||
}); | ||
} | ||
return visibleOptions; | ||
} | ||
/** | ||
* Get an array of the arguments which have descriptions. | ||
* | ||
* @param {Command} cmd | ||
* @returns {{ term: string, description:string }[]} | ||
*/ | ||
visibleArguments(cmd) { | ||
if (cmd._argsDescription && cmd._args.length) { | ||
return cmd._args.map((argument) => { | ||
return { term: argument.name, description: cmd._argsDescription[argument.name] || '' }; | ||
}, 0); | ||
} | ||
return []; | ||
} | ||
/** | ||
* Get the command term to show in the list of subcommands. | ||
* | ||
* @param {Command} cmd | ||
* @returns {string} | ||
*/ | ||
subcommandTerm(cmd) { | ||
// Legacy. Ignores custom usage string, and nested commands. | ||
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' '); | ||
return cmd._name + | ||
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') + | ||
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option | ||
(args ? ' ' + args : ''); | ||
} | ||
/** | ||
* Get the option term to show in the list of options. | ||
* | ||
* @param {Option} option | ||
* @returns {string} | ||
*/ | ||
optionTerm(option) { | ||
return option.flags; | ||
} | ||
/** | ||
* Get the longest command term length. | ||
* | ||
* @param {Command} cmd | ||
* @param {Help} helper | ||
* @returns {number} | ||
*/ | ||
longestSubcommandTermLength(cmd, helper) { | ||
return helper.visibleCommands(cmd).reduce((max, command) => { | ||
return Math.max(max, helper.subcommandTerm(command).length); | ||
}, 0); | ||
}; | ||
/** | ||
* Get the longest option term length. | ||
* | ||
* @param {Command} cmd | ||
* @param {Help} helper | ||
* @returns {number} | ||
*/ | ||
longestOptionTermLength(cmd, helper) { | ||
return helper.visibleOptions(cmd).reduce((max, option) => { | ||
return Math.max(max, helper.optionTerm(option).length); | ||
}, 0); | ||
}; | ||
/** | ||
* Get the longest argument term length. | ||
* | ||
* @param {Command} cmd | ||
* @param {Help} helper | ||
* @returns {number} | ||
*/ | ||
longestArgumentTermLength(cmd, helper) { | ||
return helper.visibleArguments(cmd).reduce((max, argument) => { | ||
return Math.max(max, argument.term.length); | ||
}, 0); | ||
}; | ||
/** | ||
* Get the command usage to be displayed at the top of the built-in help. | ||
* | ||
* @param {Command} cmd | ||
* @returns {string} | ||
*/ | ||
commandUsage(cmd) { | ||
// Usage | ||
let cmdName = cmd._name; | ||
if (cmd._aliases[0]) { | ||
cmdName = cmdName + '|' + cmd._aliases[0]; | ||
} | ||
let parentCmdNames = ''; | ||
for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) { | ||
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames; | ||
} | ||
return parentCmdNames + cmdName + ' ' + cmd.usage(); | ||
} | ||
/** | ||
* Get the description for the command. | ||
* | ||
* @param {Command} cmd | ||
* @returns {string} | ||
*/ | ||
commandDescription(cmd) { | ||
// @ts-ignore: overloaded return type | ||
return cmd.description(); | ||
} | ||
/** | ||
* Get the command description to show in the list of subcommands. | ||
* | ||
* @param {Command} cmd | ||
* @returns {string} | ||
*/ | ||
subcommandDescription(cmd) { | ||
// @ts-ignore: overloaded return type | ||
return cmd.description(); | ||
} | ||
/** | ||
* Get the option description to show in the list of options. | ||
* | ||
* @param {Option} option | ||
* @return {string} | ||
*/ | ||
optionDescription(option) { | ||
if (option.negate) { | ||
return option.description; | ||
} | ||
const extraInfo = []; | ||
if (option.argChoices) { | ||
extraInfo.push( | ||
// use stringify to match the display of the default value | ||
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`); | ||
} | ||
if (option.defaultValue !== undefined) { | ||
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); | ||
} | ||
if (extraInfo.length > 0) { | ||
return `${option.description} (${extraInfo.join(', ')})`; | ||
} | ||
return option.description; | ||
}; | ||
/** | ||
* Generate the built-in help text. | ||
* | ||
* @param {Command} cmd | ||
* @param {Help} helper | ||
* @returns {string} | ||
*/ | ||
formatHelp(cmd, helper) { | ||
const termWidth = helper.padWidth(cmd, helper); | ||
const columns = helper.columns; | ||
const itemIndentWidth = 2; | ||
const itemSeparatorWidth = 2; | ||
// itemIndent term itemSeparator description | ||
const descriptionWidth = columns - termWidth - itemIndentWidth - itemSeparatorWidth; | ||
function formatItem(term, description) { | ||
if (description) { | ||
return term.padEnd(termWidth + itemSeparatorWidth) + helper.wrap(description, descriptionWidth, termWidth + itemSeparatorWidth); | ||
} | ||
return term; | ||
}; | ||
function formatList(textArray) { | ||
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth)); | ||
} | ||
// Usage | ||
let output = [`Usage: ${helper.commandUsage(cmd)}`, '']; | ||
// Description | ||
const commandDescription = helper.commandDescription(cmd); | ||
if (commandDescription.length > 0) { | ||
output = output.concat([commandDescription, '']); | ||
} | ||
// Arguments | ||
const argumentList = helper.visibleArguments(cmd).map((argument) => { | ||
return formatItem(argument.term, argument.description); | ||
}); | ||
if (argumentList.length > 0) { | ||
output = output.concat(['Arguments:', formatList(argumentList), '']); | ||
} | ||
// Options | ||
const optionList = helper.visibleOptions(cmd).map((option) => { | ||
return formatItem(helper.optionTerm(option), helper.optionDescription(option)); | ||
}); | ||
if (optionList.length > 0) { | ||
output = output.concat(['Options:', formatList(optionList), '']); | ||
} | ||
// Commands | ||
const commandList = helper.visibleCommands(cmd).map((cmd) => { | ||
return formatItem(helper.subcommandTerm(cmd), helper.subcommandDescription(cmd)); | ||
}); | ||
if (commandList.length > 0) { | ||
output = output.concat(['Commands:', formatList(commandList), '']); | ||
} | ||
return output.join('\n'); | ||
} | ||
/** | ||
* Calculate the pad width from the maximum term length. | ||
* | ||
* @param {Command} cmd | ||
* @param {Help} helper | ||
* @returns {number} | ||
*/ | ||
padWidth(cmd, helper) { | ||
return Math.max( | ||
helper.longestOptionTermLength(cmd, helper), | ||
helper.longestSubcommandTermLength(cmd, helper), | ||
helper.longestArgumentTermLength(cmd, helper) | ||
); | ||
}; | ||
/** | ||
* Optionally wrap the given str to a max width of width characters per line | ||
* while indenting with indent spaces. Do not wrap if insufficient width or | ||
* string is manually formatted. | ||
* | ||
* @param {string} str | ||
* @param {number} width | ||
* @param {number} indent | ||
* @return {string} | ||
*/ | ||
wrap(str, width, indent) { | ||
// Detect manually wrapped and indented strings by searching for line breaks | ||
// followed by multiple spaces/tabs. | ||
if (str.match(/[\n]\s+/)) return str; | ||
// Do not wrap to narrow columns (or can end up with a word per line). | ||
const minWidth = 40; | ||
if (width < minWidth) return str; | ||
const indentString = ' '.repeat(indent); | ||
const regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g'); | ||
const lines = str.match(regex) || []; | ||
return lines.map((line, i) => { | ||
if (line.slice(-1) === '\n') { | ||
line = line.slice(0, line.length - 1); | ||
} | ||
return ((i > 0 && indent) ? indentString : '') + line.trimRight(); | ||
}).join('\n'); | ||
} | ||
} | ||
class Option { | ||
@@ -18,4 +350,3 @@ /** | ||
* @param {string} flags | ||
* @param {string} description | ||
* @api public | ||
* @param {string} [description] | ||
*/ | ||
@@ -25,2 +356,4 @@ | ||
this.flags = flags; | ||
this.description = description || ''; | ||
this.required = flags.includes('<'); // A value must be supplied when the option is specified. | ||
@@ -38,11 +371,91 @@ this.optional = flags.includes('['); // A value is optional when the option is specified. | ||
} | ||
this.description = description || ''; | ||
this.defaultValue = undefined; | ||
this.defaultValueDescription = undefined; | ||
this.parseArg = undefined; | ||
this.hidden = false; | ||
this.argChoices = undefined; | ||
} | ||
/** | ||
* Set the default value, and optionally supply the description to be displayed in the help. | ||
* | ||
* @param {any} value | ||
* @param {string} [description] | ||
* @return {Option} | ||
*/ | ||
default(value, description) { | ||
this.defaultValue = value; | ||
this.defaultValueDescription = description; | ||
return this; | ||
}; | ||
/** | ||
* Set the custom handler for processing CLI option arguments into option values. | ||
* | ||
* @param {Function} [fn] | ||
* @return {Option} | ||
*/ | ||
argParser(fn) { | ||
this.parseArg = fn; | ||
return this; | ||
}; | ||
/** | ||
* Whether the option is mandatory and must have a value after parsing. | ||
* | ||
* @param {boolean} [value] | ||
* @return {Option} | ||
*/ | ||
makeOptionMandatory(value) { | ||
this.mandatory = (value === undefined) || value; | ||
return this; | ||
}; | ||
/** | ||
* Hide option in help. | ||
* | ||
* @param {boolean} [value] | ||
* @return {Option} | ||
*/ | ||
hideHelp(value) { | ||
this.hidden = (value === undefined) || value; | ||
return this; | ||
}; | ||
/** | ||
* Validation of option argument failed. | ||
* Intended for use from custom argument processing functions. | ||
* | ||
* @param {string} message | ||
*/ | ||
argumentRejected(message) { | ||
throw new CommanderError(1, 'commander.optionArgumentRejected', message); | ||
} | ||
/** | ||
* Only allow option value to be one of choices. | ||
* | ||
* @param {string[]} values | ||
* @return {Option} | ||
*/ | ||
choices(values) { | ||
this.argChoices = values; | ||
this.parseArg = (arg) => { | ||
if (!values.includes(arg)) { | ||
this.argumentRejected(`error: option '${this.flags}' argument of '${arg}' not in allowed choices: ${values.join(', ')}`); | ||
} | ||
return arg; | ||
}; | ||
return this; | ||
}; | ||
/** | ||
* Return option name. | ||
* | ||
* @return {string} | ||
* @api private | ||
*/ | ||
@@ -110,3 +523,2 @@ | ||
* @param {string} [name] | ||
* @api public | ||
*/ | ||
@@ -136,2 +548,4 @@ | ||
this._combineFlagAndOptionalValue = true; | ||
this._description = ''; | ||
this._argsDescription = undefined; | ||
@@ -144,6 +558,7 @@ this._hidden = false; | ||
this._helpLongFlag = '--help'; | ||
this._hasImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false | ||
this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false | ||
this._helpCommandName = 'help'; | ||
this._helpCommandnameAndArgs = 'help [command]'; | ||
this._helpCommandDescription = 'display help for command'; | ||
this._helpConfiguration = {}; | ||
} | ||
@@ -175,3 +590,2 @@ | ||
* @return {Command} returns new command for action handler, or `this` for executable command | ||
* @api public | ||
*/ | ||
@@ -196,3 +610,3 @@ | ||
cmd._hidden = !!(opts.noHelp || opts.hidden); | ||
cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden | ||
cmd._hasHelpOption = this._hasHelpOption; | ||
@@ -206,2 +620,3 @@ cmd._helpFlags = this._helpFlags; | ||
cmd._helpCommandDescription = this._helpCommandDescription; | ||
cmd._helpConfiguration = this._helpConfiguration; | ||
cmd._exitCallback = this._exitCallback; | ||
@@ -229,3 +644,2 @@ cmd._storeOptionsAsProperties = this._storeOptionsAsProperties; | ||
* @return {Command} new command | ||
* @api public | ||
*/ | ||
@@ -238,2 +652,28 @@ | ||
/** | ||
* You can customise the help with a subclass of Help by overriding createHelp, | ||
* or by overriding Help properties using configureHelp(). | ||
* | ||
* @return {Help} | ||
*/ | ||
createHelp() { | ||
return Object.assign(new Help(), this.configureHelp()); | ||
}; | ||
/** | ||
* You can customise the help by overriding Help properties using configureHelp(), | ||
* or with a subclass of Help by overriding createHelp(). | ||
* | ||
* @param {Object} [configuration] - configuration options | ||
* @return {Command|Object} `this` command for chaining, or stored configuration | ||
*/ | ||
configureHelp(configuration) { | ||
if (configuration === undefined) return this._helpConfiguration; | ||
this._helpConfiguration = configuration; | ||
return this; | ||
} | ||
/** | ||
* Add a prepared subcommand. | ||
@@ -246,3 +686,2 @@ * | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -276,4 +715,2 @@ | ||
* Define argument syntax for the command. | ||
* | ||
* @api public | ||
*/ | ||
@@ -293,3 +730,2 @@ | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -299,5 +735,5 @@ | ||
if (enableOrNameAndArgs === false) { | ||
this._hasImplicitHelpCommand = false; | ||
this._addImplicitHelpCommand = false; | ||
} else { | ||
this._hasImplicitHelpCommand = true; | ||
this._addImplicitHelpCommand = true; | ||
if (typeof enableOrNameAndArgs === 'string') { | ||
@@ -317,7 +753,7 @@ this._helpCommandName = enableOrNameAndArgs.split(' ')[0]; | ||
_lazyHasImplicitHelpCommand() { | ||
if (this._hasImplicitHelpCommand === undefined) { | ||
this._hasImplicitHelpCommand = this.commands.length && !this._actionHandler && !this._findCommand('help'); | ||
_hasImplicitHelpCommand() { | ||
if (this._addImplicitHelpCommand === undefined) { | ||
return this.commands.length && !this._actionHandler && !this._findCommand('help'); | ||
} | ||
return this._hasImplicitHelpCommand; | ||
return this._addImplicitHelpCommand; | ||
}; | ||
@@ -375,3 +811,2 @@ | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -426,3 +861,2 @@ | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -501,36 +935,14 @@ | ||
/** | ||
* Internal implementation shared by .option() and .requiredOption() | ||
* Add an option. | ||
* | ||
* @param {Object} config | ||
* @param {string} flags | ||
* @param {string} description | ||
* @param {Function|*} [fn] - custom option processing function or default value | ||
* @param {*} [defaultValue] | ||
* @param {Option} option | ||
* @return {Command} `this` command for chaining | ||
* @api private | ||
*/ | ||
_optionEx(config, flags, description, fn, defaultValue) { | ||
const option = new Option(flags, description); | ||
addOption(option) { | ||
const oname = option.name(); | ||
const name = option.attributeName(); | ||
option.mandatory = !!config.mandatory; | ||
this._checkForOptionNameClash(option); | ||
// default as 3rd arg | ||
if (typeof fn !== 'function') { | ||
if (fn instanceof RegExp) { | ||
// This is a bit simplistic (especially no error messages), and probably better handled by caller using custom option processing. | ||
// No longer documented in README, but still present for backwards compatibility. | ||
const regex = fn; | ||
fn = (val, def) => { | ||
const m = regex.exec(val); | ||
return m ? m[0] : def; | ||
}; | ||
} else { | ||
defaultValue = fn; | ||
fn = null; | ||
} | ||
} | ||
let defaultValue = option.defaultValue; | ||
@@ -547,3 +959,2 @@ // preassign default value for --no-*, [optional], <required>, or plain flag if boolean value | ||
this._setOptionValue(name, defaultValue); | ||
option.defaultValue = defaultValue; | ||
} | ||
@@ -561,4 +972,12 @@ } | ||
// custom processing | ||
if (val !== null && fn) { | ||
val = fn(val, oldValue === undefined ? defaultValue : oldValue); | ||
if (val !== null && option.parseArg) { | ||
try { | ||
val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue); | ||
} catch (err) { | ||
if (err.code === 'commander.optionArgumentRejected') { | ||
console.error(err.message); | ||
this._exit(err.exitCode, err.code, err.message); | ||
} | ||
throw err; | ||
} | ||
} else if (val !== null && option.variadic) { | ||
@@ -589,9 +1008,34 @@ if (oldValue === defaultValue || !Array.isArray(oldValue)) { | ||
return this; | ||
}; | ||
} | ||
/** | ||
* Internal implementation shared by .option() and .requiredOption() | ||
* | ||
* @api private | ||
*/ | ||
_optionEx(config, flags, description, fn, defaultValue) { | ||
const option = new Option(flags, description); | ||
option.makeOptionMandatory(!!config.mandatory); | ||
if (typeof fn === 'function') { | ||
option.default(defaultValue).argParser(fn); | ||
} else if (fn instanceof RegExp) { | ||
// deprecated | ||
const regex = fn; | ||
fn = (val, def) => { | ||
const m = regex.exec(val); | ||
return m ? m[0] : def; | ||
}; | ||
option.default(defaultValue).argParser(fn); | ||
} else { | ||
option.default(fn); | ||
} | ||
return this.addOption(option); | ||
} | ||
/** | ||
* Define option with `flags`, `description` and optional | ||
* coercion `fn`. | ||
* | ||
* The `flags` string should contain both the short and long flags, | ||
* The `flags` string contains the short and/or long flags, | ||
* separated by comma, a pipe or space. The following are all valid | ||
@@ -641,3 +1085,2 @@ * all will output this way when `--help` is used. | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -653,3 +1096,3 @@ | ||
* | ||
* The `flags` string should contain both the short and long flags, separated by comma, a pipe or space. | ||
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. | ||
* | ||
@@ -661,3 +1104,2 @@ * @param {string} flags | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -679,3 +1121,2 @@ | ||
* @param {Boolean} [arg] - if `true` or omitted, an optional value can be specified directly after the flag. | ||
* @api public | ||
*/ | ||
@@ -692,3 +1133,2 @@ combineFlagAndOptionalValue(arg) { | ||
* for unknown options. | ||
* @api public | ||
*/ | ||
@@ -706,3 +1146,2 @@ allowUnknownOption(arg) { | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -725,3 +1164,2 @@ | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -781,3 +1219,2 @@ | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -794,3 +1231,3 @@ | ||
argv = process.argv; | ||
// @ts-ignore | ||
// @ts-ignore: unknown property | ||
if (process.versions && process.versions.electron) { | ||
@@ -811,3 +1248,3 @@ parseOptions.from = 'electron'; | ||
case 'electron': | ||
// @ts-ignore | ||
// @ts-ignore: unknown property | ||
if (process.defaultApp) { | ||
@@ -826,3 +1263,5 @@ this._scriptPath = argv[1]; | ||
} | ||
// @ts-ignore: unknown property | ||
if (!this._scriptPath && process.mainModule) { | ||
// @ts-ignore: unknown property | ||
this._scriptPath = process.mainModule.filename; | ||
@@ -858,3 +1297,2 @@ } | ||
* @return {Promise} | ||
* @api public | ||
*/ | ||
@@ -884,3 +1322,5 @@ | ||
// Fallback in case not set, due to how Command created or called. | ||
// @ts-ignore: unknown property | ||
if (!scriptPath && process.mainModule) { | ||
// @ts-ignore: unknown property | ||
scriptPath = process.mainModule.filename; | ||
@@ -984,3 +1424,3 @@ } | ||
const subCommand = this._findCommand(commandName); | ||
if (!subCommand) this._helpAndError(); | ||
if (!subCommand) this.help({ error: true }); | ||
@@ -1008,3 +1448,3 @@ if (subCommand._executableHandler) { | ||
this._dispatchSubcommand(operands[0], operands.slice(1), unknown); | ||
} else if (this._lazyHasImplicitHelpCommand() && operands[0] === this._helpCommandName) { | ||
} else if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) { | ||
if (operands.length === 1) { | ||
@@ -1021,3 +1461,3 @@ this.help(); | ||
// probably missing subcommand and no handler, user needs help | ||
this._helpAndError(); | ||
this.help({ error: true }); | ||
} | ||
@@ -1053,3 +1493,3 @@ | ||
// This command has subcommands and nothing hooked up at this level, so display help. | ||
this._helpAndError(); | ||
this.help({ error: true }); | ||
} else { | ||
@@ -1115,3 +1555,2 @@ // fall through for caller to handle after calling .parse() | ||
* @return {{operands: String[], unknown: String[]}} | ||
* @api public | ||
*/ | ||
@@ -1212,3 +1651,2 @@ | ||
* @return {Object} | ||
* @api public | ||
*/ | ||
@@ -1320,3 +1758,2 @@ opts() { | ||
* @return {this | string} `this` command for chaining, or version string if no arguments | ||
* @api public | ||
*/ | ||
@@ -1342,8 +1779,6 @@ | ||
* | ||
* @param {string} str | ||
* @param {string} [str] | ||
* @param {Object} [argsDescription] | ||
* @return {string|Command} | ||
* @api public | ||
*/ | ||
description(str, argsDescription) { | ||
@@ -1363,3 +1798,2 @@ if (str === undefined && argsDescription === undefined) return this._description; | ||
* @return {string|Command} | ||
* @api public | ||
*/ | ||
@@ -1389,3 +1823,2 @@ | ||
* @return {string[]|Command} | ||
* @api public | ||
*/ | ||
@@ -1406,3 +1839,2 @@ | ||
* @return {String|Command} | ||
* @api public | ||
*/ | ||
@@ -1432,4 +1864,3 @@ | ||
* @param {string} [str] | ||
* @return {String|Command} | ||
* @api public | ||
* @return {string|Command} | ||
*/ | ||
@@ -1444,249 +1875,71 @@ | ||
/** | ||
* Return prepared commands. | ||
* Return program help documentation. | ||
* | ||
* @return {Array} | ||
* @api private | ||
* @return {string} | ||
*/ | ||
prepareCommands() { | ||
const commandDetails = this.commands.filter((cmd) => { | ||
return !cmd._hidden; | ||
}).map((cmd) => { | ||
const args = cmd._args.map((arg) => { | ||
return humanReadableArgName(arg); | ||
}).join(' '); | ||
return [ | ||
cmd._name + | ||
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') + | ||
(cmd.options.length ? ' [options]' : '') + | ||
(args ? ' ' + args : ''), | ||
cmd._description | ||
]; | ||
}); | ||
if (this._lazyHasImplicitHelpCommand()) { | ||
commandDetails.push([this._helpCommandnameAndArgs, this._helpCommandDescription]); | ||
} | ||
return commandDetails; | ||
helpInformation() { | ||
const helper = this.createHelp(); | ||
return helper.formatHelp(this, helper); | ||
}; | ||
/** | ||
* Return the largest command length. | ||
* | ||
* @return {number} | ||
* @api private | ||
*/ | ||
largestCommandLength() { | ||
const commands = this.prepareCommands(); | ||
return commands.reduce((max, command) => { | ||
return Math.max(max, command[0].length); | ||
}, 0); | ||
}; | ||
_getHelpContext(contextOptions) { | ||
contextOptions = contextOptions || {}; | ||
const context = { error: !!contextOptions.error }; | ||
let write; | ||
if (context.error) { | ||
write = (arg, ...args) => process.stderr.write(arg, ...args); | ||
} else { | ||
write = (arg, ...args) => process.stdout.write(arg, ...args); | ||
} | ||
context.write = contextOptions.write || write; | ||
context.command = this; | ||
return context; | ||
} | ||
/** | ||
* Return the largest option length. | ||
* Output help information for this command. | ||
* | ||
* @return {number} | ||
* @api private | ||
*/ | ||
largestOptionLength() { | ||
const options = [].slice.call(this.options); | ||
options.push({ | ||
flags: this._helpFlags | ||
}); | ||
return options.reduce((max, option) => { | ||
return Math.max(max, option.flags.length); | ||
}, 0); | ||
}; | ||
/** | ||
* Return the largest arg length. | ||
* Outputs built-in help, and custom text added using `.addHelpText()`. | ||
* | ||
* @return {number} | ||
* @api private | ||
* @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout | ||
*/ | ||
largestArgLength() { | ||
return this._args.reduce((max, arg) => { | ||
return Math.max(max, arg.name.length); | ||
}, 0); | ||
}; | ||
/** | ||
* Return the pad width. | ||
* | ||
* @return {number} | ||
* @api private | ||
*/ | ||
padWidth() { | ||
let width = this.largestOptionLength(); | ||
if (this._argsDescription && this._args.length) { | ||
if (this.largestArgLength() > width) { | ||
width = this.largestArgLength(); | ||
} | ||
outputHelp(contextOptions) { | ||
let deprecatedCallback; | ||
if (typeof contextOptions === 'function') { | ||
deprecatedCallback = contextOptions; | ||
contextOptions = undefined; | ||
} | ||
const context = this._getHelpContext(contextOptions); | ||
if (this.commands && this.commands.length) { | ||
if (this.largestCommandLength() > width) { | ||
width = this.largestCommandLength(); | ||
} | ||
const groupListeners = []; | ||
let command = this; | ||
while (command) { | ||
groupListeners.push(command); // ordered from current command to root | ||
command = command.parent; | ||
} | ||
return width; | ||
}; | ||
groupListeners.slice().reverse().forEach(command => command.emit('beforeAllHelp', context)); | ||
this.emit('beforeHelp', context); | ||
/** | ||
* Return help for options. | ||
* | ||
* @return {string} | ||
* @api private | ||
*/ | ||
optionHelp() { | ||
const width = this.padWidth(); | ||
const columns = process.stdout.columns || 80; | ||
const descriptionWidth = columns - width - 4; | ||
function padOptionDetails(flags, description) { | ||
return pad(flags, width) + ' ' + optionalWrap(description, descriptionWidth, width + 2); | ||
}; | ||
// Explicit options (including version) | ||
const help = this.options.map((option) => { | ||
const fullDesc = option.description + | ||
((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); | ||
return padOptionDetails(option.flags, fullDesc); | ||
}); | ||
// Implicit help | ||
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) { | ||
helpFlags = this._helpLongFlag; | ||
} else if (!showLongHelpFlag) { | ||
helpFlags = this._helpShortFlag; | ||
let helpInformation = this.helpInformation(); | ||
if (deprecatedCallback) { | ||
helpInformation = deprecatedCallback(helpInformation); | ||
if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { | ||
throw new Error('outputHelp callback must return a string or a Buffer'); | ||
} | ||
help.push(padOptionDetails(helpFlags, this._helpDescription)); | ||
} | ||
context.write(helpInformation); | ||
return help.join('\n'); | ||
this.emit(this._helpLongFlag); // deprecated | ||
this.emit('afterHelp', context); | ||
groupListeners.forEach(command => command.emit('afterAllHelp', context)); | ||
}; | ||
/** | ||
* Return command help documentation. | ||
* | ||
* @return {string} | ||
* @api private | ||
*/ | ||
commandHelp() { | ||
if (!this.commands.length && !this._lazyHasImplicitHelpCommand()) return ''; | ||
const commands = this.prepareCommands(); | ||
const width = this.padWidth(); | ||
const columns = process.stdout.columns || 80; | ||
const descriptionWidth = columns - width - 4; | ||
return [ | ||
'Commands:', | ||
commands.map((cmd) => { | ||
const desc = cmd[1] ? ' ' + cmd[1] : ''; | ||
return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2); | ||
}).join('\n').replace(/^/gm, ' '), | ||
'' | ||
].join('\n'); | ||
}; | ||
/** | ||
* Return program help documentation. | ||
* | ||
* @return {string} | ||
* @api public | ||
*/ | ||
helpInformation() { | ||
let desc = []; | ||
if (this._description) { | ||
desc = [ | ||
this._description, | ||
'' | ||
]; | ||
const argsDescription = this._argsDescription; | ||
if (argsDescription && this._args.length) { | ||
const width = this.padWidth(); | ||
const columns = process.stdout.columns || 80; | ||
const descriptionWidth = columns - width - 5; | ||
desc.push('Arguments:'); | ||
this._args.forEach((arg) => { | ||
desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name] || '', descriptionWidth, width + 4)); | ||
}); | ||
desc.push(''); | ||
} | ||
} | ||
let cmdName = this._name; | ||
if (this._aliases[0]) { | ||
cmdName = cmdName + '|' + this._aliases[0]; | ||
} | ||
let parentCmdNames = ''; | ||
for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) { | ||
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames; | ||
} | ||
const usage = [ | ||
'Usage: ' + parentCmdNames + cmdName + ' ' + this.usage(), | ||
'' | ||
]; | ||
let cmds = []; | ||
const commandHelp = this.commandHelp(); | ||
if (commandHelp) cmds = [commandHelp]; | ||
let options = []; | ||
if (this._hasHelpOption || this.options.length > 0) { | ||
options = [ | ||
'Options:', | ||
'' + this.optionHelp().replace(/^/gm, ' '), | ||
'' | ||
]; | ||
} | ||
return usage | ||
.concat(desc) | ||
.concat(options) | ||
.concat(cmds) | ||
.join('\n'); | ||
}; | ||
/** | ||
* Output help information for this command. | ||
* | ||
* When listener(s) are available for the helpLongFlag | ||
* those callbacks are invoked. | ||
* | ||
* @api public | ||
*/ | ||
outputHelp(cb) { | ||
if (!cb) { | ||
cb = (passthru) => { | ||
return passthru; | ||
}; | ||
} | ||
const cbOutput = cb(this.helpInformation()); | ||
if (typeof cbOutput !== 'string' && !Buffer.isBuffer(cbOutput)) { | ||
throw new Error('outputHelp callback must return a string or a Buffer'); | ||
} | ||
process.stdout.write(cbOutput); | ||
this.emit(this._helpLongFlag); | ||
}; | ||
/** | ||
* You can pass in flags and a description to override the help | ||
@@ -1699,3 +1952,2 @@ * flags and help description for your command. Pass in false to | ||
* @return {Command} `this` command for chaining | ||
* @api public | ||
*/ | ||
@@ -1721,24 +1973,48 @@ | ||
* | ||
* @param {Function} [cb] | ||
* @api public | ||
* Outputs built-in help, and custom text added using `.addHelpText()`. | ||
* | ||
* @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout | ||
*/ | ||
help(cb) { | ||
this.outputHelp(cb); | ||
// exitCode: preserving original behaviour which was calling process.exit() | ||
help(contextOptions) { | ||
this.outputHelp(contextOptions); | ||
let exitCode = process.exitCode || 0; | ||
if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error) { | ||
exitCode = 1; | ||
} | ||
// message: do not have all displayed text available so only passing placeholder. | ||
this._exit(process.exitCode || 0, 'commander.help', '(outputHelp)'); | ||
this._exit(exitCode, 'commander.help', '(outputHelp)'); | ||
}; | ||
/** | ||
* Output help information and exit. Display for error situations. | ||
* Add additional text to be displayed with the built-in help. | ||
* | ||
* @api private | ||
* Position is 'before' or 'after' to affect just this command, | ||
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. | ||
* | ||
* @param {string} position - before or after built-in help | ||
* @param {string | Function} text - string to add, or a function returning a string | ||
* @return {Command} `this` command for chaining | ||
*/ | ||
_helpAndError() { | ||
this.outputHelp(); | ||
// message: do not have all displayed text available so only passing placeholder. | ||
this._exit(1, 'commander.help', '(outputHelp)'); | ||
}; | ||
addHelpText(position, text) { | ||
const allowedValues = ['beforeAll', 'before', 'after', 'afterAll']; | ||
if (!allowedValues.includes(position)) { | ||
throw new Error(`Unexpected value for position to addHelpText. | ||
Expecting one of '${allowedValues.join("', '")}'`); | ||
} | ||
const helpEvent = `${position}Help`; | ||
this.on(helpEvent, (context) => { | ||
let helpStr; | ||
if (typeof text === 'function') { | ||
helpStr = text({ error: context.error, command: context.command }); | ||
} else { | ||
helpStr = text; | ||
} | ||
// Ignore falsy value when nothing to output. | ||
if (helpStr) { | ||
context.write(`${helpStr}\n`); | ||
} | ||
}); | ||
return this; | ||
} | ||
}; | ||
@@ -1760,2 +2036,3 @@ | ||
exports.CommanderError = CommanderError; | ||
exports.Help = Help; | ||
@@ -1777,59 +2054,2 @@ /** | ||
/** | ||
* Pad `str` to `width`. | ||
* | ||
* @param {string} str | ||
* @param {number} width | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function pad(str, width) { | ||
const len = Math.max(0, width - str.length); | ||
return str + Array(len + 1).join(' '); | ||
} | ||
/** | ||
* Wraps the given string with line breaks at the specified width while breaking | ||
* words and indenting every but the first line on the left. | ||
* | ||
* @param {string} str | ||
* @param {number} width | ||
* @param {number} indent | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function wrap(str, width, indent) { | ||
const regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g'); | ||
const lines = str.match(regex) || []; | ||
return lines.map((line, i) => { | ||
if (line.slice(-1) === '\n') { | ||
line = line.slice(0, line.length - 1); | ||
} | ||
return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line.trimRight(); | ||
}).join('\n'); | ||
} | ||
/** | ||
* Optionally wrap the given str to a max width of width characters per line | ||
* while indenting with indent spaces. Do not wrap if insufficient width or | ||
* string is manually formatted. | ||
* | ||
* @param {string} str | ||
* @param {number} width | ||
* @param {number} indent | ||
* @return {string} | ||
* @api private | ||
*/ | ||
function optionalWrap(str, width, indent) { | ||
// Detect manually wrapped and indented strings by searching for line breaks | ||
// followed by multiple spaces/tabs. | ||
if (str.match(/[\n]\s+/)) return str; | ||
// Do not wrap to narrow columns (or can end up with a word per line). | ||
const minWidth = 40; | ||
if (width < minWidth) return str; | ||
return wrap(str, width, indent); | ||
} | ||
/** | ||
* Output help information if help flags specified | ||
@@ -1836,0 +2056,0 @@ * |
{ | ||
"name": "commander", | ||
"version": "6.2.0", | ||
"version": "7.0.0-0", | ||
"description": "the complete solution for node.js command-line programs", | ||
@@ -25,3 +25,5 @@ "keywords": [ | ||
"test": "jest && npm run test-typings", | ||
"test-typings": "tsc -p tsconfig.json" | ||
"test-typings": "tsc -p tsconfig.json", | ||
"typescript-checkJS": "tsc --allowJS --checkJS index.js --noEmit", | ||
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS" | ||
}, | ||
@@ -28,0 +30,0 @@ "main": "index", |
247
Readme.md
@@ -19,6 +19,7 @@ # Commander.js | ||
- [Other option types, negatable boolean and boolean|value](#other-option-types-negatable-boolean-and-booleanvalue) | ||
- [Custom option processing](#custom-option-processing) | ||
- [Required option](#required-option) | ||
- [Variadic option](#variadic-option) | ||
- [Version option](#version-option) | ||
- [More configuration](#more-configuration) | ||
- [Custom option processing](#custom-option-processing) | ||
- [Commands](#commands) | ||
@@ -30,8 +31,7 @@ - [Specify the argument syntax](#specify-the-argument-syntax) | ||
- [Custom help](#custom-help) | ||
- [Display help from code](#display-help-from-code) | ||
- [.usage and .name](#usage-and-name) | ||
- [.help(cb)](#helpcb) | ||
- [.outputHelp(cb)](#outputhelpcb) | ||
- [.helpInformation()](#helpinformation) | ||
- [.helpOption(flags, description)](#helpoptionflags-description) | ||
- [.addHelpCommand()](#addhelpcommand) | ||
- [More configuration](#more-configuration-1) | ||
- [Custom event listeners](#custom-event-listeners) | ||
@@ -47,2 +47,3 @@ - [Bits and pieces](#bits-and-pieces) | ||
- [Override exit handling](#override-exit-handling) | ||
- [Additional documentation](#additional-documentation) | ||
- [Examples](#examples) | ||
@@ -212,61 +213,2 @@ - [Support](#support) | ||
### Custom option processing | ||
You may specify a function to do custom processing of option-arguments. The callback function receives two parameters, | ||
the user specified option-argument and the previous value for the option. It returns the new value for the option. | ||
This allows you to coerce the option-argument to the desired type, or accumulate values, or do entirely custom processing. | ||
You can optionally specify the default/starting value for the option after the function parameter. | ||
Example file: [options-custom-processing.js](./examples/options-custom-processing.js) | ||
```js | ||
function myParseInt(value, dummyPrevious) { | ||
// parseInt takes a string and an optional radix | ||
return parseInt(value); | ||
} | ||
function increaseVerbosity(dummyValue, previous) { | ||
return previous + 1; | ||
} | ||
function collect(value, previous) { | ||
return previous.concat([value]); | ||
} | ||
function commaSeparatedList(value, dummyPrevious) { | ||
return value.split(','); | ||
} | ||
program | ||
.option('-f, --float <number>', 'float argument', parseFloat) | ||
.option('-i, --integer <number>', 'integer argument', myParseInt) | ||
.option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0) | ||
.option('-c, --collect <value>', 'repeatable value', collect, []) | ||
.option('-l, --list <items>', 'comma separated list', commaSeparatedList) | ||
; | ||
program.parse(process.argv); | ||
if (program.float !== undefined) console.log(`float: ${program.float}`); | ||
if (program.integer !== undefined) console.log(`integer: ${program.integer}`); | ||
if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`); | ||
if (program.collect.length > 0) console.log(program.collect); | ||
if (program.list !== undefined) console.log(program.list); | ||
``` | ||
```bash | ||
$ custom -f 1e2 | ||
float: 100 | ||
$ custom --integer 2 | ||
integer: 2 | ||
$ custom -v -v -v | ||
verbose: 3 | ||
$ custom -c a -c b -c c | ||
[ 'a', 'b', 'c' ] | ||
$ custom --list x,y,z | ||
[ 'x', 'y', 'z' ] | ||
``` | ||
### Required option | ||
@@ -344,2 +286,88 @@ | ||
### More configuration | ||
You can add most options using the `.option()` method, but there are some additional features available | ||
by constructing an `Option` explicitly for less common cases. | ||
Example file: [options-extra.js](./examples/options-extra.js) | ||
```js | ||
program | ||
.addOption(new Option('-s, --secret').hideHelp()) | ||
.addOption(new Option('-t, --timeout <delay>', 'timeout in seconds').default(60, 'one minute')) | ||
.addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large'])); | ||
``` | ||
```bash | ||
$ extra --help | ||
Usage: help [options] | ||
Options: | ||
-t, --timeout <delay> timeout in seconds (default: one minute) | ||
-d, --drink <size> drink cup size (choices: "small", "medium", "large") | ||
-h, --help display help for command | ||
$ extra --drink huge | ||
error: option '-d, --drink <size>' argument of 'huge' not in allowed choices: small, medium, large | ||
``` | ||
### Custom option processing | ||
You may specify a function to do custom processing of option-arguments. The callback function receives two parameters, | ||
the user specified option-argument and the previous value for the option. It returns the new value for the option. | ||
This allows you to coerce the option-argument to the desired type, or accumulate values, or do entirely custom processing. | ||
You can optionally specify the default/starting value for the option after the function parameter. | ||
Example file: [options-custom-processing.js](./examples/options-custom-processing.js) | ||
```js | ||
function myParseInt(value, dummyPrevious) { | ||
// parseInt takes a string and an optional radix | ||
return parseInt(value); | ||
} | ||
function increaseVerbosity(dummyValue, previous) { | ||
return previous + 1; | ||
} | ||
function collect(value, previous) { | ||
return previous.concat([value]); | ||
} | ||
function commaSeparatedList(value, dummyPrevious) { | ||
return value.split(','); | ||
} | ||
program | ||
.option('-f, --float <number>', 'float argument', parseFloat) | ||
.option('-i, --integer <number>', 'integer argument', myParseInt) | ||
.option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0) | ||
.option('-c, --collect <value>', 'repeatable value', collect, []) | ||
.option('-l, --list <items>', 'comma separated list', commaSeparatedList) | ||
; | ||
program.parse(process.argv); | ||
if (program.float !== undefined) console.log(`float: ${program.float}`); | ||
if (program.integer !== undefined) console.log(`integer: ${program.integer}`); | ||
if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`); | ||
if (program.collect.length > 0) console.log(program.collect); | ||
if (program.list !== undefined) console.log(program.list); | ||
``` | ||
```bash | ||
$ custom -f 1e2 | ||
float: 100 | ||
$ custom --integer 2 | ||
integer: 2 | ||
$ custom -v -v -v | ||
verbose: 3 | ||
$ custom -c a -c b -c c | ||
[ 'a', 'b', 'c' ] | ||
$ custom --list x,y,z | ||
[ 'x', 'y', 'z' ] | ||
``` | ||
## Commands | ||
@@ -520,3 +548,3 @@ | ||
You can display extra information by listening for "--help". | ||
You can add extra text to be displayed along with the built-in help. | ||
@@ -529,8 +557,6 @@ Example file: [custom-help](./examples/custom-help) | ||
// must be before .parse() | ||
program.on('--help', () => { | ||
console.log(''); | ||
console.log('Example call:'); | ||
console.log(' $ custom-help --help'); | ||
}); | ||
program.addHelpText('after', ` | ||
Example call: | ||
$ custom-help --help`); | ||
``` | ||
@@ -551,2 +577,24 @@ | ||
The positions in order displayed are: | ||
- `beforeAll`: add to the program for a global banner or header | ||
- `before`: display extra information before built-in help | ||
- `after`: display extra information after built-in help | ||
- `afterAll`: add to the program for a global footer (epilog) | ||
The positions "beforeAll" and "afterAll" apply to the command and all its subcommands. | ||
The second parameter can be a string, or a function returning a string. The function is passed a context object for your convenience. The properties are: | ||
- error: a boolean for whether the help is being displayed due to a usage error | ||
- command: the Command which is displaying the help | ||
### Display help from code | ||
`.help()`: display help information and exit immediately. You can optionally pass `{ error: true }` to display on stderr and exit with an error status. | ||
`.outputHelp()`: output help information without exiting. You can optionally pass `{ error: true }` to display on stderr. | ||
`.helpInformation()`: get the built-in command help information as a string for processing or displaying yourself. | ||
### .usage and .name | ||
@@ -569,19 +617,5 @@ | ||
### .help(cb) | ||
Output help information and exit immediately. Optional callback cb allows post-processing of help text before it is displayed. | ||
### .outputHelp(cb) | ||
Output help information without exiting. | ||
Optional callback cb allows post-processing of help text before it is displayed. | ||
### .helpInformation() | ||
Get the command help information as a string for processing or displaying yourself. (The text does not include the custom help | ||
from `--help` listeners.) | ||
### .helpOption(flags, description) | ||
Override the default help flags and description. Pass false to disable the built-in help option. | ||
By default every command has a help option. Override the default help flags and description. Pass false to disable the built-in help option. | ||
@@ -595,3 +629,3 @@ ```js | ||
You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`. | ||
A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`. | ||
@@ -604,2 +638,24 @@ You can both turn on and customise the help command by supplying the name and description: | ||
### More configuration | ||
The built-in help is formatted using the Help class. | ||
You can configure the Help behaviour by modifying data properties and methods using `.configureHelp()`, or by subclassing using `.createHelp()` if you prefer. | ||
The data properties are: | ||
- `columns`: specify the wrap width, useful for unit tests | ||
- `sortSubcommands`: sort the subcommands alphabetically | ||
- `sortOptions`: sort the options alphabetically | ||
There are methods getting the visible lists of arguments, options, and subcommands. There are methods for formatting the items in the lists, with each item having a _term_ and _description_. Take a look at `.formatHelp()` to see how they are used. | ||
Example file: [configure-help.js](./examples/configure-help.js) | ||
``` | ||
program.configureHelp({ | ||
sortSubcommands: true, | ||
subcommandTerm: (cmd) => cmd.name() // Just show the name, instead of short usage. | ||
}); | ||
``` | ||
## Custom event listeners | ||
@@ -748,2 +804,9 @@ | ||
### Additional documentation | ||
There is more information available about: | ||
- [deprecated](./docs/deprecated.md) features still supported for backwards compatibility | ||
- [options taking varying arguments](./docs/options-taking-varying-arguments.md) | ||
## Examples | ||
@@ -779,9 +842,7 @@ | ||
console.log('exec "%s" using %s mode', cmd, options.exec_mode); | ||
}).on('--help', function() { | ||
console.log(''); | ||
console.log('Examples:'); | ||
console.log(''); | ||
console.log(' $ deploy exec sequential'); | ||
console.log(' $ deploy exec async'); | ||
}); | ||
}).addHelpText('after', ` | ||
Examples: | ||
$ deploy exec sequential | ||
$ deploy exec async` | ||
); | ||
@@ -788,0 +849,0 @@ program.parse(process.argv); |
// Type definitions for commander | ||
// Original definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne> | ||
// Using method rather than property for method-signature-style, to document method overloads separately. Allow either. | ||
/* eslint-disable @typescript-eslint/method-signature-style */ | ||
declare namespace commander { | ||
@@ -16,16 +19,123 @@ | ||
flags: string; | ||
description: string; | ||
required: boolean; // A value must be supplied when the option is specified. | ||
optional: boolean; // A value is optional when the option is specified. | ||
variadic: boolean; | ||
mandatory: boolean; // The option must have a value after parsing, which usually means it must be specified on command line. | ||
bool: boolean; | ||
optionFlags: string; | ||
short?: string; | ||
long: string; | ||
description: string; | ||
long?: string; | ||
negate: boolean; | ||
defaultValue?: any; | ||
defaultValueDescription?: string; | ||
parseArg?: <T>(value: string, previous: T) => T; | ||
hidden: boolean; | ||
argChoices?: string[]; | ||
/** | ||
* Set the default value, and optionally supply the description to be displayed in the help. | ||
*/ | ||
default(value: any, description?: string): this; | ||
/** | ||
* Calculate the full description, including defaultValue etc. | ||
*/ | ||
fullDescription(): string; | ||
/** | ||
* Set the custom handler for processing CLI option arguments into option values. | ||
*/ | ||
argParser<T>(fn: (value: string, previous: T) => T): this; | ||
/** | ||
* Whether the option is mandatory and must have a value after parsing. | ||
*/ | ||
makeOptionMandatory(value?: boolean): this; | ||
/** | ||
* Hide option in help. | ||
*/ | ||
hideHelp(value?: boolean): this; | ||
/** | ||
* Validation of option argument failed. | ||
* Intended for use from custom argument processing functions. | ||
*/ | ||
argumentRejected(messsage: string): never; | ||
/** | ||
* Only allow option value to be one of choices. | ||
*/ | ||
choices(values: string[]): this; | ||
/** | ||
* Return option name. | ||
*/ | ||
name(): string; | ||
} | ||
type OptionConstructor = new (flags: string, description?: string) => Option; | ||
interface Help { | ||
/** output columns, long lines are wrapped to fit */ | ||
columns: number; | ||
sortSubcommands: boolean; | ||
sortOptions: boolean; | ||
/** Get the command term to show in the list of subcommands. */ | ||
subcommandTerm(cmd: Command): string; | ||
/** Get the command description to show in the list of subcommands. */ | ||
subcommandDescription(cmd: Command): string; | ||
/** Get the option term to show in the list of options. */ | ||
optionTerm(option: Option): string; | ||
/** Get the option description to show in the list of options. */ | ||
optionDescription(option: Option): string; | ||
/** Get the command usage to be displayed at the top of the built-in help. */ | ||
commandUsage(cmd: Command): string; | ||
/** Get the description for the command. */ | ||
commandDescription(cmd: Command): string; | ||
/** Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. */ | ||
visibleCommands(cmd: Command): Command[]; | ||
/** Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. */ | ||
visibleOptions(cmd: Command): Option[]; | ||
/** Get an array of the arguments which have descriptions. */ | ||
visibleArguments(cmd: Command): Array<{ term: string; description: string}>; | ||
/** Get the longest command term length. */ | ||
longestSubcommandTermLength(cmd: Command, helper: Help): number; | ||
/** Get the longest option term length. */ | ||
longestOptionTermLength(cmd: Command, helper: Help): number; | ||
/** Get the longest argument term length. */ | ||
longestArgumentTermLength(cmd: Command, helper: Help): number; | ||
/** Calculate the pad width from the maximum term length. */ | ||
padWidth(cmd: Command, helper: Help): number; | ||
/** | ||
* Optionally wrap the given str to a max width of width characters per line | ||
* while indenting with indent spaces. Do not wrap if insufficient width or | ||
* string is manually formatted. | ||
*/ | ||
wrap(str: string, width: number, indent: number): string; | ||
/** Generate the built-in help text. */ | ||
formatHelp(cmd: Command, helper: Help): string; | ||
} | ||
type HelpConstructor = new () => Help; | ||
type HelpConfiguration = Partial<Help>; | ||
interface ParseOptions { | ||
from: 'node' | 'electron' | 'user'; | ||
} | ||
interface HelpContext { // optional parameter for .help() and .outputHelp() | ||
error: boolean; | ||
} | ||
interface AddHelpTextContext { // passed to text function used with .addHelpText() | ||
error: boolean; | ||
command: Command; | ||
} | ||
type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll'; | ||
interface Command { | ||
@@ -130,2 +240,16 @@ [key: string]: any; // options as properties | ||
/** | ||
* You can customise the help with a subclass of Help by overriding createHelp, | ||
* or by overriding Help properties using configureHelp(). | ||
*/ | ||
createHelp(): Help; | ||
/** | ||
* You can customise the help by overriding Help properties using configureHelp(), | ||
* or with a subclass of Help by overriding createHelp(). | ||
*/ | ||
configureHelp(configuration: HelpConfiguration): this; | ||
/** Get configuration */ | ||
configureHelp(): HelpConfiguration; | ||
/** | ||
* Register callback `fn` for the command. | ||
@@ -149,3 +273,3 @@ * | ||
* | ||
* The `flags` string should contain both the short and long flags, | ||
* The `flags` string contains the short and/or long flags, | ||
* separated by comma, a pipe or space. The following are all valid | ||
@@ -189,4 +313,5 @@ * all will output this way when `--help` is used. | ||
option(flags: string, description?: string, defaultValue?: string | boolean): this; | ||
option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; | ||
/** @deprecated since v7, instead use choices or a custom function */ | ||
option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; | ||
option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; | ||
@@ -197,9 +322,17 @@ /** | ||
* | ||
* The `flags` string should contain both the short and long flags, separated by comma, a pipe or space. | ||
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. | ||
*/ | ||
requiredOption(flags: string, description?: string, defaultValue?: string | boolean): this; | ||
requiredOption<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; | ||
/** @deprecated since v7, instead use choices or a custom function */ | ||
requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; | ||
requiredOption<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; | ||
/** | ||
* Add a prepared Option. | ||
* | ||
* See .option() and .requiredOption() for creating and attaching an option in a single call. | ||
*/ | ||
addOption(option: Option): this; | ||
/** | ||
* Whether to store option values as properties on command object, | ||
@@ -354,5 +487,7 @@ * or store separately (specify false). In both cases the option values can be accessed using .opts(). | ||
* | ||
* When listener(s) are available for the helpLongFlag | ||
* those callbacks are invoked. | ||
* Outputs built-in help, and custom text added using `.addHelpText()`. | ||
* | ||
*/ | ||
outputHelp(context?: HelpContext): void; | ||
/** @deprecated since v7 */ | ||
outputHelp(cb?: (str: string) => string): void; | ||
@@ -374,14 +509,21 @@ | ||
* Output help information and exit. | ||
* | ||
* Outputs built-in help, and custom text added using `.addHelpText()`. | ||
*/ | ||
help(context?: HelpContext): never; | ||
/** @deprecated since v7 */ | ||
help(cb?: (str: string) => string): never; | ||
/** | ||
* Add a listener (callback) for when events occur. (Implemented using EventEmitter.) | ||
* Add additional text to be displayed with the built-in help. | ||
* | ||
* @example | ||
* program | ||
* .on('--help', () -> { | ||
* console.log('See web site for more information.'); | ||
* }); | ||
* Position is 'before' or 'after' to affect just this command, | ||
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. | ||
*/ | ||
addHelpText(position: AddHelpTextPosition, text: string): this; | ||
addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string | undefined): this; | ||
/** | ||
* Add a listener (callback) for when events occur. (Implemented using EventEmitter.) | ||
*/ | ||
on(event: string | symbol, listener: (...args: any[]) => void): this; | ||
@@ -392,5 +534,6 @@ } | ||
interface CommandOptions { | ||
noHelp?: boolean; // old name for hidden | ||
hidden?: boolean; | ||
isDefault?: boolean; | ||
/** @deprecated since v7, replaced by hidden */ | ||
noHelp?: boolean; | ||
} | ||
@@ -411,2 +554,3 @@ interface ExecutableCommandOptions extends CommandOptions { | ||
CommanderError: CommanderErrorConstructor; | ||
Help: HelpConstructor; | ||
} | ||
@@ -413,0 +557,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
130314
2354
853
2