├── .husky
├── .gitignore
├── pre-commit
└── commit-msg
├── .gitattributes
├── .babelrc
├── .prettierrc
├── .eslintrc
├── commitlint.config.js
├── CHANGELOG.md
├── .npmignore
├── .gitignore
├── LICENSE
├── .github
└── workflows
│ └── ci.yml
├── package.json
├── ansicolor.d.ts
├── README.md
├── test.js
└── ansicolor.js
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.js text eol=lf
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2016" ]
3 | }
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "printWidth": 120
5 | }
6 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 6,
4 | "sourceType": "module"
5 | },
6 | "extends": ["plugin:prettier/recommended"]
7 | }
8 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'subject-case': [1, 'always', ['sentence-case']],
5 | 'header-max-length': [1, 'always', 72]
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Recent Updates / Changelog
2 |
3 | - Each input line now gets wrapped separately, i.e. `'foo\nbar'.red === 'foo'.red + '\n' + 'bar'.red`
4 | - You can now do `ansi.green.inverse.underline.italic ('hello')` (chainable API)
5 | - You can now change the default RGB values for CSS output
6 | - `.parse ()` now returns full span style data (ex. `{ italic: true, bgColor: { name: 'red', dim: true }, ...`)
7 | - `.strip ()` for removing ANSI codes
8 | - `.names` for run-time reflection
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 |
41 | # ESDoc
42 | doc
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 |
41 | # ESDoc
42 | doc
43 |
44 | # VS Code
45 | settings.json
46 |
47 | # Ignore generated file
48 | build/ansicolor.js
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 | on: [ push, pull_request ]
3 |
4 | jobs:
5 | test:
6 | runs-on: ubuntu-22.04
7 | steps:
8 | - name: checkout
9 | uses: actions/checkout@v3
10 |
11 | - name: Setup NodeJS
12 | uses: actions/setup-node@v3
13 | with:
14 | node-version: 20
15 | registry-url: https://registry.npmjs.org
16 |
17 | - name: Install Dependencies
18 | run: npm i
19 |
20 | - name: Format
21 | run: npm run format
22 |
23 | - name: Lint
24 | run: npm run lint
25 |
26 | - name: Test
27 | run: npm run test
28 |
29 | deploy:
30 | runs-on: ubuntu-22.04
31 | needs: test
32 |
33 | # Run only on pushing to master
34 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
35 |
36 | steps:
37 | - name: checkout
38 | uses: actions/checkout@v3
39 |
40 | - name: Setup NodeJS
41 | uses: actions/setup-node@v3
42 | with:
43 | node-version: 20
44 | registry-url: https://registry.npmjs.org
45 |
46 | - name: Install Dependencies
47 | run: npm i
48 |
49 | - name: Build
50 | run: npm run build
51 |
52 | - name: Release to NPM
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
56 | # Enable npm provenance - (doing here so when we have tests that actually publish to local npm registry they won't fail)
57 | npm_config_provenance: true
58 | run: npx semantic-release
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ansicolor",
3 | "version": "2.0.2",
4 | "description": "A JavaScript ANSI color/style management. ANSI parsing. ANSI to CSS. Small, clean, no dependencies.",
5 | "main": "./build/ansicolor.js",
6 | "types": "./ansicolor.d.ts",
7 | "scripts": {
8 | "babel": "babel ansicolor.js --source-maps inline --out-file ./build/ansicolor.js",
9 | "build": "npm run babel",
10 | "test": "npm run build && nyc --reporter=html --reporter=text env ANSICOLOR_MODULE='./build/ansicolor' mocha --reporter spec",
11 | "autotest": "env ANSICOLOR_MODULE='./ansicolor' mocha --reporter spec --watch",
12 | "prepare": "husky install",
13 | "format": "prettier --write ansicolor.js test.js",
14 | "lint": "eslint ansicolor.js test.js --fix",
15 | "semantic-release": "semantic-release"
16 | },
17 | "lint-staged": {
18 | "*.js": ["prettier --write", "eslint --fix"]
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/xpl/ansicolor.git"
23 | },
24 | "exports": {
25 | "import": "./build/ansicolor.mjs",
26 | "require": "./build/ansicolor.js",
27 | "types": "./ansicolor.d.ts"
28 | },
29 | "keywords": [
30 | "ANSI",
31 | "ansi to css",
32 | "code",
33 | "codes",
34 | "color",
35 | "colors",
36 | "text",
37 | "command-line",
38 | "command line",
39 | "sequence",
40 | "control",
41 | "formatting",
42 | "cli",
43 | "shell",
44 | "escape",
45 | "escapes",
46 | "red",
47 | "green",
48 | "blue",
49 | "cyan",
50 | "magenta",
51 | "yellow",
52 | "dim",
53 | "bright",
54 | "background",
55 | "color logging",
56 | "colored logging",
57 | "colored log",
58 | "log with colors",
59 | "log colors",
60 | "color helper",
61 | "colorize",
62 | "color output",
63 | "ansi color",
64 | "ansi-color",
65 | "ansicolor",
66 | "ansi coloring",
67 | "colored strings",
68 | "terminal colors",
69 | "ansi styles",
70 | "strip ansi codes",
71 | "parse ansi",
72 | "ansi parser",
73 | "ansi to html",
74 | "ansi for web",
75 | "web ansi",
76 | "css ansi",
77 | "terminal colors emulation",
78 | "console",
79 | "console colors",
80 | "ansi console",
81 | "logging",
82 | "log",
83 | "chrome",
84 | "chrome devtools",
85 | "web inspector",
86 | "console.log",
87 | "developer tools",
88 | "devtools",
89 | "tty colors",
90 | "tty",
91 | "rainbow"
92 | ],
93 | "author": "Vitaly Gordon ",
94 | "license": "Unlicense",
95 | "bugs": {
96 | "url": "https://github.com/xpl/ansicolor/issues"
97 | },
98 | "homepage": "https://xpl.github.io/ansicolor",
99 | "devDependencies": {
100 | "@commitlint/cli": "^17.3.0",
101 | "@commitlint/config-conventional": "^17.3.0",
102 | "@types/node": "^13.7.0",
103 | "babel-cli": "^6.26.0",
104 | "babel-preset-es2016": "^6.24.1",
105 | "eslint": "^8.31.0",
106 | "eslint-config-prettier": "^8.6.0",
107 | "eslint-plugin-prettier": "^4.2.1",
108 | "husky": "^8.0.3",
109 | "istanbul": "^0.4.5",
110 | "lint-staged": "^15.0.2",
111 | "mocha": "^5.2.0",
112 | "nyc": "^14.1.1",
113 | "prettier": "^2.8.1",
114 | "semantic-release": "^19.0.5"
115 | },
116 | "publishConfig": {
117 | "access": "public"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/ansicolor.d.ts:
--------------------------------------------------------------------------------
1 | export declare interface ParsedColor {
2 | name?: string;
3 | bright?: boolean;
4 | dim?: boolean;
5 | }
6 |
7 | export declare interface ParsedSpan {
8 |
9 | text: string;
10 | css: string;
11 | italic?: boolean;
12 | bold?: boolean;
13 | color?: ParsedColor;
14 | bgColor?: ParsedColor;
15 | }
16 |
17 | export declare interface AnsiColored extends Iterator {
18 |
19 | readonly spans: ParsedSpan[];
20 | readonly str: string;
21 | readonly asChromeConsoleLogArguments: string[];
22 | }
23 |
24 | export declare interface RGBValues {
25 |
26 | black: [number, number, number];
27 | darkGray: [number, number, number];
28 | lightGray: [number, number, number];
29 | white: [number, number, number];
30 |
31 | red: [number, number, number];
32 | lightRed: [number, number, number];
33 |
34 | green: [number, number, number];
35 | lightGreen: [number, number, number];
36 |
37 | yellow: [number, number, number];
38 | lightYellow: [number, number, number];
39 |
40 | blue: [number, number, number];
41 | lightBlue: [number, number, number];
42 |
43 | magenta: [number, number, number];
44 | lightMagenta: [number, number, number];
45 |
46 | cyan: [number, number, number];
47 | lightCyan: [number, number, number];
48 | }
49 |
50 | export declare interface AnsicolorMethods {
51 |
52 | (text: string|number|null|undefined): string; // applies the style to the string
53 |
54 | default: AnsicolorMethods;
55 | white: AnsicolorMethods;
56 | black: AnsicolorMethods;
57 | red: AnsicolorMethods;
58 | green: AnsicolorMethods;
59 | yellow: AnsicolorMethods;
60 | blue: AnsicolorMethods;
61 | magenta: AnsicolorMethods;
62 | cyan: AnsicolorMethods;
63 |
64 | darkGray: AnsicolorMethods;
65 | lightGray: AnsicolorMethods;
66 | lightRed: AnsicolorMethods;
67 | lightGreen: AnsicolorMethods;
68 | lightYellow: AnsicolorMethods;
69 | lightBlue: AnsicolorMethods;
70 | lightMagenta: AnsicolorMethods;
71 | lightCyan: AnsicolorMethods;
72 |
73 | bright: AnsicolorMethods;
74 | dim: AnsicolorMethods;
75 | italic: AnsicolorMethods;
76 | underline: AnsicolorMethods;
77 | inverse: AnsicolorMethods;
78 |
79 | bgDefault: AnsicolorMethods;
80 | bgWhite: AnsicolorMethods;
81 | bgBlack: AnsicolorMethods;
82 | bgRed: AnsicolorMethods;
83 | bgGreen: AnsicolorMethods;
84 | bgYellow: AnsicolorMethods;
85 | bgBlue: AnsicolorMethods;
86 | bgMagenta: AnsicolorMethods;
87 | bgCyan: AnsicolorMethods;
88 |
89 | bgDarkGray: AnsicolorMethods;
90 | bgLightGray: AnsicolorMethods;
91 | bgLightRed: AnsicolorMethods;
92 | bgLightGreen: AnsicolorMethods;
93 | bgLightYellow: AnsicolorMethods;
94 | bgLightBlue: AnsicolorMethods;
95 | bgLightMagenta: AnsicolorMethods;
96 | bgLightCyan: AnsicolorMethods;
97 | }
98 |
99 | export declare class ansicolor {
100 |
101 | static get nice (): ansicolor; // installs unsafe String extensions when accessed
102 |
103 | static get rgb (): RGBValues;
104 | static set rgb (newSchema: RGBValues);
105 |
106 | static parse (text: string): AnsiColored;
107 | static strip (text: string): string;
108 | static isEscaped (x?: any): boolean;
109 |
110 | static get default (): AnsicolorMethods;
111 | static get white (): AnsicolorMethods;
112 | static get black (): AnsicolorMethods;
113 | static get red (): AnsicolorMethods;
114 | static get green (): AnsicolorMethods;
115 | static get yellow (): AnsicolorMethods;
116 | static get blue (): AnsicolorMethods;
117 | static get magenta (): AnsicolorMethods;
118 | static get cyan (): AnsicolorMethods;
119 |
120 | static get darkGray (): AnsicolorMethods;
121 | static get lightGray (): AnsicolorMethods;
122 | static get lightRed (): AnsicolorMethods;
123 | static get lightGreen (): AnsicolorMethods;
124 | static get lightYellow (): AnsicolorMethods;
125 | static get lightBlue (): AnsicolorMethods;
126 | static get lightMagenta (): AnsicolorMethods;
127 | static get lightCyan (): AnsicolorMethods;
128 |
129 | static get bright (): AnsicolorMethods;
130 | static get dim (): AnsicolorMethods;
131 | static get italic (): AnsicolorMethods;
132 | static get underline (): AnsicolorMethods;
133 | static get inverse (): AnsicolorMethods;
134 |
135 | static get bgDefault (): AnsicolorMethods;
136 | static get bgWhite (): AnsicolorMethods;
137 | static get bgBlack (): AnsicolorMethods;
138 | static get bgRed (): AnsicolorMethods;
139 | static get bgGreen (): AnsicolorMethods;
140 | static get bgYellow (): AnsicolorMethods;
141 | static get bgBlue (): AnsicolorMethods;
142 | static get bgMagenta (): AnsicolorMethods;
143 | static get bgCyan (): AnsicolorMethods;
144 |
145 | static get bgDarkGray (): AnsicolorMethods;
146 | static get bgLightGray (): AnsicolorMethods;
147 | static get bgLightRed (): AnsicolorMethods;
148 | static get bgLightGreen (): AnsicolorMethods;
149 | static get bgLightYellow (): AnsicolorMethods;
150 | static get bgLightBlue (): AnsicolorMethods;
151 | static get bgLightMagenta (): AnsicolorMethods;
152 | static get bgLightCyan (): AnsicolorMethods;
153 | }
154 |
155 | export function parse (text: string): AnsiColored;
156 | export function parseIterator (textOrGetText: string | (() => string)): Iterator;
157 | export function strip (text: string): string;
158 | export function isEscaped (x?: any): boolean;
159 |
160 | export const white: AnsicolorMethods;
161 | export const black: AnsicolorMethods;
162 | export const red: AnsicolorMethods;
163 | export const green: AnsicolorMethods;
164 | export const yellow: AnsicolorMethods;
165 | export const blue: AnsicolorMethods;
166 | export const magenta: AnsicolorMethods;
167 | export const cyan: AnsicolorMethods;
168 |
169 | export const darkGray: AnsicolorMethods;
170 | export const lightGray: AnsicolorMethods;
171 | export const lightRed: AnsicolorMethods;
172 | export const lightGreen: AnsicolorMethods;
173 | export const lightYellow: AnsicolorMethods;
174 | export const lightBlue: AnsicolorMethods;
175 | export const lightMagenta: AnsicolorMethods;
176 | export const lightCyan: AnsicolorMethods;
177 |
178 | export const bright: AnsicolorMethods;
179 | export const dim: AnsicolorMethods;
180 | export const italic: AnsicolorMethods;
181 | export const underline: AnsicolorMethods;
182 | export const inverse: AnsicolorMethods;
183 |
184 | export const bgDefault: AnsicolorMethods;
185 | export const bgWhite: AnsicolorMethods;
186 | export const bgBlack: AnsicolorMethods;
187 | export const bgRed: AnsicolorMethods;
188 | export const bgGreen: AnsicolorMethods;
189 | export const bgYellow: AnsicolorMethods;
190 | export const bgBlue: AnsicolorMethods;
191 | export const bgMagenta: AnsicolorMethods;
192 | export const bgCyan: AnsicolorMethods;
193 |
194 | export const bgDarkGray: AnsicolorMethods;
195 | export const bgLightGray: AnsicolorMethods;
196 | export const bgLightRed: AnsicolorMethods;
197 | export const bgLightGreen: AnsicolorMethods;
198 | export const bgLightYellow: AnsicolorMethods;
199 | export const bgLightBlue: AnsicolorMethods;
200 | export const bgLightMagenta: AnsicolorMethods;
201 | export const bgLightCyan: AnsicolorMethods;
202 |
203 | export default ansicolor;
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ansicolor
2 |
3 | [](https://npmjs.com/package/ansicolor)
4 |
5 | A JavaScript ANSI color/style management. ANSI parsing. ANSI to CSS. Small, clean, no dependencies.
6 |
7 | ```bash
8 | npm install ansicolor
9 | ```
10 |
11 | ## What For
12 |
13 | - String coloring with ANSI escape codes
14 | - Solves the [style hierarchy problem](#why-another-one) (when other similar tools fail)
15 | - Parsing/removing ANSI style data from strings
16 | - Converting ANSI styles to CSS or a Chrome DevTools-compatible output
17 | - A middleware for your [platform-agnostic logging system](https://github.com/xpl/ololog)
18 |
19 | ## Why Another One?
20 |
21 | Other tools lack consistency, failing to solve a simple hierarchy problem:
22 |
23 | ```javascript
24 | require ('colors') // a popular color utility
25 |
26 | console.log (('foo'.cyan + 'bar').red)
27 | ```
28 |
29 | 
30 |
31 | WTF?! The `bar` word above should be rendered in red, but it's not! That sucks. It's because ANSI codes are linear, not hierarchical (as with XML/HTML). A special kind of magic is needed to make this work. **Ansicolor** does that magic for you:
32 |
33 | ```javascript
34 | require ('ansicolor').nice // .nice for unsafe String extensions
35 |
36 | console.log (('foo'.cyan + 'bar').red)
37 | ```
38 |
39 | 
40 |
41 | Nice!
42 |
43 | ## Crash Course
44 |
45 | Importing (as methods):
46 |
47 | ```javascript
48 | import { green, inverse, bgLightCyan, underline, dim } from 'ansicolor'
49 | ```
50 | ```javascript
51 | const { green, inverse, bgLightCyan, underline, dim } = require ('ansicolor')
52 | ```
53 |
54 | Usage:
55 |
56 | ```javascript
57 | console.log ('foo' + green (inverse (bgLightCyan ('bar')) + 'baz') + 'qux')
58 | ```
59 | ```javascript
60 | console.log (underline.bright.green ('foo' + dim.red.bgLightCyan ('bar'))) // method chaining
61 | ```
62 |
63 | Importing (as object):
64 |
65 | ```javascript
66 | import { ansicolor, ParsedSpan } from 'ansicolor' // along with type definitions
67 | ```
68 | ```javascript
69 | import ansicolor from 'ansicolor'
70 | ```
71 |
72 | ### Nice Mode (not recommended)
73 |
74 | ```javascript
75 | const ansi = require ('ansicolor').nice
76 | ```
77 |
78 | The `('ansicolor').nice` export defines styling APIs on the `String` prototype directly. It uses an ad-hoc DSL (sort of) for infix-style string coloring. The `nice` is convenient, but not safe, avoid using it in public modules, as it alters global objects, and that might cause potential hard-to-debug compatibility issues.
79 |
80 | ```javascript
81 | console.log ('foo'.red.bright + 'bar'.bgYellow.underline.dim)
82 | ```
83 |
84 | ### Supported Styles
85 |
86 | ```javascript
87 | 'foreground colors'
88 | .red.green.yellow.blue.magenta.cyan.white.darkGray.black
89 | ```
90 | ```javascript
91 | 'light foreground colors'
92 | .lightRed.lightGreen.lightYellow.lightBlue.lightMagenta.lightCyan.lightGray
93 | ```
94 | ```javascript
95 | 'background colors'
96 | .bgRed.bgGreen.bgYellow.bgBlue.bgMagenta.bgCyan.bgWhite.bgDarkGray.bgBlack
97 | ```
98 | ```javascript
99 | 'light background colors'
100 | .bgLightRed.bgLightGreen.bgLightYellow.bgLightBlue.bgLightMagenta.bgLightCyan.bgLightGray
101 | ```
102 | ```javascript
103 | 'styles'
104 | .bright.dim.italic.underline.inverse // your platform should support italic
105 | ```
106 |
107 | You also can obtain all those style names (for reflection purposes):
108 |
109 | ```javascript
110 | const { names } = require ('ansicolor')
111 |
112 | names // ['red', 'green', ...
113 | ```
114 |
115 | ## Removing ANSI Styles From Strings
116 |
117 | ```javascript
118 | const { strip } = require ('ansicolor')
119 |
120 | strip ('\u001b[0m\u001b[4m\u001b[42m\u001b[31mfoo\u001b[39m\u001b[49m\u001b[24mfoo\u001b[0m')) // 'foofoo'
121 | ```
122 |
123 | ## Checking If Strings Contain ANSI Codes
124 |
125 | ```javascript
126 | const { isEscaped, green } = require ('ansicolor')
127 |
128 | isEscaped ('text') // false
129 | isEscaped (green ('text')) // true
130 | ```
131 |
132 |
133 | ## Converting to CSS/HTML
134 |
135 | Inspection of ANSI styles in arbitrary strings is essential when implementing platform-agnostic logging — that piece of code is available under command line interface and in a browser as well. Here's an example of how you would parse a colored string into an array-like structure. That structure can be traversed later to build HTML/JSON/XML or any other markup/syntax.
136 |
137 | ```javascript
138 | const { parse } = require ('ansicolor')
139 |
140 | const parsed = parse ('foo'.bgLightRed.bright.italic + 'bar'.red.dim)
141 | ```
142 |
143 | The `ansi.parse ()` method will return a pseudo-array of styled spans, you can iterate over it with a `for ... of` loop and convert it to an array with the *spread operator* (`...`). Also, there's the `.spans` property for obtaining the already-spread array directly:
144 |
145 | ```javascript
146 | assert.deepEqual (parsed.spans /* or [...parsed] */,
147 |
148 | [ { css: 'font-weight: bold;font-style: italic;background:rgba(255,51,0,1);',
149 | italic: true,
150 | bold: true,
151 | color: { bright: true },
152 | bgColor: { name: 'lightRed' },
153 | text: 'foo' },
154 |
155 | { css: 'color:rgba(204,0,0,0.5);',
156 | color: { name: 'red', dim: true },
157 | text: 'bar' } ])
158 | ```
159 |
160 | ### Custom Color Themes
161 |
162 | You can change default RGB values (won't work in terminals, affects only the ANSI→CSS transformation part):
163 |
164 | ```javascript
165 | const ansi = require ('ansicolor')
166 |
167 | ansi.rgb = {
168 |
169 | black: [0, 0, 0],
170 | darkGray: [100, 100, 100],
171 | lightGray: [200, 200, 200],
172 | white: [255, 255, 255],
173 |
174 | red: [204, 0, 0],
175 | lightRed: [255, 51, 0],
176 |
177 | green: [0, 204, 0],
178 | lightGreen: [51, 204, 51],
179 |
180 | yellow: [204, 102, 0],
181 | lightYellow: [255, 153, 51],
182 |
183 | blue: [0, 0, 255],
184 | lightBlue: [26, 140, 255],
185 |
186 | magenta: [204, 0, 204],
187 | lightMagenta: [255, 0, 255],
188 |
189 | cyan: [0, 153, 255],
190 | lightCyan: [0, 204, 255],
191 | }
192 | ```
193 |
194 | ## Chrome DevTools Compatibility
195 |
196 | Web browsers usually implement their own proprietary CSS-based color formats for `console.log` and most of them fail to display standard ANSI colors. _Ansicolor_ offers you a helper method to convert ANSI-styled strings to browser-compatible argument lists acceptable by Chrome's `console.log`:
197 |
198 | ```javascript
199 | const { bgGreen, red, parse } = require ('ansicolor')
200 |
201 | const string = 'foo' + bgGreen (red.underline.bright.inverse ('bar') + 'baz')
202 | const parsed = parse (string)
203 |
204 | console.log (...parsed.asChromeConsoleLogArguments) // prints with colors in Chrome!
205 | ```
206 |
207 | Here's what the format looks like:
208 |
209 | ```javascript
210 | parsed.asChromeConsoleLogArguments // [ "%cfoo%cbar%cbaz",
211 | // "",
212 | // "font-weight: bold;text-decoration: underline;background:rgba(255,51,0,1);color:rgba(0,204,0,1);",
213 | // "background:rgba(0,204,0,1);"
214 | // ]
215 | ```
216 |
217 | Play with this feature online: [demo page](https://xpl.github.io/ololog/). Open the DevTools console and type expressions in the input box to see colored console output.
218 |
219 | Happy logging!
220 |
221 | ## Projects That Use `ansicolor`
222 |
223 | - [**Ololog!**](https://github.com/xpl/ololog) — a better `console.log` for the log-driven debugging junkies
224 | - [**CCXT**](https://github.com/ccxt/ccxt) — a cryptocurrency trading API with 130+ exchanges
225 | - [**Grafana**](https://github.com/grafana/grafana) — beautiful monitoring & metric analytics & dashboards
226 |
227 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* ------------------------------------------------------------------------ */
4 |
5 | const assert = require ('assert'),
6 | ansi = require (process.env.ANSICOLOR_MODULE).nice
7 |
8 | /* ------------------------------------------------------------------------ */
9 |
10 | describe ('ansicolor', () => {
11 |
12 | const dbg = s => s.replace (/\u001b\[(\d+)m/g, '\u001b[36m{$1}\u001b[39m')
13 |
14 | const same = (a, b) => {
15 |
16 | console.log ('\n')
17 |
18 | console.log ('expected:', b)
19 | console.log (' ', dbg (b), '\n')
20 |
21 | console.log ('actual :', a)
22 | console.log (' ', dbg (a), '\n')
23 |
24 | assert.equal (a, b)
25 | }
26 |
27 | it ('works', () => {
28 |
29 | same ('foo' + ansi.green (ansi.inverse (ansi.bgLightCyan ('bar') + 'baz') + 'qux'),
30 | 'foo\u001b[32m\u001b[7m\u001b[106mbar\u001b[49mbaz\u001b[27mqux\u001b[39m')
31 | })
32 |
33 | it ('chaining works', () => {
34 |
35 | same (ansi.underline.bright.green ('chai' + ansi.dim.red.bgLightCyan ('ning')),
36 | '\u001b[4m\u001b[22m\u001b[1m\u001b[32mchai\u001b[22m\u001b[2m\u001b[31m\u001b[106mning\u001b[49m\u001b[32m\u001b[22m\u001b[1m\u001b[39m\u001b[22m\u001b[24m')
37 | })
38 |
39 | it ('nice mode works', () => {
40 |
41 | ansi.nice // shouldn't mess up with repeated calls
42 | ansi.nice
43 |
44 | same ('foo' + ('bar'.red.underline.bright + 'baz').green.underline + 'qux',
45 | 'foo\u001b[4m\u001b[32m\u001b[22m\u001b[1m\u001b[4m\u001b[31mbar\u001b[32m\u001b[4m\u001b[22mbaz\u001b[39m\u001b[24mqux')
46 | })
47 |
48 | it ('brightness hierarchy works', () => {
49 |
50 | same (('foo' + 'bar'.dim + 'baz').bright, '\u001b[22m\u001b[1mfoo\u001b[22m\u001b[2mbar\u001b[22m\u001b[1mbaz\u001b[22m')
51 | })
52 |
53 | it ('hierarchy works', () => {
54 |
55 | same ((('red'.red + 'green').green + 'blue').blue, '\u001b[34m\u001b[32m\u001b[31mred\u001b[32mgreen\u001b[34mblue\u001b[39m')
56 |
57 | same (('foo'.cyan + 'bar').red, '\u001b[31m\u001b[36mfoo\u001b[31mbar\u001b[39m')
58 | same (('foo'.bgCyan + 'bar').bgRed, '\u001b[41m\u001b[46mfoo\u001b[41mbar\u001b[49m')
59 | same (('foo'.bgLightCyan + 'bar').bgLightRed, '\u001b[101m\u001b[106mfoo\u001b[101mbar\u001b[49m')
60 | same (('foo'.underline + 'bar').underline, '\u001b[4m\u001b[4mfoo\u001b[4mbar\u001b[24m')
61 |
62 | same (('foo'.bright + 'bar').bright, '\u001b[22m\u001b[1m\u001b[22m\u001b[1mfoo\u001b[22m\u001b[1mbar\u001b[22m')
63 | same (('foo'.dim + 'bar').dim, '\u001b[22m\u001b[2m\u001b[22m\u001b[2mfoo\u001b[22m\u001b[2mbar\u001b[22m')
64 | same (('foo'.inverse + 'bar').inverse, '\u001b[7m\u001b[7mfoo\u001b[7mbar\u001b[27m')
65 | })
66 |
67 | it ('basic parsing works', () => {
68 |
69 | const parsed = ansi.parse ('foo'.bgLightRed.bright.italic.underline + 'bar'.red.dim)
70 |
71 | // assert.deepEqual ([...parsed], parsed.spans) // not working in node v4
72 | assert.equal (parsed[Symbol.iterator] ().next ().value.text, 'foo')
73 |
74 | assert.deepEqual (parsed.spans, [
75 | {
76 | css: 'font-weight: bold;font-style: italic;text-decoration: underline;background:rgba(255,51,0,1);',
77 | italic: true,
78 | bold: true,
79 | underline: true,
80 | bright: false,
81 | dim: false,
82 | inverse: false,
83 | color: { bright: true,
84 | dim: false,
85 | name: undefined,
86 | },
87 | bgColor: {
88 | bright: false,
89 | dim: false,
90 | name: 'lightRed'
91 | },
92 | text: 'foo',
93 | code: {
94 | isBrightness: false,
95 | str: '\u001b[49m',
96 | subtype: 'default',
97 | type: 'bgColor',
98 | value: 49
99 | }
100 | },
101 | {
102 | bgColor: undefined,
103 | bold: false,
104 | bright: false,
105 | code: {
106 | isBrightness: false,
107 | str: "\u001b[39m",
108 | subtype: "default",
109 | type: "color",
110 | value: 39
111 | },
112 | color: {
113 | bright: false,
114 | dim: true,
115 | name: "red",
116 | },
117 | css: "color:rgba(204,0,0,0.5);",
118 | dim: false,
119 | inverse: false,
120 | italic: false,
121 | text: "bar",
122 | underline: false,
123 | }
124 | ])
125 | })
126 |
127 | it ('brightness semantics (CSS)', () => {
128 |
129 | const parsed = ansi.parse ('foo'.bright.red)
130 |
131 | assert.deepEqual (parsed.spans,[{
132 | bgColor: undefined,
133 | bold: true,
134 | bright: false,
135 | code: {
136 | isBrightness: true,
137 | str: '\u001b[22m',
138 | subtype: 'dim',
139 | type: 'unstyle',
140 | value: 22,
141 | },
142 | color: {
143 | bright: true,
144 | dim: false,
145 | name: 'red',
146 | },
147 | css: 'font-weight: bold;color:rgba(255,51,0,1);',
148 | dim: false,
149 | inverse: false,
150 | italic: false,
151 | text: 'foo',
152 | underline: false,
153 | }])
154 | })
155 |
156 | it ('asChromeConsoleLogArguments works', () => {
157 |
158 | const parsed = ansi.parse ('foo' + ('bar'.red.underline.bright.inverse + 'baz').bgGreen)
159 |
160 | assert.deepEqual (parsed.asChromeConsoleLogArguments, parsed.browserConsoleArguments) // legacy API
161 |
162 | assert.deepEqual (parsed.asChromeConsoleLogArguments, [
163 |
164 | "%cfoo%cbar%cbaz",
165 | "",
166 | "font-weight: bold;text-decoration: underline;background:rgba(255,51,0,1);color:rgba(0,204,0,1);",
167 | "background:rgba(0,204,0,1);"
168 | ])
169 | })
170 |
171 | it ('.dim works in CSS (there was a bug)', () => {
172 |
173 | assert.deepEqual (ansi.parse ('foo'.dim).spans,[
174 | {
175 | bgColor: undefined,
176 | bold: false,
177 | bright: false,
178 | code: {
179 | isBrightness: true,
180 | str: '\u001b[22m',
181 | subtype: 'dim',
182 | type: 'unstyle',
183 | value: 22,
184 | },
185 | color: {
186 | bright: false,
187 | dim: true,
188 | name: undefined,
189 | },
190 | css: 'color:rgba(0,0,0,0.5);',
191 | dim: false,
192 | inverse: false,
193 | italic: false,
194 | text: 'foo',
195 | underline: false,
196 | }
197 | ])
198 | })
199 |
200 | it ('stripping works', () => { // clauses were copypasted from strip-ansi
201 |
202 | assert.equal ('foofoo', ansi.strip ('\u001b[0m\u001b[4m\u001b[42m\u001b[31mfoo\u001b[39m\u001b[49m\u001b[24mfoo\u001b[0m'))
203 | assert.equal ('bar', ansi.strip ('\x1b[0;33;49;3;9;4mbar\x1b[0m'))
204 | })
205 |
206 | it ('color names enumeration works', () => {
207 |
208 | assert.deepEqual (ansi.names, [
209 | 'black',
210 | 'bgBlack',
211 | 'red',
212 | 'bgRed',
213 | 'green',
214 | 'bgGreen',
215 | 'yellow',
216 | 'bgYellow',
217 | 'blue',
218 | 'bgBlue',
219 | 'magenta',
220 | 'bgMagenta',
221 | 'cyan',
222 | 'bgCyan',
223 | 'lightGray',
224 | 'bgLightGray',
225 | 'default',
226 | 'bgDefault',
227 | 'darkGray',
228 | 'bgDarkGray',
229 | 'lightRed',
230 | 'bgLightRed',
231 | 'lightGreen',
232 | 'bgLightGreen',
233 | 'lightYellow',
234 | 'bgLightYellow',
235 | 'lightBlue',
236 | 'bgLightBlue',
237 | 'lightMagenta',
238 | 'bgLightMagenta',
239 | 'lightCyan',
240 | 'bgLightCyan',
241 | 'white',
242 | 'bgWhite',
243 | 'bgBrightRed',
244 | 'bgBrightGreen',
245 | 'bgBrightYellow',
246 | 'bgBrightBlue',
247 | 'bgBrightMagenta',
248 | 'bgBrightCyan',
249 | 'bright',
250 | 'dim',
251 | 'italic',
252 | 'underline',
253 | 'inverse'
254 | ])
255 | })
256 |
257 | it ('type coercion works', () => {
258 |
259 | assert.equal (ansi.red (123), ansi.red ('123'))
260 | })
261 |
262 | it ('newline separation works', () => {
263 |
264 | assert.equal ('foo\nbar\nbaz'.red, 'foo'.red + '\n' + 'bar'.red + '\n' + 'baz'.red)
265 | })
266 |
267 | it ('inverse works', () => {
268 |
269 | same ('bgRed.inverse'.bgRed.inverse, '\u001b[7m\u001b[41mbgRed.inverse\u001b[49m\u001b[27m')
270 |
271 | assert.equal (ansi.parse ('foo'.bgRed.inverse).spans[0].css, 'background:rgba(255,255,255,1);color:rgba(204,0,0,1);')
272 | })
273 |
274 | it ('.str works', () => {
275 |
276 | assert.equal (new ansi ('foo\u001b[32m\u001b[7m\u001b[106mbar\u001b[49mbaz\u001b[27mqux\u001b[39m').str,
277 | 'foo\u001b[32m\u001b[7m\u001b[106mbar\u001b[49mbaz\u001b[27mqux\u001b[39m')
278 | })
279 |
280 | it ('compatible with previous versions where Light was called Bright in bg methods', () => {
281 |
282 | same ('foo'.bgLightCyan, 'foo'.bgBrightCyan)
283 | })
284 |
285 | it ('explicit reset code works', () => {
286 |
287 | assert.deepEqual (ansi.parse ('\u001b[1m\u001b[31mbold_red\u001b[0mreset').spans, [
288 | {
289 | bgColor: undefined,
290 | bold: true,
291 | bright: false,
292 | code: {
293 | isBrightness: false,
294 | str: '\u001b[0m',
295 | subtype: '',
296 | type: 'style',
297 | value: 0,
298 | },
299 | color: {
300 | bright: true,
301 | dim: false,
302 | name: 'red',
303 | },
304 | css: 'font-weight: bold;color:rgba(255,51,0,1);',
305 | dim: false,
306 | inverse: false,
307 | italic: false,
308 | text: 'bold_red',
309 | underline: false,
310 | },
311 | {
312 | bgColor: undefined,
313 | bold: false,
314 | bright: false,
315 | code: {
316 | isBrightness: false,
317 | str: '',
318 | subtype: undefined,
319 | type: undefined,
320 | value: undefined,
321 | },
322 | color: undefined,
323 | css: '',
324 | dim: false,
325 | inverse: false,
326 | italic: false,
327 | text: 'reset',
328 | underline: false,
329 | }
330 | ])
331 | })
332 |
333 | it ('implicit reset code works', () => {
334 |
335 | assert.deepEqual (ansi.parse ('\u001b[1m\u001b[31mbold_red\u001b[mreset').spans,
336 |
337 | [
338 | {
339 | bgColor: undefined,
340 | bold: true,
341 | bright: false,
342 | code: {
343 | isBrightness: false,
344 | str: '\u001b[0m',
345 | subtype: '',
346 | type: 'style',
347 | value: 0,
348 | },
349 | color: {
350 | bright: true,
351 | dim: false,
352 | name: 'red',
353 | },
354 | css: 'font-weight: bold;color:rgba(255,51,0,1);',
355 | dim: false,
356 | inverse: false,
357 | italic: false,
358 | text: 'bold_red',
359 | underline: false,
360 | },
361 | {
362 | bgColor: undefined,
363 | bold: false,
364 | bright: false,
365 | code: {
366 | isBrightness: false,
367 | str: '',
368 | subtype: undefined,
369 | type: undefined,
370 | value: undefined,
371 | },
372 | color: undefined,
373 | css: '',
374 | dim: false,
375 | inverse: false,
376 | italic: false,
377 | text: 'reset',
378 | underline: false,
379 | }
380 | ])
381 | })
382 |
383 | it ('parsing a string with no codes', () => {
384 |
385 | assert.deepEqual (ansi.parse ('foobar').spans,[
386 | {
387 | bgColor: undefined,
388 | bold: false,
389 | bright: false,
390 | code: {
391 | isBrightness: false,
392 | str: '',
393 | subtype: undefined,
394 | type: undefined,
395 | value: undefined,
396 | },
397 | color: undefined,
398 | css: '',
399 | dim: false,
400 | inverse: false,
401 | italic: false,
402 | text: 'foobar',
403 | underline: false,
404 | }
405 | ])
406 | })
407 |
408 | it ('combined codes work', () => {
409 |
410 | assert.deepEqual (ansi.parse ('\u001b[2;31mfoo🕵️bar\u001b[0mbaz').spans,[
411 | {
412 | bgColor: undefined,
413 | bold: false,
414 | bright: false,
415 | code: {
416 | isBrightness: false,
417 | str: '\u001b[0m',
418 | subtype: '',
419 | type: 'style',
420 | value: 0,
421 | },
422 | color: {
423 | bright: false,
424 | dim: true,
425 | name: 'red',
426 | },
427 | css: 'color:rgba(204,0,0,0.5);',
428 | dim: false,
429 | inverse: false,
430 | italic: false,
431 | text: 'foo🕵️bar',
432 | underline: false,
433 | },
434 | {
435 | bgColor: undefined,
436 | bold: false,
437 | bright: false,
438 | code: {
439 | isBrightness: false,
440 | str: '',
441 | subtype: undefined,
442 | type: undefined,
443 | value: undefined,
444 | },
445 | color: undefined,
446 | css: '',
447 | dim: false,
448 | inverse: false,
449 | italic: false,
450 | text: 'baz',
451 | underline: false,
452 | }
453 | ])
454 | })
455 |
456 | it ('broken codes do not parse', () => {
457 |
458 | assert.equal (ansi.parse ('\u001b2;31mfoo').spans[0].text, '\u001b2;31mfoo')
459 | assert.equal (ansi.parse ('\u001b[2;xmfoo').spans[0].text, '\u001b[2;xmfoo')
460 | assert.equal (ansi.parse ('\u001b[0').spans[0].text, '\u001b[0')
461 | })
462 |
463 | it ('changing .rgb works', () => {
464 |
465 | ansi.rgb.red = [255,0,0]
466 |
467 | assert.equal (ansi.parse ('foo'.red.bgLightRed).spans[0].css, 'color:rgba(255,0,0,1);background:rgba(255,51,0,1);')
468 | })
469 |
470 | it ('accepting certain non-string values works', () => {
471 |
472 | same (ansi.cyan(42), '\u001b[36m42\u001b[39m')
473 | same (ansi.cyan(null), '\u001b[36mnull\u001b[39m')
474 | same (ansi.cyan(undefined), '\u001b[36mundefined\u001b[39m')
475 | })
476 |
477 | it ('isEscaped works', () => {
478 | assert.equal (ansi.isEscaped(ansi.red('foo')), true);
479 | assert.equal (ansi.isEscaped('foo'), false);
480 | assert.equal (ansi.isEscaped(null), false);
481 | assert.equal (ansi.isEscaped(undefined), false);
482 | assert.equal (ansi.isEscaped(42), false);
483 | })
484 |
485 | it ('accepting non ansii colors works', () => {
486 | assert.doesNotThrow (() => ansi.parse('\u001b[253mtest\u001b[39m'));
487 | })
488 | })
489 |
490 |
--------------------------------------------------------------------------------
/ansicolor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* ------------------------------------------------------------------------ */
4 |
5 | const O = Object;
6 |
7 | /* See https://misc.flogisoft.com/bash/tip_colors_and_formatting
8 | ------------------------------------------------------------------------ */
9 |
10 | const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightGray', '', 'default'],
11 | colorCodesLight = [
12 | 'darkGray',
13 | 'lightRed',
14 | 'lightGreen',
15 | 'lightYellow',
16 | 'lightBlue',
17 | 'lightMagenta',
18 | 'lightCyan',
19 | 'white',
20 | ''
21 | ],
22 | styleCodes = ['', 'bright', 'dim', 'italic', 'underline', '', '', 'inverse'],
23 | asBright = {
24 | red: 'lightRed',
25 | green: 'lightGreen',
26 | yellow: 'lightYellow',
27 | blue: 'lightBlue',
28 | magenta: 'lightMagenta',
29 | cyan: 'lightCyan',
30 | black: 'darkGray',
31 | lightGray: 'white'
32 | },
33 | types = { 0: 'style', 2: 'unstyle', 3: 'color', 9: 'colorLight', 4: 'bgColor', 10: 'bgColorLight' },
34 | subtypes = {
35 | color: colorCodes,
36 | colorLight: colorCodesLight,
37 | bgColor: colorCodes,
38 | bgColorLight: colorCodesLight,
39 | style: styleCodes,
40 | unstyle: styleCodes
41 | };
42 |
43 | /* ------------------------------------------------------------------------ */
44 |
45 | class Color {
46 | constructor(background, name, brightness) {
47 | this.background = background;
48 | this.name = name;
49 | this.brightness = brightness;
50 | }
51 |
52 | get inverse() {
53 | return new Color(!this.background, this.name || (this.background ? 'black' : 'white'), this.brightness);
54 | }
55 |
56 | get clean() {
57 | const name = this.name === 'default' ? '' : this.name;
58 | const bright = this.brightness === Code.bright;
59 | const dim = this.brightness === Code.dim;
60 |
61 | if (!name && !bright && !dim) {
62 | return undefined;
63 | }
64 |
65 | return {
66 | name,
67 | bright,
68 | dim
69 | };
70 | }
71 |
72 | defaultBrightness(value) {
73 | return new Color(this.background, this.name, this.brightness || value);
74 | }
75 |
76 | css(inverted) {
77 | const color = inverted ? this.inverse : this;
78 |
79 | const rgbName = (color.brightness === Code.bright && asBright[color.name]) || color.name;
80 |
81 | const prop = color.background ? 'background:' : 'color:',
82 | rgb = Colors.rgb[rgbName],
83 | alpha = this.brightness === Code.dim ? 0.5 : 1;
84 |
85 | return rgb
86 | ? prop + 'rgba(' + [...rgb, alpha].join(',') + ');'
87 | : !color.background && alpha < 1
88 | ? 'color:rgba(0,0,0,0.5);'
89 | : ''; // Chrome does not support 'opacity' property...
90 | }
91 | }
92 |
93 | /* ------------------------------------------------------------------------ */
94 |
95 | class Code {
96 | constructor(n) {
97 | let value = undefined;
98 | let type = undefined;
99 | let subtype = undefined;
100 | let str = '';
101 | let isBrightness = false;
102 |
103 | if (n !== undefined) {
104 | value = Number(n);
105 | type = types[Math.floor(value / 10)];
106 | if (type === undefined || subtypes[type] === undefined) {
107 | return;
108 | }
109 | subtype = subtypes[type][value % 10];
110 | str = '\u001b[' + value + 'm';
111 | isBrightness = value === Code.noBrightness || value === Code.bright || value === Code.dim;
112 | }
113 |
114 | this.value = value;
115 | this.type = type;
116 | this.subtype = subtype;
117 | this.str = str;
118 | this.isBrightness = isBrightness;
119 | }
120 |
121 | static str(x) {
122 | if (x === undefined) return '';
123 | return '\u001b[' + Number(x) + 'm';
124 | }
125 |
126 | clone() {
127 | const newCode = new Code(undefined);
128 | newCode.value = this.value;
129 | newCode.type = this.type;
130 | newCode.subtype = this.subtype;
131 | newCode.str = this.str;
132 | newCode.isBrightness = this.isBrightness;
133 | return newCode;
134 | }
135 | }
136 |
137 | /* ------------------------------------------------------------------------ */
138 |
139 | O.assign(Code, {
140 | reset: 0,
141 | bright: 1,
142 | dim: 2,
143 | inverse: 7,
144 | noBrightness: 22,
145 | noItalic: 23,
146 | noUnderline: 24,
147 | noInverse: 27,
148 | noColor: 39,
149 | noBgColor: 49
150 | });
151 |
152 | /* ------------------------------------------------------------------------ */
153 |
154 | const replaceAll = (str, a, b) => str.split(a).join(b);
155 |
156 | /* ANSI brightness codes do not overlap, e.g. "{bright}{dim}foo" will be rendered bright (not dim).
157 | So we fix it by adding brightness canceling before each brightness code, so the former example gets
158 | converted to "{noBrightness}{bright}{noBrightness}{dim}foo" – this way it gets rendered as expected.
159 | */
160 |
161 | const denormalizeBrightness = (s) => s.replace(/(\u001b\[(1|2)m)/g, '\u001b[22m$1');
162 | const normalizeBrightness = (s) => s.replace(/\u001b\[22m(\u001b\[(1|2)m)/g, '$1');
163 |
164 | const wrap = (x, openCode, closeCode) => {
165 | const open = Code.str(openCode),
166 | close = Code.str(closeCode);
167 |
168 | return String(x)
169 | .split('\n')
170 | .map((line) => denormalizeBrightness(open + replaceAll(normalizeBrightness(line), close, open) + close))
171 | .join('\n');
172 | };
173 |
174 | /* ------------------------------------------------------------------------ */
175 |
176 | const camel = (a, b) => a + b.charAt(0).toUpperCase() + b.slice(1);
177 |
178 | const stringWrappingMethods = (() =>
179 | [
180 | ...colorCodes.map((k, i) =>
181 | !k
182 | ? []
183 | : [
184 | // color methods
185 |
186 | [k, 30 + i, Code.noColor],
187 | [camel('bg', k), 40 + i, Code.noBgColor]
188 | ]
189 | ),
190 |
191 | ...colorCodesLight.map((k, i) =>
192 | !k
193 | ? []
194 | : [
195 | // light color methods
196 |
197 | [k, 90 + i, Code.noColor],
198 | [camel('bg', k), 100 + i, Code.noBgColor]
199 | ]
200 | ),
201 |
202 | /* THIS ONE IS FOR BACKWARDS COMPATIBILITY WITH PREVIOUS VERSIONS (had 'bright' instead of 'light' for backgrounds)
203 | */
204 | ...['', 'BrightRed', 'BrightGreen', 'BrightYellow', 'BrightBlue', 'BrightMagenta', 'BrightCyan'].map((k, i) =>
205 | !k ? [] : [['bg' + k, 100 + i, Code.noBgColor]]
206 | ),
207 |
208 | ...styleCodes.map((k, i) =>
209 | !k
210 | ? []
211 | : [
212 | // style methods
213 |
214 | [k, i, k === 'bright' || k === 'dim' ? Code.noBrightness : 20 + i]
215 | ]
216 | )
217 | ].reduce((a, b) => a.concat(b)))();
218 |
219 | /* ------------------------------------------------------------------------ */
220 |
221 | const assignStringWrappingAPI = (target, wrapBefore = target) =>
222 | stringWrappingMethods.reduce(
223 | (memo, [k, open, close]) =>
224 | O.defineProperty(memo, k, {
225 | get: () => assignStringWrappingAPI((str) => wrapBefore(wrap(str, open, close)))
226 | }),
227 |
228 | target
229 | );
230 |
231 | /* ------------------------------------------------------------------------ */
232 |
233 | const TEXT = 0,
234 | BRACKET = 1,
235 | CODE = 2;
236 |
237 | class Span {
238 | constructor(code, text) {
239 | this.code = code;
240 | this.text = text;
241 |
242 | // Those are added in the actual parse, this is done for performance reasons to have the same hidden class
243 | this.css = '';
244 | this.color = undefined;
245 | this.bgColor = undefined;
246 | this.bold = undefined;
247 | this.inverse = undefined;
248 | this.italic = undefined;
249 | this.underline = undefined;
250 | this.bright = undefined;
251 | this.dim = undefined;
252 | }
253 | }
254 |
255 | // getString as function instead of string to allow garbage collection
256 | function* rawParse(getString) {
257 | const stateObject = {
258 | state: TEXT,
259 | buffer: '',
260 | text: '',
261 | code: '',
262 | codes: []
263 | };
264 |
265 | const ONE_MB = 1048576;
266 |
267 | // Instead of holding the reference to the string we split into chunks of 1MB
268 | // and after processing is finished we can remove the reference so it can be GCed
269 | const chunks = splitStringToChunksOfSize(getString(), ONE_MB);
270 |
271 | for (let i = 0; i < chunks.length; i++) {
272 | const chunk = chunks[i];
273 | // Free memory for the previous chunk
274 | chunks[i] = undefined;
275 | yield* processChunk(chunk, stateObject);
276 | }
277 |
278 | if (stateObject.state !== TEXT) stateObject.text += stateObject.buffer;
279 |
280 | if (stateObject.text) {
281 | yield new Span(new Code(), stateObject.text);
282 | }
283 | }
284 |
285 | function splitStringToChunksOfSize(str, chunkSize) {
286 | const chunks = [];
287 | const chunksLength = Math.ceil(str.length / chunkSize);
288 |
289 | for (let i = 0, o = 0; i < chunksLength; ++i, o += chunkSize) {
290 | chunks.push(str.substring(o, o + chunkSize));
291 | }
292 |
293 | return chunks;
294 | }
295 |
296 | function* processChunk(chunk, stateObject) {
297 | const chars = chunk;
298 | const charsLength = chunk.length;
299 |
300 | for (let i = 0; i < charsLength; i++) {
301 | const c = chars[i];
302 |
303 | stateObject.buffer += c;
304 |
305 | switch (stateObject.state) {
306 | case TEXT:
307 | if (c === '\u001b') {
308 | stateObject.state = BRACKET;
309 | stateObject.buffer = c;
310 | } else {
311 | stateObject.text += c;
312 | }
313 | break;
314 |
315 | case BRACKET:
316 | if (c === '[') {
317 | stateObject.state = CODE;
318 | stateObject.code = '';
319 | stateObject.codes = [];
320 | } else {
321 | stateObject.state = TEXT;
322 | stateObject.text += stateObject.buffer;
323 | }
324 | break;
325 |
326 | case CODE:
327 | if (c >= '0' && c <= '9') {
328 | stateObject.code += c;
329 | } else if (c === ';') {
330 | stateObject.codes.push(new Code(stateObject.code));
331 | stateObject.code = '';
332 | } else if (c === 'm') {
333 | stateObject.code = stateObject.code || '0';
334 | for (const code of stateObject.codes) {
335 | yield new Span(code, stateObject.text);
336 | stateObject.text = '';
337 | }
338 |
339 | yield new Span(new Code(stateObject.code), stateObject.text);
340 | stateObject.text = '';
341 | stateObject.state = TEXT;
342 | } else {
343 | stateObject.state = TEXT;
344 | stateObject.text += stateObject.buffer;
345 | }
346 | }
347 | }
348 | }
349 |
350 | /**
351 | * Parse ansi text
352 | * @param {Generator} rawSpansIterator raw spans iterator
353 | * @return {Generator}
354 | */
355 | function* parseAnsi(rawSpansIterator) {
356 | let color = new Color();
357 | let bgColor = new Color(true /* background */);
358 | let brightness = undefined;
359 | let styles = new Set();
360 |
361 | function reset() {
362 | color = new Color();
363 | bgColor = new Color(true /* background */);
364 | brightness = undefined;
365 | styles.clear();
366 | }
367 |
368 | reset();
369 |
370 | for (const span of rawSpansIterator) {
371 | const c = span.code;
372 |
373 | if (span.text !== '') {
374 | const inverted = styles.has('inverse');
375 | const underline = styles.has('underline') ? 'text-decoration: underline;' : '';
376 | const italic = styles.has('italic') ? 'font-style: italic;' : '';
377 | const bold = brightness === Code.bright ? 'font-weight: bold;' : '';
378 |
379 | const foreColor = color.defaultBrightness(brightness);
380 |
381 | const newSpan = new Span(span.code ? span.code.clone() : undefined, span.text);
382 |
383 | newSpan.css = span.css ? span.css : bold + italic + underline + foreColor.css(inverted) + bgColor.css(inverted);
384 | newSpan.bold = span.bold ? span.bold : !!bold;
385 | newSpan.color = span.color ? span.color : foreColor.clean;
386 | newSpan.bgColor = span.bgColor ? span.bgColor : bgColor.clean;
387 | newSpan.inverse = inverted;
388 | newSpan.italic = !!italic;
389 | newSpan.underline = !!underline;
390 | newSpan.bright = styles.has('bright');
391 | newSpan.dim = styles.has('dim');
392 |
393 | yield newSpan;
394 | }
395 |
396 | if (c.isBrightness) {
397 | brightness = c.value;
398 | continue;
399 | }
400 |
401 | if (span.code.value === undefined) {
402 | continue;
403 | }
404 |
405 | if (span.code.value === Code.reset) {
406 | reset();
407 | continue;
408 | }
409 |
410 | switch (span.code.type) {
411 | case 'color':
412 | case 'colorLight':
413 | color = new Color(false, c.subtype);
414 | break;
415 |
416 | case 'bgColor':
417 | case 'bgColorLight':
418 | bgColor = new Color(true, c.subtype);
419 | break;
420 |
421 | case 'style':
422 | styles.add(c.subtype);
423 | break;
424 | case 'unstyle':
425 | styles.delete(c.subtype);
426 | break;
427 | }
428 | }
429 | }
430 |
431 | /* ------------------------------------------------------------------------ */
432 |
433 | /**
434 | * Represents an ANSI-escaped string.
435 | */
436 | class Colors {
437 | /**
438 | * @param {string} s a string containing ANSI escape codes.
439 | */
440 | constructor(s) {
441 | this.spans = s ? Array.from(rawParse(typeof s === 'string' ? () => s : s)) : [];
442 | }
443 |
444 | get str() {
445 | return this.spans.reduce((str, p) => str + p.text + p.code.str, '');
446 | }
447 |
448 | get parsed() {
449 | const newColors = new Colors();
450 |
451 | newColors.spans = Array.from(parseAnsi(this.spans));
452 |
453 | return newColors;
454 | }
455 |
456 | /* Outputs with Chrome DevTools-compatible format */
457 |
458 | get asChromeConsoleLogArguments() {
459 | const spans = this.parsed.spans;
460 |
461 | return [spans.map((s) => '%c' + s.text).join(''), ...spans.map((s) => s.css)];
462 | }
463 |
464 | get browserConsoleArguments() /* LEGACY, DEPRECATED */ {
465 | return this.asChromeConsoleLogArguments;
466 | }
467 |
468 | /**
469 | * @desc installs String prototype extensions
470 | * @example
471 | * require ('ansicolor').nice
472 | * console.log ('foo'.bright.red)
473 | */
474 | static get nice() {
475 | Colors.names.forEach((k) => {
476 | if (!(k in String.prototype)) {
477 | O.defineProperty(String.prototype, k, {
478 | get: function () {
479 | return Colors[k](this);
480 | }
481 | });
482 | }
483 | });
484 |
485 | return Colors;
486 | }
487 |
488 | /**
489 | * @desc parses a string containing ANSI escape codes
490 | * @return {Colors} parsed representation.
491 | */
492 | static parse(s) {
493 | return new Colors(s).parsed;
494 | }
495 |
496 | /**
497 | *
498 | * @param {string | () => string} s string or a function returning a string (for large strings you may want to use a function to avoid memory issues)
499 | * @returns {Generator} Spans iterator
500 | */
501 | static parseIterator(s) {
502 | return parseAnsi(rawParse(typeof s === 'string' ? () => s : s));
503 | }
504 |
505 | /**
506 | * @desc strips ANSI codes from a string
507 | * @param {string} s a string containing ANSI escape codes.
508 | * @return {string} clean string.
509 | */
510 | static strip(s) {
511 | return s.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]/g, ''); // hope V8 caches the regexp
512 | }
513 |
514 | /**
515 | * @desc checks if a value contains ANSI escape codes
516 | * @param {any} s value to check
517 | * @return {boolean} has codes
518 | */
519 | static isEscaped(s) {
520 | s = String(s);
521 | return Colors.strip(s) !== s;
522 | }
523 |
524 | /**
525 | * @example
526 | * const spans = [...ansi.parse ('\u001b[7m\u001b[7mfoo\u001b[7mbar\u001b[27m')]
527 | */
528 | [Symbol.iterator]() {
529 | return this.spans[Symbol.iterator]();
530 | }
531 |
532 | /**
533 | * @desc This allows an alternative import style, see https://github.com/xpl/ansicolor/issues/7#issuecomment-578923578
534 | * @example
535 | * import { ansicolor, ParsedSpan } from 'ansicolor'
536 | */
537 | static get ansicolor() {
538 | return Colors;
539 | }
540 | }
541 |
542 | /* ------------------------------------------------------------------------ */
543 |
544 | assignStringWrappingAPI(Colors, (str) => str);
545 |
546 | /* ------------------------------------------------------------------------ */
547 |
548 | Colors.names = stringWrappingMethods.map(([k]) => k);
549 |
550 | /* ------------------------------------------------------------------------ */
551 |
552 | Colors.rgb = {
553 | black: [0, 0, 0],
554 | darkGray: [100, 100, 100],
555 | lightGray: [200, 200, 200],
556 | white: [255, 255, 255],
557 |
558 | red: [204, 0, 0],
559 | lightRed: [255, 51, 0],
560 |
561 | green: [0, 204, 0],
562 | lightGreen: [51, 204, 51],
563 |
564 | yellow: [204, 102, 0],
565 | lightYellow: [255, 153, 51],
566 |
567 | blue: [0, 0, 255],
568 | lightBlue: [26, 140, 255],
569 |
570 | magenta: [204, 0, 204],
571 | lightMagenta: [255, 0, 255],
572 |
573 | cyan: [0, 153, 255],
574 | lightCyan: [0, 204, 255]
575 | };
576 |
577 | /* ------------------------------------------------------------------------ */
578 |
579 | module.exports = Colors;
580 |
581 | /* ------------------------------------------------------------------------ */
582 |
--------------------------------------------------------------------------------