@optique/core
Version:
Type-safe combinatorial command-line interface parser
194 lines (193 loc) • 6.1 kB
JavaScript
//#region src/message.ts
/**
* Creates a structured message with template strings and values.
*
* This function allows creating messages where specific values can be
* highlighted or styled differently when displayed to the user.
*
* @example
* ```typescript
* const error = message`Expected number between ${min} and ${max}, got ${value}`;
* const concat = message`${optionName("--age")}: ${error}`;
* ```
*
* @param message Template strings array (from template literal).
* @param values Values to be interpolated into the template.
* @returns A structured Message object.
*/
function message(message$1, ...values$1) {
const messageTerms = [];
for (let i = 0; i < message$1.length; i++) {
if (message$1[i] !== "") messageTerms.push({
type: "text",
text: message$1[i]
});
if (i >= values$1.length) continue;
const value$1 = values$1[i];
if (typeof value$1 === "string") messageTerms.push({
type: "value",
value: value$1
});
else if (Array.isArray(value$1)) messageTerms.push(...value$1);
else if (typeof value$1 === "object" && value$1 != null && "type" in value$1) messageTerms.push(value$1);
else throw new TypeError(`Invalid value type in message: ${typeof value$1}.`);
}
return messageTerms;
}
/**
* Creates a {@link MessageTerm} for plain text. Usually used for
* dynamically generated messages.
* @param text The plain text to be included in the message.
* @returns A {@link MessageTerm} representing the plain text.
*/
function text(text$1) {
return {
type: "text",
text: text$1
};
}
/**
* Creates a {@link MessageTerm} for an option name.
* @param name The name of the option, which can be a short or long option name.
* For example, `"-f"` or `"--foo"`.
* @returns A {@link MessageTerm} representing the option name.
*/
function optionName(name) {
return {
type: "optionName",
optionName: name
};
}
/**
* Creates a {@link MessageTerm} for a list of option names.
* @param names The list of option names, which can include both short and long
* option names. For example, `["--foo", "--bar"]`.
* @returns A {@link MessageTerm} representing the list of option names.
*/
function optionNames(names) {
return {
type: "optionNames",
optionNames: names
};
}
/**
* Creates a {@link MessageTerm} for a metavariable.
* @param metavar The metavariable name, which is a string that represents
* a variable in the message. For example, `"VALUE"` or
* `"ARG"`.
* @returns A {@link MessageTerm} representing the metavariable.
*/
function metavar(metavar$1) {
return {
type: "metavar",
metavar: metavar$1
};
}
/**
* Creates a {@link MessageTerm} for a single value. However, you usually
* don't need to use this function directly, as {@link message} string template
* will automatically create a {@link MessageTerm} for a value when
* you use a string in a template literal.
* @param value The value, which can be any string representation of a value.
* For example, `"42"` or `"hello"`.
* @returns A {@link MessageTerm} representing the value.
*/
function value(value$1) {
return {
type: "value",
value: value$1
};
}
/**
* Creates a {@link MessageTerm} for a list of consecutive values.
* @param values The list of consecutive values, which can include multiple
* string representations of consecutive values.
* For example, `["42", "hello"]`.
* @returns A {@link MessageTerm} representing the list of values.
*/
function values(values$1) {
return {
type: "values",
values: values$1
};
}
/**
* Formats a {@link Message} into a human-readable string for
* the terminal.
* @param msg The message to format, which is an array of
* {@link MessageTerm} objects.
* @param options Optional formatting options to customize the output.
* @returns A formatted string representation of the message.
*/
function formatMessage(msg, options = {}) {
const useColors = options.colors ?? false;
const useQuotes = options.quotes ?? true;
function* stream() {
const wordPattern = /\s*\S+\s*/g;
for (const term of msg) if (term.type === "text") while (true) {
const match = wordPattern.exec(term.text);
if (match == null) break;
yield {
text: match[0],
width: match[0].length
};
}
else if (term.type === "optionName") {
const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
yield {
text: useColors ? `\x1b[3m${name}\x1b[0m` : name,
width: name.length
};
} else if (term.type === "optionNames") {
const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
let i = 0;
for (const name of names) {
if (i > 0) yield {
text: "/",
width: 1
};
yield {
text: useColors ? `\x1b[3m${name}\x1b[0m` : name,
width: name.length
};
i++;
}
} else if (term.type === "metavar") {
const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
yield {
text: useColors ? `\x1b[1m${metavar$1}\x1b[0m` : metavar$1,
width: metavar$1.length
};
} else if (term.type === "value") {
const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
yield {
text: useColors ? `\x1b[32m${value$1}\x1b[0m` : value$1,
width: value$1.length
};
} else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
if (i > 0) yield {
text: " ",
width: 1
};
const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
yield {
text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}\x1b[0m` : value$1 : value$1,
width: value$1.length
};
}
else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
}
let output = "";
let totalWidth = 0;
for (const { text: text$1, width } of stream()) {
if (options.maxWidth != null && totalWidth + width > options.maxWidth) {
output += "\n";
totalWidth = 0;
}
output += text$1;
totalWidth += width;
}
return output;
}
//#endregion
export { formatMessage, message, metavar, optionName, optionNames, text, value, values };