meow-helper
Version:
Generate nicely formatted help text for meow CLI automatically.
201 lines • 9.39 kB
JavaScript
;
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