UNPKG

@optique/core

Version:

Type-safe combinatorial command-line interface parser

194 lines (193 loc) 6.1 kB
//#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 };