|
const { humanReadableArgName } = require('./argument.js'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Help { |
|
constructor() { |
|
this.helpWidth = undefined; |
|
this.sortSubcommands = false; |
|
this.sortOptions = false; |
|
this.showGlobalOptions = false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, helper.subcommandTerm(command).length); |
|
}, 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
longestOptionTermLength(cmd, helper) { |
|
return helper.visibleOptions(cmd).reduce((max, option) => { |
|
return Math.max(max, helper.optionTerm(option).length); |
|
}, 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
longestGlobalOptionTermLength(cmd, helper) { |
|
return helper.visibleGlobalOptions(cmd).reduce((max, option) => { |
|
return Math.max(max, helper.optionTerm(option).length); |
|
}, 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
longestArgumentTermLength(cmd, helper) { |
|
return helper.visibleArguments(cmd).reduce((max, argument) => { |
|
return Math.max(max, helper.argumentTerm(argument).length); |
|
}, 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 extraDescripton = `(${extraInfo.join(', ')})`; |
|
if (argument.description) { |
|
return `${argument.description} ${extraDescripton}`; |
|
} |
|
return extraDescripton; |
|
} |
|
return argument.description; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
formatHelp(cmd, helper) { |
|
const termWidth = helper.padWidth(cmd, helper); |
|
const helpWidth = helper.helpWidth || 80; |
|
const itemIndentWidth = 2; |
|
const itemSeparatorWidth = 2; |
|
function formatItem(term, description) { |
|
if (description) { |
|
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`; |
|
return helper.wrap( |
|
fullText, |
|
helpWidth - itemIndentWidth, |
|
termWidth + itemSeparatorWidth, |
|
); |
|
} |
|
return term; |
|
} |
|
function formatList(textArray) { |
|
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth)); |
|
} |
|
|
|
|
|
let output = [`Usage: ${helper.commandUsage(cmd)}`, '']; |
|
|
|
|
|
const commandDescription = helper.commandDescription(cmd); |
|
if (commandDescription.length > 0) { |
|
output = output.concat([ |
|
helper.wrap(commandDescription, helpWidth, 0), |
|
'', |
|
]); |
|
} |
|
|
|
|
|
const argumentList = helper.visibleArguments(cmd).map((argument) => { |
|
return formatItem( |
|
helper.argumentTerm(argument), |
|
helper.argumentDescription(argument), |
|
); |
|
}); |
|
if (argumentList.length > 0) { |
|
output = output.concat(['Arguments:', formatList(argumentList), '']); |
|
} |
|
|
|
|
|
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), '']); |
|
} |
|
|
|
if (this.showGlobalOptions) { |
|
const globalOptionList = helper |
|
.visibleGlobalOptions(cmd) |
|
.map((option) => { |
|
return formatItem( |
|
helper.optionTerm(option), |
|
helper.optionDescription(option), |
|
); |
|
}); |
|
if (globalOptionList.length > 0) { |
|
output = output.concat([ |
|
'Global Options:', |
|
formatList(globalOptionList), |
|
'', |
|
]); |
|
} |
|
} |
|
|
|
|
|
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'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
padWidth(cmd, helper) { |
|
return Math.max( |
|
helper.longestOptionTermLength(cmd, helper), |
|
helper.longestGlobalOptionTermLength(cmd, helper), |
|
helper.longestSubcommandTermLength(cmd, helper), |
|
helper.longestArgumentTermLength(cmd, helper), |
|
); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wrap(str, width, indent, minColumnWidth = 40) { |
|
|
|
const indents = |
|
' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff'; |
|
|
|
const manualIndent = new RegExp(`[\\n][${indents}]+`); |
|
if (str.match(manualIndent)) return str; |
|
|
|
const columnWidth = width - indent; |
|
if (columnWidth < minColumnWidth) return str; |
|
|
|
const leadingStr = str.slice(0, indent); |
|
const columnText = str.slice(indent).replace('\r\n', '\n'); |
|
const indentString = ' '.repeat(indent); |
|
const zeroWidthSpace = '\u200B'; |
|
const breaks = `\\s${zeroWidthSpace}`; |
|
|
|
|
|
const regex = new RegExp( |
|
`\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, |
|
'g', |
|
); |
|
const lines = columnText.match(regex) || []; |
|
return ( |
|
leadingStr + |
|
lines |
|
.map((line, i) => { |
|
if (line === '\n') return ''; |
|
return (i > 0 ? indentString : '') + line.trimEnd(); |
|
}) |
|
.join('\n') |
|
); |
|
} |
|
} |
|
|
|
exports.Help = Help; |
|
|