From a8377f099478672cc5d66d38208c3ef04b6497ce Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Fri, 6 Feb 2026 00:18:06 +0800 Subject: [PATCH] https://www.npmjs.com/package/@hatterjiang/term-color-render?activeTab=readme --- .npmignore | 2 + README.md | 18 ++++++- index.js | 126 +++++++++++++++++++++++++++++++++++++++++++++++ index.ts | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 12 +++++ 5 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 .npmignore create mode 100644 index.js create mode 100644 index.ts create mode 100644 package.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..f98ec88 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +.git/ +.idea/ diff --git a/README.md b/README.md index deaecb7..b65653d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ -# term-color-render +# term color render +```js +import {renderColor} from "./index"; + +console.log(renderColor('hello [red]red world[/red], [bold][green]bold and green text[/green][/bold]')); +``` + + +Supported colors: +- `[red]red text[/red]` +- `[blue]blue text[/blue]` +- `[green]green text[/green]` +- `[yellow]yellow text[/yellow]` +- `[cyan]cyan text[/cyan]` +- `[pink]cyan text[/pink]` +- `[bold]bold text[/bold]` +- `[under]under text[/under]` diff --git a/index.js b/index.js new file mode 100644 index 0000000..90ea7c6 --- /dev/null +++ b/index.js @@ -0,0 +1,126 @@ +// index.ts +function parseColorTokens(message) { + const tokens = []; + if (message) { + let inColorStart = false; + let inColorEnd = false; + let chars = []; + const messageLength = message.length; + for (let i = 0; i < messageLength; i++) { + const c = message.charAt(i); + const nextC = i + 1 < messageLength ? message.charAt(i + 1) : null; + switch (c) { + case "\\": + if (nextC === null) { + chars.push(c); + } else { + chars.push(nextC); + i++; + } + break; + case "[": + if (inColorStart || inColorEnd) { + break; + } else if (nextC == "/") { + inColorEnd = true; + i++; + } else { + inColorStart = true; + } + if (chars.length > 0) { + tokens.push({ + type: "text", + content: chars.join("") + }); + chars = []; + } + break; + case "]": + if (inColorStart || inColorEnd) { + if (chars.length > 0) { + tokens.push({ + type: "color", + colorStart: inColorStart, + color: chars.join("") + }); + chars = []; + } + inColorStart = false; + inColorEnd = false; + } else { + chars.push(c); + } + break; + default: + chars.push(c); + break; + } + } + const inColor = inColorStart || inColorEnd; + if (chars.length > 0 && !inColor) { + tokens.push({ + type: "text", + content: chars.join("") + }); + } + } + return tokens; +} +var COLOR_MAP = { + blink: "5", + bold: "1", + under: "4", + red: "31", + green: "32", + yellow: "33", + blue: "34", + pink: "35", + cyan: "36" +}; +function renderColorTokens(tokens) { + const text = []; + const colorMapStack = /* @__PURE__ */ new Map(); + for (const token of tokens) { + if (token.type === "color") { + const color = token.color; + if (color) { + const colorCode = COLOR_MAP[color]; + if (!colorCode) { + text.push(`[${token.colorStart ? "" : "/"}${color}]`); + continue; + } + const colorStack = colorMapStack.get(color) ?? []; + if (colorStack.length == 0) { + colorMapStack.set(color, colorStack); + } + if (token.colorStart) { + colorStack.push(1); + } else { + colorStack.pop(); + text.push("\x1B[0m"); + } + const colors = []; + for (const [color2, colorStack2] of colorMapStack) { + if (colorStack2.length > 0) { + colors.push(colorCode); + } + } + if (colors.length > 0) { + text.push(`\x1B[${colors.join(";")}m`); + } + } + } else { + if (token.content) { + text.push(token.content); + } + } + } + text.push("\x1B[0m"); + return text.join(""); +} +function renderColor(message) { + return renderColorTokens(parseColorTokens(message)); +} +export { + renderColor +}; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..10bb9cd --- /dev/null +++ b/index.ts @@ -0,0 +1,135 @@ +interface ColorToken { + type: "color" | "text"; + content?: string; + colorStart?: boolean; + color?: string; +} + +function parseColorTokens(message: string): ColorToken[] { + const tokens: ColorToken[] = []; + if (message) { + let inColorStart = false; + let inColorEnd = false; + let chars: string[] = []; + const messageLength = message.length; + for (let i = 0; i < messageLength; i++) { + const c = message.charAt(i); + const nextC = (i + 1) < messageLength + ? message.charAt(i + 1) + : null; + switch (c) { + case "\\": + if (nextC === null) { + chars.push(c); + } else { + chars.push(nextC); + i++; + } + break; + case "[": + if (inColorStart || inColorEnd) { + // SHOULD NOT HAPPEN + break; + } else if (nextC == "/") { + inColorEnd = true; + i++; + } else { + inColorStart = true; + } + if (chars.length > 0) { + tokens.push({ + type: "text", + content: chars.join(""), + }); + chars = []; + } + break; + case "]": + if (inColorStart || inColorEnd) { + if (chars.length > 0) { + tokens.push({ + type: "color", + colorStart: inColorStart, + color: chars.join(""), + }); + chars = []; + } + inColorStart = false; + inColorEnd = false; + } else { + chars.push(c); + } + break; + default: + chars.push(c); + break; + } + } + const inColor = inColorStart || inColorEnd; + if (chars.length > 0 && !inColor) { + tokens.push({ + type: "text", + content: chars.join(""), + }); + } + } + return tokens; +} + +const COLOR_MAP: Record = { + blink: "5", + bold: "1", + under: "4", + red: "31", + green: "32", + yellow: "33", + blue: "34", + pink: "35", + cyan: "36", +}; + +function renderColorTokens(tokens: ColorToken[]): string { + const text: string[] = []; + const colorMapStack = new Map(); + for (const token of tokens) { + if (token.type === "color") { + const color = token.color; + if (color) { + const colorCode = COLOR_MAP[color]; + if (!colorCode) { + text.push(`[${token.colorStart ? "" : "/"}${color}]`); + continue; + } + const colorStack = colorMapStack.get(color) ?? []; + if (colorStack.length == 0) { + colorMapStack.set(color, colorStack); + } + if (token.colorStart) { + colorStack.push(1); + } else { + colorStack.pop(); + text.push("\x1b[0m"); + } + const colors: string[] = []; + for (const [color, colorStack] of colorMapStack) { + if (colorStack.length > 0) { + colors.push(colorCode); + } + } + if (colors.length > 0) { + text.push(`\x1b[${colors.join(";")}m`); + } + } + } else { + if (token.content) { + text.push(token.content); + } + } + } + text.push("\x1b[0m"); // FINALLY END ALL COLOR + return text.join(""); +} + +export function renderColor(message: string): string { + return renderColorTokens(parseColorTokens(message)); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..69d7c49 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "@hatterjiang/term-color-render", + "version": "1.0.0", + "description": "Render color for term use syntax like [red]text[/red]", + "license": "MIT", + "author": "hatterjiang", + "type": "commonjs", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +}