| const { humanReadableArgName } = require('./argument.js'); |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| class Help { |
| constructor() { |
| this.helpWidth = undefined; |
| this.minWidthToWrap = 40; |
| this.sortSubcommands = false; |
| this.sortOptions = false; |
| this.showGlobalOptions = false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| prepareContext(contextOptions) { |
| this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| visibleCommands(cmd) { |
| const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden); |
| const helpCommand = cmd._getHelpCommand(); |
| if (helpCommand && !helpCommand._hidden) { |
| visibleCommands.push(helpCommand); |
| } |
| if (this.sortSubcommands) { |
| visibleCommands.sort((a, b) => { |
| |
| return a.name().localeCompare(b.name()); |
| }); |
| } |
| return visibleCommands; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| compareOptions(a, b) { |
| const getSortKey = (option) => { |
| |
| return option.short |
| ? option.short.replace(/^-/, '') |
| : option.long.replace(/^--/, ''); |
| }; |
| return getSortKey(a).localeCompare(getSortKey(b)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| visibleOptions(cmd) { |
| const visibleOptions = cmd.options.filter((option) => !option.hidden); |
| |
| const helpOption = cmd._getHelpOption(); |
| if (helpOption && !helpOption.hidden) { |
| |
| const removeShort = helpOption.short && cmd._findOption(helpOption.short); |
| const removeLong = helpOption.long && cmd._findOption(helpOption.long); |
| if (!removeShort && !removeLong) { |
| visibleOptions.push(helpOption); |
| } else if (helpOption.long && !removeLong) { |
| visibleOptions.push( |
| cmd.createOption(helpOption.long, helpOption.description), |
| ); |
| } else if (helpOption.short && !removeShort) { |
| visibleOptions.push( |
| cmd.createOption(helpOption.short, helpOption.description), |
| ); |
| } |
| } |
| if (this.sortOptions) { |
| visibleOptions.sort(this.compareOptions); |
| } |
| return visibleOptions; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| visibleGlobalOptions(cmd) { |
| if (!this.showGlobalOptions) return []; |
|
|
| const globalOptions = []; |
| for ( |
| let ancestorCmd = cmd.parent; |
| ancestorCmd; |
| ancestorCmd = ancestorCmd.parent |
| ) { |
| const visibleOptions = ancestorCmd.options.filter( |
| (option) => !option.hidden, |
| ); |
| globalOptions.push(...visibleOptions); |
| } |
| if (this.sortOptions) { |
| globalOptions.sort(this.compareOptions); |
| } |
| return globalOptions; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| visibleArguments(cmd) { |
| |
| if (cmd._argsDescription) { |
| cmd.registeredArguments.forEach((argument) => { |
| argument.description = |
| argument.description || cmd._argsDescription[argument.name()] || ''; |
| }); |
| } |
|
|
| |
| if (cmd.registeredArguments.find((argument) => argument.description)) { |
| return cmd.registeredArguments; |
| } |
| return []; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| subcommandTerm(cmd) { |
| |
| const args = cmd.registeredArguments |
| .map((arg) => humanReadableArgName(arg)) |
| .join(' '); |
| return ( |
| cmd._name + |
| (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') + |
| (cmd.options.length ? ' [options]' : '') + |
| (args ? ' ' + args : '') |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| optionTerm(option) { |
| return option.flags; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| argumentTerm(argument) { |
| return argument.name(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| longestSubcommandTermLength(cmd, helper) { |
| return helper.visibleCommands(cmd).reduce((max, command) => { |
| return Math.max( |
| max, |
| this.displayWidth( |
| helper.styleSubcommandTerm(helper.subcommandTerm(command)), |
| ), |
| ); |
| }, 0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| longestOptionTermLength(cmd, helper) { |
| return helper.visibleOptions(cmd).reduce((max, option) => { |
| return Math.max( |
| max, |
| this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))), |
| ); |
| }, 0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| longestGlobalOptionTermLength(cmd, helper) { |
| return helper.visibleGlobalOptions(cmd).reduce((max, option) => { |
| return Math.max( |
| max, |
| this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))), |
| ); |
| }, 0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| longestArgumentTermLength(cmd, helper) { |
| return helper.visibleArguments(cmd).reduce((max, argument) => { |
| return Math.max( |
| max, |
| this.displayWidth( |
| helper.styleArgumentTerm(helper.argumentTerm(argument)), |
| ), |
| ); |
| }, 0); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| commandUsage(cmd) { |
| |
| let cmdName = cmd._name; |
| if (cmd._aliases[0]) { |
| cmdName = cmdName + '|' + cmd._aliases[0]; |
| } |
| let ancestorCmdNames = ''; |
| for ( |
| let ancestorCmd = cmd.parent; |
| ancestorCmd; |
| ancestorCmd = ancestorCmd.parent |
| ) { |
| ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames; |
| } |
| return ancestorCmdNames + cmdName + ' ' + cmd.usage(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| commandDescription(cmd) { |
| |
| return cmd.description(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| subcommandDescription(cmd) { |
| |
| return cmd.summary() || cmd.description(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| optionDescription(option) { |
| const extraInfo = []; |
|
|
| if (option.argChoices) { |
| extraInfo.push( |
| |
| `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`, |
| ); |
| } |
| if (option.defaultValue !== undefined) { |
| |
| |
| const showDefault = |
| option.required || |
| option.optional || |
| (option.isBoolean() && typeof option.defaultValue === 'boolean'); |
| if (showDefault) { |
| extraInfo.push( |
| `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`, |
| ); |
| } |
| } |
| |
| if (option.presetArg !== undefined && option.optional) { |
| extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`); |
| } |
| if (option.envVar !== undefined) { |
| extraInfo.push(`env: ${option.envVar}`); |
| } |
| if (extraInfo.length > 0) { |
| return `${option.description} (${extraInfo.join(', ')})`; |
| } |
|
|
| return option.description; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| argumentDescription(argument) { |
| const extraInfo = []; |
| if (argument.argChoices) { |
| extraInfo.push( |
| |
| `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`, |
| ); |
| } |
| if (argument.defaultValue !== undefined) { |
| extraInfo.push( |
| `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`, |
| ); |
| } |
| if (extraInfo.length > 0) { |
| const extraDescription = `(${extraInfo.join(', ')})`; |
| if (argument.description) { |
| return `${argument.description} ${extraDescription}`; |
| } |
| return extraDescription; |
| } |
| return argument.description; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| formatHelp(cmd, helper) { |
| const termWidth = helper.padWidth(cmd, helper); |
| const helpWidth = helper.helpWidth ?? 80; |
|
|
| function callFormatItem(term, description) { |
| return helper.formatItem(term, termWidth, description, helper); |
| } |
|
|
| |
| let output = [ |
| `${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`, |
| '', |
| ]; |
|
|
| |
| const commandDescription = helper.commandDescription(cmd); |
| if (commandDescription.length > 0) { |
| output = output.concat([ |
| helper.boxWrap( |
| helper.styleCommandDescription(commandDescription), |
| helpWidth, |
| ), |
| '', |
| ]); |
| } |
|
|
| |
| const argumentList = helper.visibleArguments(cmd).map((argument) => { |
| return callFormatItem( |
| helper.styleArgumentTerm(helper.argumentTerm(argument)), |
| helper.styleArgumentDescription(helper.argumentDescription(argument)), |
| ); |
| }); |
| if (argumentList.length > 0) { |
| output = output.concat([ |
| helper.styleTitle('Arguments:'), |
| ...argumentList, |
| '', |
| ]); |
| } |
|
|
| |
| const optionList = helper.visibleOptions(cmd).map((option) => { |
| return callFormatItem( |
| helper.styleOptionTerm(helper.optionTerm(option)), |
| helper.styleOptionDescription(helper.optionDescription(option)), |
| ); |
| }); |
| if (optionList.length > 0) { |
| output = output.concat([ |
| helper.styleTitle('Options:'), |
| ...optionList, |
| '', |
| ]); |
| } |
|
|
| if (helper.showGlobalOptions) { |
| const globalOptionList = helper |
| .visibleGlobalOptions(cmd) |
| .map((option) => { |
| return callFormatItem( |
| helper.styleOptionTerm(helper.optionTerm(option)), |
| helper.styleOptionDescription(helper.optionDescription(option)), |
| ); |
| }); |
| if (globalOptionList.length > 0) { |
| output = output.concat([ |
| helper.styleTitle('Global Options:'), |
| ...globalOptionList, |
| '', |
| ]); |
| } |
| } |
|
|
| |
| const commandList = helper.visibleCommands(cmd).map((cmd) => { |
| return callFormatItem( |
| helper.styleSubcommandTerm(helper.subcommandTerm(cmd)), |
| helper.styleSubcommandDescription(helper.subcommandDescription(cmd)), |
| ); |
| }); |
| if (commandList.length > 0) { |
| output = output.concat([ |
| helper.styleTitle('Commands:'), |
| ...commandList, |
| '', |
| ]); |
| } |
|
|
| return output.join('\n'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| displayWidth(str) { |
| return stripColor(str).length; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| styleTitle(str) { |
| return str; |
| } |
|
|
| styleUsage(str) { |
| |
| |
| return str |
| .split(' ') |
| .map((word) => { |
| if (word === '[options]') return this.styleOptionText(word); |
| if (word === '[command]') return this.styleSubcommandText(word); |
| if (word[0] === '[' || word[0] === '<') |
| return this.styleArgumentText(word); |
| return this.styleCommandText(word); |
| }) |
| .join(' '); |
| } |
| styleCommandDescription(str) { |
| return this.styleDescriptionText(str); |
| } |
| styleOptionDescription(str) { |
| return this.styleDescriptionText(str); |
| } |
| styleSubcommandDescription(str) { |
| return this.styleDescriptionText(str); |
| } |
| styleArgumentDescription(str) { |
| return this.styleDescriptionText(str); |
| } |
| styleDescriptionText(str) { |
| return str; |
| } |
| styleOptionTerm(str) { |
| return this.styleOptionText(str); |
| } |
| styleSubcommandTerm(str) { |
| |
| |
| return str |
| .split(' ') |
| .map((word) => { |
| if (word === '[options]') return this.styleOptionText(word); |
| if (word[0] === '[' || word[0] === '<') |
| return this.styleArgumentText(word); |
| return this.styleSubcommandText(word); |
| }) |
| .join(' '); |
| } |
| styleArgumentTerm(str) { |
| return this.styleArgumentText(str); |
| } |
| styleOptionText(str) { |
| return str; |
| } |
| styleArgumentText(str) { |
| return str; |
| } |
| styleSubcommandText(str) { |
| return str; |
| } |
| styleCommandText(str) { |
| return str; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| padWidth(cmd, helper) { |
| return Math.max( |
| helper.longestOptionTermLength(cmd, helper), |
| helper.longestGlobalOptionTermLength(cmd, helper), |
| helper.longestSubcommandTermLength(cmd, helper), |
| helper.longestArgumentTermLength(cmd, helper), |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| preformatted(str) { |
| return /\n[^\S\r\n]/.test(str); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| formatItem(term, termWidth, description, helper) { |
| const itemIndent = 2; |
| const itemIndentStr = ' '.repeat(itemIndent); |
| if (!description) return itemIndentStr + term; |
|
|
| |
| const paddedTerm = term.padEnd( |
| termWidth + term.length - helper.displayWidth(term), |
| ); |
|
|
| |
| const spacerWidth = 2; |
| const helpWidth = this.helpWidth ?? 80; |
| const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent; |
| let formattedDescription; |
| if ( |
| remainingWidth < this.minWidthToWrap || |
| helper.preformatted(description) |
| ) { |
| formattedDescription = description; |
| } else { |
| const wrappedDescription = helper.boxWrap(description, remainingWidth); |
| formattedDescription = wrappedDescription.replace( |
| /\n/g, |
| '\n' + ' '.repeat(termWidth + spacerWidth), |
| ); |
| } |
|
|
| |
| return ( |
| itemIndentStr + |
| paddedTerm + |
| ' '.repeat(spacerWidth) + |
| formattedDescription.replace(/\n/g, `\n${itemIndentStr}`) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| boxWrap(str, width) { |
| if (width < this.minWidthToWrap) return str; |
|
|
| const rawLines = str.split(/\r\n|\n/); |
| |
| const chunkPattern = /[\s]*[^\s]+/g; |
| const wrappedLines = []; |
| rawLines.forEach((line) => { |
| const chunks = line.match(chunkPattern); |
| if (chunks === null) { |
| wrappedLines.push(''); |
| return; |
| } |
|
|
| let sumChunks = [chunks.shift()]; |
| let sumWidth = this.displayWidth(sumChunks[0]); |
| chunks.forEach((chunk) => { |
| const visibleWidth = this.displayWidth(chunk); |
| |
| if (sumWidth + visibleWidth <= width) { |
| sumChunks.push(chunk); |
| sumWidth += visibleWidth; |
| return; |
| } |
| wrappedLines.push(sumChunks.join('')); |
|
|
| const nextChunk = chunk.trimStart(); |
| sumChunks = [nextChunk]; |
| sumWidth = this.displayWidth(nextChunk); |
| }); |
| wrappedLines.push(sumChunks.join('')); |
| }); |
|
|
| return wrappedLines.join('\n'); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| function stripColor(str) { |
| |
| const sgrPattern = /\x1b\[\d*(;\d*)*m/g; |
| return str.replace(sgrPattern, ''); |
| } |
|
|
| exports.Help = Help; |
| exports.stripColor = stripColor; |
|
|