From 4329da08cbd767712c890c2bb0ed7c81ba09f9be Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Fri, 6 Feb 2026 22:38:46 +0800 Subject: [PATCH] update term color render --- .gitignore | 3 + index.js | 126 -------------------------------- package-lock.json | 48 +++++++++++++ package.json | 49 +++++++++++-- src/index.js | 152 +++++++++++++++++++++++++++++++++++++++ index.ts => src/index.ts | 33 +++++++-- tsconfig.json | 41 +++++++++++ 7 files changed, 315 insertions(+), 137 deletions(-) delete mode 100644 index.js create mode 100644 package-lock.json create mode 100644 src/index.js rename index.ts => src/index.ts (80%) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index d4777d2..b695a82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +node_modules/ +.idea/ +dist/ # ---> macOS # General .DS_Store diff --git a/index.js b/index.js deleted file mode 100644 index 90ea7c6..0000000 --- a/index.js +++ /dev/null @@ -1,126 +0,0 @@ -// 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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..62ed0fe --- /dev/null +++ b/package-lock.json @@ -0,0 +1,48 @@ +{ + "name": "@hatterjiang/term-color-render", + "version": "1.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@hatterjiang/term-color-render", + "version": "1.0.2", + "license": "MIT", + "devDependencies": { + "@types/node": "^25.2.1", + "typescript": "^5.9.3" + } + }, + "node_modules/@types/node": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", + "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 102e0bd..94833ad 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,51 @@ { "name": "@hatterjiang/term-color-render", - "version": "1.0.1", + "version": "1.0.2", "description": "Render color for term use syntax like [red]text[/red]", "license": "MIT", - "author": "hatterjiang", - "type": "commonjs", - "main": "index.ts", + "author": "Hatter Jiang", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/mjs/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/mjs/index.js" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + } + }, + "repository": { + "type": "git", + "url": "https://git.hatter.ink/hatter/term-color-render.git" + }, + "keywords": [ + "terminal", + "term", + "color" + ], + "files": [ + "dist/**/*", + "README.md", + "LICENSE" + ], "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "prebuild": "rm -rf dist", + "build": "npm run build:mjs && npm run build:cjs && npm run build:types", + "build:mjs": "tsc --module ESNext --outDir dist/mjs --moduleSuffixes .mjs", + "build:cjs": "tsc --module CommonJS --outDir dist/cjs --moduleSuffixes .cjs", + "build:types": "tsc --emitDeclarationOnly --outDir dist/types", + "lint": "eslint src --ext .ts", + "test": "jest", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "@types/node": "^25.2.1", + "typescript": "^5.9.3" } } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..865e545 --- /dev/null +++ b/src/index.js @@ -0,0 +1,152 @@ +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) { + // 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 = { + blink: "5", + bold: "1", + under: "4", + red: "31", + green: "32", + yellow: "33", + blue: "34", + pink: "35", + cyan: "36", +}; +function renderColorTokens(tokens, renderColor) { + const text = []; + 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(); + if (renderColor) { + text.push("\x1b[0m"); + } + } + const colors = []; + for (const [color, colorStack] of colorMapStack) { + if (colorStack.length > 0) { + colors.push(colorCode); + } + } + if (colors.length > 0) { + if (renderColor) { + text.push(`\x1b[${colors.join(";")}m`); + } + } + } + } + else { + if (token.content) { + text.push(token.content); + } + } + } + if (renderColor) { + text.push("\x1b[0m"); // FINALLY END ALL COLOR + } + return text.join(""); +} +function supportColor() { + try { + if (process.env.FORCE_COLOR !== undefined) { + return process.env.FORCE_COLOR !== '0'; + } + if (process.env.NO_COLOR !== undefined) { + return false; + } + return process.stdout.isTTY && process.stderr.isTTY; + } + catch (e) { + // check color support failed, default false + return false; + } +} +export function renderColor(message, renderColor) { + return renderColorTokens(parseColorTokens(message), renderColor ?? supportColor()); +} diff --git a/index.ts b/src/index.ts similarity index 80% rename from index.ts rename to src/index.ts index 10bb9cd..c3e693c 100644 --- a/index.ts +++ b/src/index.ts @@ -88,7 +88,7 @@ const COLOR_MAP: Record = { cyan: "36", }; -function renderColorTokens(tokens: ColorToken[]): string { +function renderColorTokens(tokens: ColorToken[], renderColor: boolean): string { const text: string[] = []; const colorMapStack = new Map(); for (const token of tokens) { @@ -108,7 +108,9 @@ function renderColorTokens(tokens: ColorToken[]): string { colorStack.push(1); } else { colorStack.pop(); - text.push("\x1b[0m"); + if (renderColor) { + text.push("\x1b[0m"); + } } const colors: string[] = []; for (const [color, colorStack] of colorMapStack) { @@ -117,7 +119,9 @@ function renderColorTokens(tokens: ColorToken[]): string { } } if (colors.length > 0) { - text.push(`\x1b[${colors.join(";")}m`); + if (renderColor) { + text.push(`\x1b[${colors.join(";")}m`); + } } } } else { @@ -126,10 +130,27 @@ function renderColorTokens(tokens: ColorToken[]): string { } } } - text.push("\x1b[0m"); // FINALLY END ALL COLOR + if (renderColor) { + text.push("\x1b[0m"); // FINALLY END ALL COLOR + } return text.join(""); } -export function renderColor(message: string): string { - return renderColorTokens(parseColorTokens(message)); +function supportColor(): boolean { + try { + if (process.env.FORCE_COLOR !== undefined) { + return process.env.FORCE_COLOR !== '0'; + } + if (process.env.NO_COLOR !== undefined) { + return false; + } + return process.stdout.isTTY && process.stderr.isTTY; + } catch (e) { + // check color support failed, default false + return false; + } +} + +export function renderColor(message: string, renderColor?: boolean): string { + return renderColorTokens(parseColorTokens(message), renderColor ?? supportColor()); } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1a28b5f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "ts-node": { + "files": true, + "emit": true, + "compilerHost": true + }, + "compilerOptions": { + // 基础设置 + "target": "es2015", + "module": "ESNext", + "moduleResolution": "node", + "lib": [ + "es2015" + ], + // 输出设置 + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + // 模块兼容性 + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + // 严格模式(推荐) + "strict": true, + "skipLibCheck": true, +// "typeRoots": [ +// "./node_modules/@types" +// ], +// "types": [ +// "node" +// ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file