@oclif/core
Version:
base library for oclif CLIs
338 lines (337 loc) • 14.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandHelp = void 0;
const ansis_1 = __importDefault(require("ansis"));
const ensure_arg_object_1 = require("../util/ensure-arg-object");
const ids_1 = require("../util/ids");
const util_1 = require("../util/util");
const theme_1 = require("../ux/theme");
const docopts_1 = require("./docopts");
const formatter_1 = require("./formatter");
// Don't use os.EOL because we need to ensure that a string
// written on any platform, that may use \r\n or \n, will be
// split on any platform, not just the os specific EOL at runtime.
const POSSIBLE_LINE_FEED = /\r\n|\n/;
/**
* Determines the sort order of flags. Will default to alphabetical if not set or set to an invalid value.
*/
function determineSortOrder(flagSortOrder) {
if (flagSortOrder === 'alphabetical')
return 'alphabetical';
if (flagSortOrder === 'none')
return 'none';
return 'alphabetical';
}
class CommandHelp extends formatter_1.HelpFormatter {
command;
config;
opts;
constructor(command, config, opts) {
super(config, opts);
this.command = command;
this.config = config;
this.opts = opts;
}
aliases(aliases) {
if (!aliases || aliases.length === 0)
return;
const body = aliases
.map((a) => [
(0, theme_1.colorize)(this.config?.theme?.dollarSign, '$'),
(0, theme_1.colorize)(this.config?.theme?.bin, this.config.bin),
(0, theme_1.colorize)(this.config?.theme?.alias, a),
].join(' '))
.join('\n');
return body;
}
arg(arg) {
const name = arg.name.toUpperCase();
if (arg.required)
return `${name}`;
return `[${name}]`;
}
args(args) {
if (args.filter((a) => a.description).length === 0)
return;
return args.map((a) => {
// Add ellipsis to indicate that the argument takes multiple values if strict is false
const name = this.command.strict === false ? `${a.name.toUpperCase()}...` : a.name.toUpperCase();
let description = a.description || '';
if (a.default)
description = `${(0, theme_1.colorize)(this.config?.theme?.flagDefaultValue, `[default: ${a.default}]`)} ${description}`;
if (a.options)
description = `${(0, theme_1.colorize)(this.config?.theme?.flagOptions, `(${a.options.join('|')})`)} ${description}`;
return [
(0, theme_1.colorize)(this.config?.theme?.flag, name),
description ? (0, theme_1.colorize)(this.config?.theme?.sectionDescription, description) : undefined,
];
});
}
defaultUsage() {
// Docopts by default
if (this.opts.docopts === undefined || this.opts.docopts) {
return docopts_1.DocOpts.generate(this.command);
}
return (0, util_1.compact)([
this.command.id,
Object.values(this.command.args ?? {})
?.filter((a) => !a.hidden)
.map((a) => this.arg(a))
.join(' '),
]).join(' ');
}
description() {
const cmd = this.command;
let description;
if (this.opts.hideCommandSummaryInDescription) {
description = (cmd.description || '').split(POSSIBLE_LINE_FEED).slice(1);
}
else if (cmd.description) {
const summary = cmd.summary ? `${cmd.summary}\n` : null;
description = summary
? [...summary.split(POSSIBLE_LINE_FEED), ...(cmd.description || '').split(POSSIBLE_LINE_FEED)]
: (cmd.description || '').split(POSSIBLE_LINE_FEED);
}
if (description) {
return this.wrap(description.join('\n'));
}
}
examples(examples) {
if (!examples || examples.length === 0)
return;
const body = (0, util_1.castArray)(examples)
.map((a) => {
let description;
let commands;
if (typeof a === 'string') {
const lines = a.split(POSSIBLE_LINE_FEED).filter(Boolean);
// If the example is <description>\n<command> then format correctly
if (lines.length >= 2 && !this.isCommand(lines[0]) && lines.slice(1).every((i) => this.isCommand(i))) {
description = lines[0];
commands = lines.slice(1);
}
else {
return lines.map((line) => this.formatIfCommand(line)).join('\n');
}
}
else {
description = a.description;
commands = [a.command];
}
const multilineSeparator = this.config.platform === 'win32' ? (this.config.shell.includes('powershell') ? '`' : '^') : '\\';
// The command will be indented in the section, which is also indented
const finalIndentedSpacing = this.indentSpacing * 2;
const multilineCommands = commands
.map((c) =>
// First indent keeping room for escaped newlines
this.indent(this.wrap(this.formatIfCommand(c), finalIndentedSpacing + 4))
// Then add the escaped newline
.split(POSSIBLE_LINE_FEED)
.join(` ${multilineSeparator}\n `))
.join('\n');
return `${this.wrap(description, finalIndentedSpacing)}\n\n${multilineCommands}`;
})
.join('\n\n');
return body;
}
flagHelpLabel(flag, showOptions = false) {
let label = flag.helpLabel;
if (!label) {
const labels = [];
labels.push(flag.char ? `-${flag.char[0]}` : ' ');
if (flag.name) {
if (flag.type === 'boolean' && flag.allowNo) {
labels.push(`--[no-]${flag.name.trim()}`);
}
else {
labels.push(`--${flag.name.trim()}`);
}
}
label = labels.join(flag.char ? (0, theme_1.colorize)(this.config?.theme?.flagSeparator, ', ') : ' ');
}
if (flag.type === 'option') {
let value = docopts_1.DocOpts.formatUsageType(flag, this.opts.showFlagNameInTitle ?? false, this.opts.showFlagOptionsInTitle ?? showOptions);
if (!value.includes('|'))
value = (0, theme_1.colorize)('underline', value);
label += `=${value}`;
}
return (0, theme_1.colorize)(this.config.theme?.flag, label);
}
flags(flags) {
if (flags.length === 0)
return;
const noChar = flags.reduce((previous, current) => previous && current.char === undefined, true);
// eslint-disable-next-line complexity
return flags.map((flag) => {
let left = this.flagHelpLabel(flag);
if (noChar)
left = left.replace(' ', '');
let right = flag.summary || flag.description || '';
const canBeCached = !(this.opts.respectNoCacheDefault === true && flag.noCacheDefault === true);
if (flag.type === 'option' && flag.default && canBeCached) {
right = `${(0, theme_1.colorize)(this.config?.theme?.flagDefaultValue, `[default: ${flag.default}]`)} ${right}`;
}
if (flag.required)
right = `${(0, theme_1.colorize)(this.config?.theme?.flagRequired, '(required)')} ${right}`;
if (flag.type === 'option' && flag.options && !flag.helpValue && !this.opts.showFlagOptionsInTitle) {
right += (0, theme_1.colorize)(this.config?.theme?.flagOptions, `\n<options: ${flag.options.join('|')}>`);
}
return [left, (0, theme_1.colorize)(this.config?.theme?.sectionDescription, right.trim())];
});
}
flagsDescriptions(flags) {
const flagsWithExtendedDescriptions = flags.filter((flag) => flag.summary && flag.description);
if (flagsWithExtendedDescriptions.length === 0)
return;
const body = flagsWithExtendedDescriptions
.map((flag) => {
// Guaranteed to be set because of the filter above, but make ts happy
const summary = flag.summary || '';
let flagHelp = this.flagHelpLabel(flag, true);
if (!flag.char)
flagHelp = flagHelp.replace(' ', '');
flagHelp +=
flagHelp.length + summary.length + 2 < this.opts.maxWidth
? ' ' + summary
: '\n\n' + this.indent(this.wrap(summary, this.indentSpacing * 2));
return `${flagHelp}\n\n${this.indent(this.wrap(flag.description || '', this.indentSpacing * 2))}`;
})
.join('\n\n');
return body;
}
generate() {
const cmd = this.command;
const unsortedFlags = Object.entries(cmd.flags || {})
.filter(([, v]) => !v.hidden)
.map(([k, v]) => {
v.name = k;
return v;
});
const flags = determineSortOrder(this.opts.flagSortOrder) === 'alphabetical'
? (0, util_1.sortBy)(unsortedFlags, (f) => [!f.char, f.char, f.name])
: unsortedFlags;
const args = Object.values((0, ensure_arg_object_1.ensureArgObject)(cmd.args)).filter((a) => !a.hidden);
const output = (0, util_1.compact)(this.sections().map(({ generate, header }) => {
const body = generate({ args, cmd, flags }, header);
// Generate can return a list of sections
if (Array.isArray(body)) {
return body
.map((helpSection) => helpSection && helpSection.body && this.section(helpSection.header, helpSection.body))
.join('\n\n');
}
return body && this.section(header, body);
})).join('\n\n');
return output;
}
groupFlags(flags) {
const mainFlags = [];
const flagGroups = {};
for (const flag of flags) {
const group = flag.helpGroup;
if (group) {
if (!flagGroups[group])
flagGroups[group] = [];
flagGroups[group].push(flag);
}
else {
mainFlags.push(flag);
}
}
return { flagGroups, mainFlags };
}
sections() {
const sections = [
{
generate: () => this.usage(),
header: this.opts.usageHeader || 'USAGE',
},
{
generate: ({ args }, header) => [{ body: this.args(args), header }],
header: 'ARGUMENTS',
},
{
generate: ({ flags }, header) => {
const { flagGroups, mainFlags } = this.groupFlags(flags);
const flagSections = [];
const mainFlagBody = this.flags(mainFlags);
if (mainFlagBody)
flagSections.push({ body: mainFlagBody, header });
for (const [name, flags] of Object.entries(flagGroups)) {
const body = this.flags(flags);
if (body)
flagSections.push({ body, header: `${name.toUpperCase()} ${header}` });
}
return (0, util_1.compact)(flagSections);
},
header: 'FLAGS',
},
{
generate: () => this.description(),
header: 'DESCRIPTION',
},
{
generate: ({ cmd }) => this.aliases(cmd.aliases),
header: 'ALIASES',
},
{
generate: ({ cmd }) => {
const examples = cmd.examples || cmd.example;
return this.examples(examples);
},
header: 'EXAMPLES',
},
{
generate: ({ flags }) => this.flagsDescriptions(flags),
header: 'FLAG DESCRIPTIONS',
},
];
const allowedSections = this.opts.sections?.map((s) => s.toLowerCase());
return sections.filter(({ header }) => !allowedSections || allowedSections.includes(header.toLowerCase()));
}
usage() {
const { id, usage } = this.command;
const standardId = (0, ids_1.toStandardizedId)(id, this.config);
const configuredId = (0, ids_1.toConfiguredId)(id, this.config);
const body = (usage ? (0, util_1.castArray)(usage) : [this.defaultUsage()])
.map((u) => {
const allowedSpacing = this.opts.maxWidth - this.indentSpacing;
const dollarSign = (0, theme_1.colorize)(this.config?.theme?.dollarSign, '$');
const bin = (0, theme_1.colorize)(this.config?.theme?.bin, this.config.bin);
const command = (0, theme_1.colorize)(this.config?.theme?.command, '<%= command.id %>');
const commandDescription = (0, theme_1.colorize)(this.config?.theme?.sectionDescription, u
.replace('<%= command.id %>', '')
.replace(new RegExp(`^${standardId}`), '')
.replace(new RegExp(`^${configuredId}`), '')
.trim());
const line = `${dollarSign} ${bin} ${command} ${commandDescription}`.trim();
if (line.length > allowedSpacing) {
const splitIndex = line.slice(0, Math.max(0, allowedSpacing)).lastIndexOf(' ');
return (line.slice(0, Math.max(0, splitIndex)) +
'\n' +
this.indent(this.wrap(line.slice(Math.max(0, splitIndex)), this.indentSpacing * 2)));
}
return this.wrap(line);
})
.join('\n');
return body;
}
formatIfCommand(example) {
example = this.render(example);
const dollarSign = (0, theme_1.colorize)(this.config?.theme?.dollarSign, '$');
if (example.startsWith(this.config.bin))
return `${dollarSign} ${example}`;
if (example.startsWith(`$ ${this.config.bin}`))
return `${dollarSign}${example.replace(`$`, '')}`;
return example;
}
isCommand(example) {
return ansis_1.default
.strip(this.formatIfCommand(example))
.startsWith(`${(0, theme_1.colorize)(this.config?.theme?.dollarSign, '$')} ${this.config.bin}`);
}
}
exports.CommandHelp = CommandHelp;
exports.default = CommandHelp;