mirror of
https://github.com/chenasraf/massarg.git
synced 2026-05-18 01:39:05 +00:00
feat: global column width
This commit is contained in:
137
src/help.ts
137
src/help.ts
@@ -10,18 +10,31 @@ import {
|
|||||||
} from './option'
|
} from './option'
|
||||||
|
|
||||||
export const GenerateTableCommandConfig = z.object({
|
export const GenerateTableCommandConfig = z.object({
|
||||||
|
/** Length of each row in the table */
|
||||||
lineLength: z.number().optional(),
|
lineLength: z.number().optional(),
|
||||||
|
/** When `false`, each row is separated by a blank line */
|
||||||
compact: z.boolean().optional(),
|
compact: z.boolean().optional(),
|
||||||
|
/** Style of the command/option name */
|
||||||
nameStyle: StringStyle.optional(),
|
nameStyle: StringStyle.optional(),
|
||||||
|
/** Style of the command/option description */
|
||||||
descriptionStyle: StringStyle.optional(),
|
descriptionStyle: StringStyle.optional(),
|
||||||
|
/** Prefix for the command/option name (default is the command's prefix) */
|
||||||
namePrefix: z.string().optional(),
|
namePrefix: z.string().optional(),
|
||||||
|
/** Prefix for the command/option aliases (default is the command's prefix) */
|
||||||
aliasPrefix: z.string().optional(),
|
aliasPrefix: z.string().optional(),
|
||||||
negatePrefix: z.string().optional(),
|
|
||||||
negateAliasPrefix: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
export type GenerateTableCommandConfig = z.infer<typeof GenerateTableCommandConfig>
|
export type GenerateTableCommandConfig = z.infer<typeof GenerateTableCommandConfig>
|
||||||
|
|
||||||
export const GenerateTableOptionConfig = GenerateTableCommandConfig
|
export const GenerateTableOptionConfig = GenerateTableCommandConfig.merge(
|
||||||
|
z.object({
|
||||||
|
/** Prefix for the command/option negations (default is the command's prefix) */
|
||||||
|
negatePrefix: z.string().optional(),
|
||||||
|
/** Prefix for the command/option negation aliases (default is the command's prefix) */
|
||||||
|
negateAliasPrefix: z.string().optional(),
|
||||||
|
/** Whether to display negations with each option name */
|
||||||
|
displayNegations: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
export type GenerateTableOptionConfig = z.infer<typeof GenerateTableOptionConfig>
|
export type GenerateTableOptionConfig = z.infer<typeof GenerateTableOptionConfig>
|
||||||
|
|
||||||
export const HelpConfig = z.object({
|
export const HelpConfig = z.object({
|
||||||
@@ -38,6 +51,9 @@ export const HelpConfig = z.object({
|
|||||||
*/
|
*/
|
||||||
bindOption: z.boolean().optional(),
|
bindOption: z.boolean().optional(),
|
||||||
|
|
||||||
|
/** Whether to align all tables to the column widths, or have each table be independent. Default is `true` */
|
||||||
|
useGlobalTableColumns: z.boolean().default(true).optional(),
|
||||||
|
|
||||||
/** Options for generating the table of commands */
|
/** Options for generating the table of commands */
|
||||||
commandOptions: GenerateTableCommandConfig.omit({ lineLength: true }).optional(),
|
commandOptions: GenerateTableCommandConfig.omit({ lineLength: true }).optional(),
|
||||||
/** Options for generating the table of options */
|
/** Options for generating the table of options */
|
||||||
@@ -83,6 +99,7 @@ export type HelpConfig = z.infer<typeof HelpConfig>
|
|||||||
|
|
||||||
export const defaultHelpConfig: DeepRequired<HelpConfig> = {
|
export const defaultHelpConfig: DeepRequired<HelpConfig> = {
|
||||||
lineLength: 80,
|
lineLength: 80,
|
||||||
|
useGlobalTableColumns: true,
|
||||||
commandOptions: {
|
commandOptions: {
|
||||||
nameStyle: {
|
nameStyle: {
|
||||||
color: 'yellow',
|
color: 'yellow',
|
||||||
@@ -96,6 +113,7 @@ export const defaultHelpConfig: DeepRequired<HelpConfig> = {
|
|||||||
aliasPrefix: OPT_SHORT_PREFIX,
|
aliasPrefix: OPT_SHORT_PREFIX,
|
||||||
negatePrefix: NEGATE_FULL_PREFIX,
|
negatePrefix: NEGATE_FULL_PREFIX,
|
||||||
negateAliasPrefix: NEGATE_SHORT_PREFIX,
|
negateAliasPrefix: NEGATE_SHORT_PREFIX,
|
||||||
|
displayNegations: false,
|
||||||
nameStyle: {
|
nameStyle: {
|
||||||
color: 'yellow',
|
color: 'yellow',
|
||||||
},
|
},
|
||||||
@@ -160,14 +178,23 @@ export class HelpGenerator {
|
|||||||
const entry = this.entry
|
const entry = this.entry
|
||||||
const CMD_OPT_INDENT = 4
|
const CMD_OPT_INDENT = 4
|
||||||
const _wrap = (text: string, indent = 0) => wrap(text, this.config.lineLength - indent)
|
const _wrap = (text: string, indent = 0) => wrap(text, this.config.lineLength - indent)
|
||||||
const options = generateHelpTable(entry.options, {
|
const optionOptions = {
|
||||||
...this.config.optionOptions,
|
...this.config.optionOptions,
|
||||||
lineLength: this.config.lineLength - CMD_OPT_INDENT,
|
lineLength: this.config.lineLength - CMD_OPT_INDENT,
|
||||||
}).trimEnd()
|
}
|
||||||
const commands = generateHelpTable(entry.commands, {
|
const commandOptions = {
|
||||||
...this.config.commandOptions,
|
...this.config.commandOptions,
|
||||||
|
displayNegations: false,
|
||||||
lineLength: this.config.lineLength - CMD_OPT_INDENT,
|
lineLength: this.config.lineLength - CMD_OPT_INDENT,
|
||||||
}).trimEnd()
|
}
|
||||||
|
const maxNameLength = this.config.useGlobalTableColumns
|
||||||
|
? Math.max(
|
||||||
|
getMaxNameLength(entry.options.map((e) => getItemDetails(e, optionOptions))),
|
||||||
|
getMaxNameLength(entry.commands.map((e) => getItemDetails(e, commandOptions))),
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
const options = generateHelpTable(entry.options, optionOptions, maxNameLength).trimEnd()
|
||||||
|
const commands = generateHelpTable(entry.commands, commandOptions, maxNameLength).trimEnd()
|
||||||
const examples = entry.examples
|
const examples = entry.examples
|
||||||
.map((example) => {
|
.map((example) => {
|
||||||
const { description, input, output } = example
|
const { description, input, output } = example
|
||||||
@@ -266,46 +293,80 @@ function wrap(text: string, lineLength: number): string {
|
|||||||
return subRows.join('\n')
|
return subRows.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateHelpTable<T extends Partial<GenerateTableCommandConfig>>(
|
type ParsedHelpItem = {
|
||||||
items: HelpItem[],
|
name: string
|
||||||
{
|
description: string
|
||||||
lineLength: lineLength = 80,
|
hidden: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxNameLength = (items: ParsedHelpItem[]): number =>
|
||||||
|
Math.max(...items.map((o) => o.name.length))
|
||||||
|
|
||||||
|
function getItemDetails(
|
||||||
|
o: HelpItem,
|
||||||
|
options?: Pick<
|
||||||
|
GenerateTableOptionConfig & GenerateTableOptionConfig,
|
||||||
|
'displayNegations' | 'namePrefix' | 'aliasPrefix' | 'negatePrefix' | 'negateAliasPrefix'
|
||||||
|
>,
|
||||||
|
): ParsedHelpItem {
|
||||||
|
const {
|
||||||
|
displayNegations = false,
|
||||||
namePrefix = '',
|
namePrefix = '',
|
||||||
negatePrefix = '',
|
negatePrefix = '',
|
||||||
aliasPrefix = '',
|
aliasPrefix = '',
|
||||||
negateAliasPrefix: aliasNegatePrefix = '',
|
negateAliasPrefix = '',
|
||||||
|
} = options ?? {}
|
||||||
|
const cmdNames = {
|
||||||
|
full: `${namePrefix}${o.name}`,
|
||||||
|
fullNegated: negatePrefix ? `${negatePrefix}${o.name}` : undefined,
|
||||||
|
aliases: o.aliases.map((a) => `${aliasPrefix}${a}`).join(' | '),
|
||||||
|
aliasesNegated: negatePrefix
|
||||||
|
? o.aliases.map((a) => `${negateAliasPrefix}${a}`).join(' | ')
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
const name = [
|
||||||
|
cmdNames.full,
|
||||||
|
cmdNames.aliases,
|
||||||
|
displayNegations && cmdNames.fullNegated,
|
||||||
|
displayNegations && cmdNames.aliasesNegated,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' | ')
|
||||||
|
const description = o.description
|
||||||
|
const hidden = o.hidden || false
|
||||||
|
return { name, description, hidden }
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateHelpTable<T extends GenerateTableCommandConfig | GenerateTableOptionConfig>(
|
||||||
|
items: HelpItem[],
|
||||||
|
fullConfig: Partial<T> = {},
|
||||||
|
maxNameLength?: number,
|
||||||
|
): string {
|
||||||
|
const {
|
||||||
|
lineLength = 80,
|
||||||
|
namePrefix = '',
|
||||||
|
aliasPrefix = '',
|
||||||
|
negatePrefix = '',
|
||||||
|
negateAliasPrefix = '',
|
||||||
|
displayNegations = false,
|
||||||
compact = false,
|
compact = false,
|
||||||
...config
|
...config
|
||||||
}: Partial<T> = {},
|
} = fullConfig as GenerateTableOptionConfig
|
||||||
): string {
|
|
||||||
const rows = items
|
const rows = items
|
||||||
.map((o) => {
|
.map((o) =>
|
||||||
const cmdNames = {
|
getItemDetails(o, {
|
||||||
full: `${namePrefix}${o.name}`,
|
namePrefix,
|
||||||
fullNegated: negatePrefix ? `${negatePrefix}${o.name}` : undefined,
|
aliasPrefix,
|
||||||
aliases: o.aliases.map((a) => `${aliasPrefix}${a}`).join(' | '),
|
negatePrefix,
|
||||||
aliasesNegated: negatePrefix
|
negateAliasPrefix,
|
||||||
? o.aliases.map((a) => `${aliasNegatePrefix}${a}`).join(' | ')
|
}),
|
||||||
: undefined,
|
)
|
||||||
}
|
|
||||||
const name = [
|
|
||||||
cmdNames.full,
|
|
||||||
cmdNames.aliases,
|
|
||||||
// cmdNames.fullNegated,
|
|
||||||
// cmdNames.aliasesNegated,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' | ')
|
|
||||||
const description = o.description
|
|
||||||
const hidden = o.hidden || false
|
|
||||||
return { name, description, hidden }
|
|
||||||
})
|
|
||||||
.filter((r) => !r.hidden)
|
.filter((r) => !r.hidden)
|
||||||
const maxNameLength = Math.max(...rows.map((o) => o.name.length))
|
maxNameLength ??= getMaxNameLength(rows)
|
||||||
const nameStyle = (name: string) => format(name, config.nameStyle)
|
const nameStyle = (name: string) => format(name, config.nameStyle)
|
||||||
const descStyle = (desc: string) => format(desc, config.descriptionStyle)
|
const descStyle = (desc: string) => format(desc, config.descriptionStyle)
|
||||||
const table = rows.map((row) => {
|
const table = rows.map((row) => {
|
||||||
const name = nameStyle(row.name.padEnd(maxNameLength + 2))
|
const name = nameStyle(row.name.padEnd(maxNameLength! + 2))
|
||||||
const description = descStyle(row.description)
|
const description = descStyle(row.description)
|
||||||
const length = stripStyle(name).length + stripStyle(description).length
|
const length = stripStyle(name).length + stripStyle(description).length
|
||||||
if (length <= lineLength) {
|
if (length <= lineLength) {
|
||||||
@@ -322,7 +383,7 @@ function generateHelpTable<T extends Partial<GenerateTableCommandConfig>>(
|
|||||||
for (const word of words) {
|
for (const word of words) {
|
||||||
if (stripStyle(currentRow).length + stripStyle(word).length + 1 > lineLength) {
|
if (stripStyle(currentRow).length + stripStyle(word).length + 1 > lineLength) {
|
||||||
subRows.push(currentRow)
|
subRows.push(currentRow)
|
||||||
currentRow = ' '.repeat(maxNameLength + 2)
|
currentRow = ' '.repeat(maxNameLength! + 2)
|
||||||
}
|
}
|
||||||
currentRow += `${word} `
|
currentRow += `${word} `
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user