UNPKG

meow-helper

Version:

Generate nicely formatted help text for meow CLI automatically.

201 lines 9.39 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.arrify = void 0; const chalk_1 = __importDefault(require("chalk")); const dashify_1 = __importDefault(require("dashify")); const cliui = require("cliui"); // eslint-disable-line @typescript-eslint/no-var-requires const { cyan, yellow } = chalk_1.default; const TITLE_COLORS = { usage: chalk_1.default.bold.inverse.green, arguments: chalk_1.default.bold.inverse.cyan, options: chalk_1.default.bold.inverse.yellow, examples: chalk_1.default.bold.inverse.magenta, }; /** * Assures output is an array by converting sets and single valiues into array. * * @param input is the input. * @returns the array. */ function arrify(input) { if (input === undefined) return []; return Array.isArray(input) ? input : [input]; } exports.arrify = arrify; /** Checks whether any option has alias. */ function hasAnyAlias(flags) { return Object.values(flags).some((flag) => flag.alias !== undefined); } /** Checks whether any option has alias. */ function hasAnyRequiredArgs(args) { return Object.keys(args).some((arg) => arg.endsWith("*")); } /** Checks whether any args is required. */ function hasAnyRequiredFlags(flags) { return Object.values(flags).some((flag) => flag.isRequired === true); } /** Checks whether any option is required. */ function hasAnyMulitple(flags) { return Object.values(flags).some((flag) => flag.isMultiple); } /** Get maximum length of option name, default value length and argument name. */ function getMaxLengths({ flags, args, }) { const requiredSpace = hasAnyRequiredFlags(flags) ? 1 : 0; // Length of asterisk. const multipleSpace = hasAnyMulitple(flags) ? 3 : 0; // Length of ... const space = requiredSpace + multipleSpace + 3; // Length of others + (name length = double dash + space = 3). const maxNameLength = Object.keys(flags).reduce((max, name) => (name.length + space > max ? name.length + space : max), 0); const maxDefaultLength = Object.values(flags).reduce((max, flag) => { const currentLength = flag.default ? flag.default.toString().length + 12 : 0; return currentLength > max ? currentLength : max; }, 1); const maxArgLength = Object.keys(args).reduce((max, name) => (name.length + 3 > max ? name.length + 3 : max), 0); return { maxNameLength, maxDefaultLength, maxArgLength, aliasLength: hasAnyAlias(flags) ? 3 : 0 }; } /** Return arg name colorized and between brackets (`<>`). If arg name ends with `...`, add `...` after bracket. Add red "*" to required args. */ function getArg(arg) { const required = arg.endsWith("*") ? chalk_1.default.red("*") : ""; arg = arg.replace("*", ""); // eslint-disable-line no-param-reassign return cyan(arg.endsWith("...") ? `<${arg.replace(/...$/, "")}>...` : `<${arg}>`) + required; } /** Colorize command if string starts with command. */ function colorizeCommand(text, options) { return text.replace(new RegExp(`^(${options.command})`), chalk_1.default `{dim $} {green $1}`); } /** Add title to help text. */ function addTitle(title, options) { const prefixSpace = " ".repeat(Math.ceil((options.titleLength - title.length) / 2)); // Unicode Character “ ” (U+00A0) No-Break-Space const suffixSpace = " ".repeat(options.titleLength - title.length - prefixSpace.length); // Unicode Character “ ” (U+00A0) No-Break-Space const titleString = TITLE_COLORS[title](`${prefixSpace}${title.toUpperCase()}${suffixSpace}`); options.ui.div({ text: titleString, padding: [1, 0, 1, 0] }); } /** Add usage to help text. */ function addUsage(options) { const usage = arrify(options.usage); addTitle("usage", options); if (usage.length > 0) usage.forEach((line) => options.ui.div(colorizeCommand(line, options))); else { const flagsText = options.flags && Object.keys(options.flags).length > 0 ? `${yellow("[options]")} ` : ""; const argsText = options.args && Object.keys(options.args).length > 0 ? Object.keys(options.args).map(getArg) : ""; options.ui.div(chalk_1.default `{dim $} {green ${options.command}} ${flagsText}${argsText}`); } } /** Add examples to help text. */ function addExamples(options) { const examples = arrify(options.examples); if (examples.length > 0) { addTitle("examples", options); examples.forEach((line) => options.ui.div(colorizeCommand(line, options))); } } /** Add command arguments to help text. */ function addArguments(maxArgLength, options) { if (!options.args || Object.keys(options.args).length === 0) return; addTitle("arguments", options); Object.entries(options.args).forEach(([argName, desc]) => { options.ui.div({ text: getArg(argName), width: maxArgLength }, { text: desc }); }); } function addGroup(flagName, options, index) { const group = options.groups[flagName]; options.ui.div({ text: chalk_1.default.yellow.dim(`${group.title}:`), padding: [index === 0 ? 0 : 1, 0, 0, 0] }); if (group.description) options.ui.div({ text: chalk_1.default.yellow.dim(group.description) }); } /** Add command options to help text. */ function addOptions(maxNameLength, maxDefaultLength, aliasLength, options) { const { flags } = options; if (!flags || Object.keys(flags).length === 0) return; const singleRow = options.lineLength - (maxNameLength + maxDefaultLength + aliasLength) > options.multilineThreshold; addTitle("options", options); Object.entries(flags).forEach(([flagName, flag], i) => { if (options.groups[flagName]) addGroup(flagName, options, i); const required = flag.isRequired === true ? chalk_1.default.red("*") : ""; const multiple = flag.isMultiple ? "..." : ""; const defaultValue = flag.default ? chalk_1.default `{dim (Default: }{yellow ${flag.default}}{dim )}` : ""; const aliasColumn = { text: flag.alias && flag.alias.length === 1 ? chalk_1.default `{yellow -${flag.alias}}` : "", width: 3 }; const nameColumn = { text: chalk_1.default `{yellow --${flagName}${multiple}}${required}`, width: maxNameLength }; const firstRow = singleRow ? [aliasColumn, nameColumn, { text: defaultValue, width: maxDefaultLength }, { text: flag.desc }] : [aliasColumn, nameColumn, { text: defaultValue, align: "right" }]; if (aliasLength === 0) firstRow.shift(); options.ui.div(...firstRow); if (!singleRow) options.ui.div({ text: flag.desc, padding: [0, 0, 0, 5] }); }); } /** * Generate help text for meow. * * @param helpOptions are options * @example * const flags: ExtendedFlags = { cwd: { alias: "c", type: "string", desc: "Current CWD." }, ...commonFlags }; * const args = { path: "Path of file." }; * * meow(getHelp({flags, args, pkg}), { flags, pkg, allowUnknownFlags: false }); */ // export default function getHelp(helpOptions: HelpOptions): string { function getHelp(helpOptions) { const lineLength = helpOptions.lineLength || 1000; const ui = cliui({ width: lineLength }); const options = { lineLength, titleLength: 15, pkg: {}, command: helpOptions.pkg && helpOptions.pkg.name, description: helpOptions.pkg && helpOptions.pkg.description, usage: [], args: {}, flags: {}, examples: [], multilineThreshold: 50, autoHelp: true, notThrow: true, groups: {}, ...helpOptions, ui, }; if (!options.command) throw new Error("Either 'command' or 'pkg' with name is required."); const { maxNameLength, maxDefaultLength, maxArgLength, aliasLength } = getMaxLengths(options); // meow autoHelp shows description from package.json automatically. if (!options.autoHelp) { ui.div({ text: chalk_1.default `{bold.green ${options.command}}`, padding: [1, 0, 0, 0] }); if (options.description) options.ui.div({ text: `${options.description}`, padding: [1, 0, 0, 0] }); } // TODO: TEMP WORKAROUND FOR https://github.com/sindresorhus/meow/issues/178 // Add alias to all non-aliased flags. Remove this after a solution found. Object.entries(options.flags) .filter(([name, { alias }]) => alias === undefined && name !== dashify_1.default(name)) .forEach(([name, flag]) => (flag.alias = dashify_1.default(name))); // eslint-disable-line addUsage(options); addArguments(maxArgLength, options); addOptions(maxNameLength, maxDefaultLength, aliasLength, options); if (hasAnyRequiredFlags(options.flags) || hasAnyRequiredArgs(options.args)) options.ui.div({ text: chalk_1.default `{red *} Required field.`, padding: [1, 0, 0, 0] }); addExamples(options); ui.div(""); const help = options.ui.toString(); if (options.notThrow) { /* istanbul ignore next */ process.on("exit", (code) => { if (code === 2) { console.log(help); // eslint-disable-line no-console process.exit(0); } process.exit(code); }); } return help; } exports.default = getHelp; //# sourceMappingURL=get-help.js.map