2025-05-04 14:29:13 +00:00
|
|
|
// ANSI Colors helper - provides nested [tag]…[/] parsing and code-block wrapping.
|
|
|
|
|
|
|
|
|
|
// ANSI color/style codes
|
|
|
|
|
const CODES = {
|
2025-05-08 01:52:12 +00:00
|
|
|
// text colors
|
|
|
|
|
gray: 30, red: 31, green: 32, yellow: 33,
|
|
|
|
|
blue: 34, pink: 35, cyan: 36, white: 37,
|
|
|
|
|
// background colors
|
|
|
|
|
bgGray: 40, bgOrange: 41, bgBlue: 42,
|
|
|
|
|
bgTurquoise: 43, bgFirefly: 44, bgIndigo: 45,
|
|
|
|
|
bgLightGray: 46, bgWhite: 47,
|
|
|
|
|
// styles
|
|
|
|
|
bold: 1, underline: 4,
|
|
|
|
|
// reset
|
|
|
|
|
reset: 0
|
2025-05-04 14:29:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Escape literal brackets so users can write \[ and \] without triggering tags.
|
|
|
|
|
*/
|
|
|
|
|
export function escapeBrackets(str) {
|
2025-05-08 01:52:12 +00:00
|
|
|
return str
|
|
|
|
|
.replace(/\\\[/g, '__ESC_LB__')
|
|
|
|
|
.replace(/\\\]/g, '__ESC_RB__');
|
2025-05-04 14:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Restore any escaped brackets after formatting. */
|
|
|
|
|
export function restoreBrackets(str) {
|
2025-05-08 01:52:12 +00:00
|
|
|
return str
|
|
|
|
|
.replace(/__ESC_LB__/g, '[')
|
|
|
|
|
.replace(/__ESC_RB__/g, ']');
|
2025-05-04 14:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parse nested [tag1,tag2]…[/] patterns into ANSI codes (stack-based).
|
|
|
|
|
*/
|
|
|
|
|
export function formatAnsi(input) {
|
2025-05-08 01:52:12 +00:00
|
|
|
const stack = [];
|
|
|
|
|
let output = '';
|
|
|
|
|
const pattern = /\[\/\]|\[([^\]]+)\]/g;
|
|
|
|
|
let lastIndex = 0;
|
|
|
|
|
let match;
|
2025-05-04 14:29:13 +00:00
|
|
|
|
2025-05-08 01:52:12 +00:00
|
|
|
while ((match = pattern.exec(input)) !== null) {
|
|
|
|
|
output += input.slice(lastIndex, match.index);
|
2025-05-04 14:29:13 +00:00
|
|
|
|
2025-05-08 01:52:12 +00:00
|
|
|
if (match[0] === '[/]') {
|
|
|
|
|
if (stack.length) stack.pop();
|
|
|
|
|
output += `\u001b[${CODES.reset}m`;
|
|
|
|
|
for (const tag of stack) {
|
|
|
|
|
const code = CODES[tag] ?? CODES.gray;
|
|
|
|
|
output += `\u001b[${code}m`;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const tags = match[1].split(/[,;\s]+/).filter(Boolean);
|
|
|
|
|
for (const tag of tags) {
|
|
|
|
|
stack.push(tag);
|
|
|
|
|
const code = CODES[tag] ?? CODES.gray;
|
|
|
|
|
output += `\u001b[${code}m`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-04 14:29:13 +00:00
|
|
|
|
2025-05-08 01:52:12 +00:00
|
|
|
lastIndex = pattern.lastIndex;
|
|
|
|
|
}
|
2025-05-04 14:29:13 +00:00
|
|
|
|
2025-05-08 01:52:12 +00:00
|
|
|
output += input.slice(lastIndex);
|
|
|
|
|
if (stack.length) output += `\u001b[${CODES.reset}m`;
|
|
|
|
|
return output;
|
2025-05-04 14:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Template-tag: ansi`[red]…[/] text`
|
|
|
|
|
* Escapes brackets, parses ANSI, and restores literals.
|
|
|
|
|
*/
|
|
|
|
|
export function ansi(strings, ...values) {
|
2025-05-08 01:52:12 +00:00
|
|
|
let built = '';
|
|
|
|
|
for (let i = 0; i < strings.length; i++) {
|
|
|
|
|
built += strings[i];
|
|
|
|
|
if (i < values.length) built += values[i];
|
|
|
|
|
}
|
|
|
|
|
return restoreBrackets(formatAnsi(escapeBrackets(built)));
|
2025-05-04 14:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Wrap text in a ```ansi code block for Discord. */
|
|
|
|
|
export function wrapAnsi(text) {
|
2025-05-08 01:52:12 +00:00
|
|
|
return '```ansi\n' + text + '\n```';
|
2025-05-04 14:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Export raw codes for advanced use (e.g., ansitheme module)
|
2025-05-08 01:52:12 +00:00
|
|
|
export { CODES };
|