├── .travis.yml ├── .gitignore ├── .github └── FUNDING.yml ├── .editorconfig ├── DOCUMENTATION.md ├── LICENSE ├── CONTRIBUTING.md ├── example └── index.js ├── package.json ├── lib ├── index.d.ts └── index.js ├── README.md └── test └── ansi_up-test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | *.log 5 | node_modules 6 | *.env 7 | .DS_Store 8 | package-lock.json 9 | .bloggify/* 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ionicabizau 2 | patreon: ionicabizau 3 | open_collective: ionicabizau 4 | custom: https://www.buymeacoffee.com/h96wwchmy -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=lf 6 | insert_final_newline=true 7 | trim_trailing_whitespace=true 8 | 9 | [{*.css, *.scss, *.js, *.ts, *.html, package.json, *.yml}] 10 | indent_style=space 11 | indent_size=4 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | You can see below the API reference of this module. 4 | 5 | Add several widely used style codes 6 | 7 | ### `processChunk(text, options, markup)` 8 | Processes the current chunk of text. 9 | 10 | #### Params 11 | 12 | - **String** `text`: The input text. 13 | - **Object** `options`: An object containing the following fields: 14 | - `json` (Boolean): If `true`, the result will be an object. 15 | - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. 16 | - **Boolean** `markup`: If false, the colors will not be parsed. 17 | 18 | #### Return 19 | - **Object|String** The result (object if `json` is wanted back or string otherwise). 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-25 Ionică Bizău (https://ionicabizau.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🌟 Contributing 2 | 3 | Want to contribute to this project? Great! Please read these quick steps to streamline the process and avoid unnecessary tasks. ✨ 4 | 5 | ## 💬 Discuss Changes 6 | Start by opening an issue in the repository using the [bug tracker][1]. Describe your proposed contribution or the bug you've found. If relevant, include platform info and screenshots. 🖼️ 7 | 8 | Wait for feedback before proceeding unless the fix is straightforward, like a typo. 📝 9 | 10 | ## 🔧 Fixing Issues 11 | 12 | Fork the project and create a branch for your fix, naming it `some-great-feature` or `some-issue-fix`. Commit changes while following the [code style][2]. If the project has tests, add one. ✅ 13 | 14 | If a `package.json` or `bower.json` exists, add yourself to the `contributors` array; create it if it doesn't. 🙌 15 | 16 | ```json 17 | { 18 | "contributors": [ 19 | "Your Name (http://your.website)" 20 | ] 21 | } 22 | ``` 23 | 24 | ## 📬 Creating a Pull Request 25 | Open a pull request and reference the initial issue (e.g., *fixes #*). Provide a clear title and consider adding visual aids for clarity. 📊 26 | 27 | ## ⏳ Wait for Feedback 28 | Your contributions will be reviewed. If feedback is given, update your branch as needed, and the pull request will auto-update. 🔄 29 | 30 | ## 🎉 Everyone Is Happy! 31 | Your contributions will be merged, and everyone will appreciate your effort! 😄❤️ 32 | 33 | Thanks! 🤩 34 | 35 | [1]: /issues 36 | [2]: https://github.com/IonicaBizau/code-style -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Anser = require("../lib"); 4 | 5 | const txt = "\u001b[38;5;196mHello\u001b[39m \u001b[48;5;226mWorld\u001b[49m"; 6 | 7 | console.log(Anser.ansiToHtml(txt)); 8 | // Hello World 9 | 10 | console.log(Anser.ansiToHtml(txt, { use_classes: true })); 11 | // Hello World 12 | 13 | console.log(Anser.ansiToJson(txt)); 14 | // [ { content: '', 15 | // fg: null, 16 | // bg: null, 17 | // fg_truecolor: null, 18 | // bg_truecolor: null, 19 | // clearLine: undefined, 20 | // decoration: null, 21 | // was_processed: false, 22 | // isEmpty: [Function: isEmpty] }, 23 | // { content: 'Hello', 24 | // fg: '255, 0, 0', 25 | // bg: null, 26 | // fg_truecolor: null, 27 | // bg_truecolor: null, 28 | // clearLine: false, 29 | // decoration: null, 30 | // was_processed: true, 31 | // isEmpty: [Function: isEmpty] }, 32 | // { content: ' ', 33 | // fg: null, 34 | // bg: null, 35 | // fg_truecolor: null, 36 | // bg_truecolor: null, 37 | // clearLine: false, 38 | // decoration: null, 39 | // was_processed: false, 40 | // isEmpty: [Function: isEmpty] }, 41 | // { content: 'World', 42 | // fg: null, 43 | // bg: '255, 255, 0', 44 | // fg_truecolor: null, 45 | // bg_truecolor: null, 46 | // clearLine: false, 47 | // decoration: null, 48 | // was_processed: true, 49 | // isEmpty: [Function: isEmpty] }, 50 | // { content: '', 51 | // fg: null, 52 | // bg: null, 53 | // fg_truecolor: null, 54 | // bg_truecolor: null, 55 | // clearLine: false, 56 | // decoration: null, 57 | // was_processed: false, 58 | // isEmpty: [Function: isEmpty] } ] 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anser", 3 | "version": "2.3.5", 4 | "description": "A low level parser for ANSI sequences.", 5 | "keywords": [ 6 | "ansi", 7 | "html" 8 | ], 9 | "author": "Ionică Bizău (https://ionicabizau.net)", 10 | "main": "lib/index.js", 11 | "types": "lib/index.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/IonicaBizau/anser.git" 15 | }, 16 | "bugs": { 17 | "url": "http://github.com/IonicaBizau/anser/issues" 18 | }, 19 | "scripts": { 20 | "test": "mocha" 21 | }, 22 | "devDependencies": { 23 | "jshint": "^2.13.6", 24 | "jslint": "^0.12.1", 25 | "mocha": "^11.1.0", 26 | "should": "*" 27 | }, 28 | "homepage": "https://github.com/IonicaBizau/anser#readme", 29 | "directories": { 30 | "example": "examples", 31 | "test": "test" 32 | }, 33 | "blah": { 34 | "description": [ 35 | { 36 | "h2": ":rocket: Features" 37 | }, 38 | { 39 | "ul": [ 40 | "Converts text containing [ANSI color escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) into equivalent HTML elements.", 41 | "Allows converting the input into JSON output.", 42 | "HTML escaping", 43 | "Converts links into HTML elements", 44 | "Friendly APIs to use with virtual dom libraries" 45 | ] 46 | } 47 | ], 48 | "example": [ 49 | "When using **TypeScript** without --esModuleInterop enabled you can do the following:", 50 | { 51 | "code": { 52 | "content": [ 53 | "import Anser = require('anser');", 54 | "const txt = \"\\u001b[38;5;196mHello\\u001b[39m \\u001b[48;5;226mWorld\\u001b[49m\";", 55 | "console.log(Anser.ansiToHtml(txt));", 56 | "// Hello World" 57 | ], 58 | "language": "ts" 59 | } 60 | }, 61 | "Or with --esModuleInterop enabled you can do the following:", 62 | { 63 | "code": { 64 | "content": [ 65 | "import Anser from 'anser';", 66 | "const txt = \"\\u001b[38;5;196mHello\\u001b[39m \\u001b[48;5;226mWorld\\u001b[49m\";", 67 | "console.log(Anser.ansiToHtml(txt));", 68 | "// Hello World" 69 | ], 70 | "language": "ts" 71 | } 72 | } 73 | ], 74 | "thanks": "This project is highly based on [`ansi_up`](https://github.com/drudru/ansi_up), by [@drudru](https://github.com/drudru/). Thanks! :cake:" 75 | }, 76 | "contributors": [ 77 | "Mikołaj Kutryj " 78 | ], 79 | "license": "MIT", 80 | "files": [ 81 | "lib/", 82 | "dist/", 83 | "src/", 84 | "index.js", 85 | "index.d.ts" 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Anser 2 | // Project: https://github.com/IonicaBizau/anser 3 | 4 | declare namespace Anser { 5 | type DecorationName = 'bold' | 'dim' | 'italic' | 'underline' | 'blink' | 'reverse' | 'hidden' | 'strikethrough'; 6 | 7 | export interface AnserJsonEntry { 8 | /** The text. */ 9 | content: string; 10 | /** The foreground color. */ 11 | fg: string; 12 | /** The background color. */ 13 | bg: string; 14 | /** The foreground true color (if 16m color is enabled). */ 15 | fg_truecolor: string; 16 | /** The background true color (if 16m color is enabled). */ 17 | bg_truecolor: string; 18 | /** `true` if a carriageReturn \r was fount at end of line. */ 19 | clearLine: boolean; 20 | /** The decoration last declared before the text. */ 21 | decoration: null | DecorationName; 22 | /** All decorations that apply to the text. */ 23 | decorations: Array; 24 | /** `true` if the colors were processed, `false` otherwise. */ 25 | was_processed: boolean; 26 | 27 | /** A function returning `true` if the content is empty, or `false` otherwise. */ 28 | isEmpty(): boolean; 29 | } 30 | 31 | export interface AnserOptions { 32 | /** If `true`, the result will be an object. */ 33 | json?: boolean; 34 | /** If `true`, HTML classes will be appended to the HTML output. */ 35 | use_classes?: boolean; 36 | remove_empty?: boolean; 37 | } 38 | 39 | type OptionsWithJson = AnserOptions & { json: true }; 40 | } 41 | 42 | declare class Anser { 43 | /** 44 | * Escape the input HTML. 45 | * 46 | * This does the minimum escaping of text to make it compliant with HTML. 47 | * In particular, the '&','<', and '>' characters are escaped. This should 48 | * be run prior to `ansiToHtml`. 49 | * 50 | * @param txt The input text (containing the ANSI snippets). 51 | * @returns The escaped html. 52 | */ 53 | static escapeForHtml (txt: string): string; 54 | 55 | /** 56 | * Adds the links in the HTML. 57 | * 58 | * This replaces any links in the text with anchor tags that display the 59 | * link. The links should have at least one whitespace character 60 | * surrounding it. Also, you should apply this after you have run 61 | * `ansiToHtml` on the text. 62 | * 63 | * @param txt The input text. 64 | * @returns The HTML containing the tags (unescaped). 65 | */ 66 | static linkify (txt: string): string; 67 | 68 | /** 69 | * This replaces ANSI terminal escape codes with SPAN tags that wrap the 70 | * content. 71 | * 72 | * This function only interprets ANSI SGR (Select Graphic Rendition) codes 73 | * that can be represented in HTML. 74 | * For example, cursor movement codes are ignored and hidden from output. 75 | * The default style uses colors that are very close to the prescribed 76 | * standard. The standard assumes that the text will have a black 77 | * background. These colors are set as inline styles on the SPAN tags. 78 | * 79 | * Another option is to set `use_classes: true` in the options argument. 80 | * This will instead set classes on the spans so the colors can be set via 81 | * CSS. The class names used are of the format `ansi-*-fg/bg` and 82 | * `ansi-bright-*-fg/bg` where `*` is the color name, 83 | * i.e black/red/green/yellow/blue/magenta/cyan/white. 84 | * 85 | * @param txt The input text. 86 | * @param options The options. 87 | * @returns The HTML output. 88 | */ 89 | static ansiToHtml (txt: string, options?: Anser.AnserOptions): string; 90 | 91 | /** 92 | * Converts ANSI input into JSON output. 93 | * 94 | * @param txt The input text. 95 | * @param options The options. 96 | * @returns The HTML output. 97 | */ 98 | static ansiToJson (txt: string, options?: Anser.AnserOptions): Anser.AnserJsonEntry[]; 99 | 100 | /** 101 | * Converts ANSI input into text output. 102 | * 103 | * @param txt The input text. 104 | * @returns The text output. 105 | */ 106 | static ansiToText (txt: string, options?: Anser.AnserOptions): string; 107 | 108 | /** 109 | * Sets up the palette. 110 | */ 111 | setupPalette (): void; 112 | 113 | /** 114 | * Escapes the input text. 115 | * 116 | * @param txt The input text. 117 | * @returns The escpaed HTML output. 118 | */ 119 | escapeForHtml (txt: string): string; 120 | 121 | /** 122 | * Adds HTML link elements. 123 | * 124 | * @param txt The input text. 125 | * @returns The HTML output containing link elements. 126 | */ 127 | linkify (txt: string): string; 128 | 129 | /** 130 | * Converts ANSI input into HTML output. 131 | * 132 | * @param txt The input text. 133 | * @param options The options. 134 | * @returns The HTML output. 135 | */ 136 | ansiToHtml (txt: string, options?: Anser.AnserOptions): string; 137 | 138 | /** 139 | * Converts ANSI input into HTML output. 140 | * 141 | * @param txt The input text. 142 | * @param options The options. 143 | * @returns The JSON output. 144 | */ 145 | ansiToJson (txt: string, options?: Anser.AnserOptions): Anser.AnserJsonEntry[]; 146 | 147 | /** 148 | * Converts ANSI input into HTML output. 149 | * 150 | * @param txt The input text. 151 | * @returns The text output. 152 | */ 153 | ansiToText (txt: string, options?: Anser.AnserOptions): string; 154 | 155 | /** 156 | * Processes the input. 157 | * 158 | * @param txt The input text. 159 | * @param options The options. 160 | * @param markup If false, the colors will not be parsed. 161 | */ 162 | process (txt: string, options: Anser.OptionsWithJson, markup?: boolean): Anser.AnserJsonEntry[]; 163 | process (txt: string, options?: Anser.AnserOptions, markup?: boolean): string; 164 | 165 | /** 166 | * Processes the current chunk into json output. 167 | * 168 | * @param text The input text. 169 | * @param options The options. 170 | * @param markup If false, the colors will not be parsed. 171 | * @return The JSON output. 172 | */ 173 | processChunkJson (text: string, options?: Anser.AnserOptions, markup?: boolean): Anser.AnserJsonEntry; 174 | 175 | /** 176 | * Processes the current chunk of text. 177 | * 178 | * @param text The input text. 179 | * @param options The options. 180 | * @param markup If false, the colors will not be parsed. 181 | * @return The result (object if `json` is wanted back or string otherwise). 182 | */ 183 | processChunk (text: string, options: Anser.OptionsWithJson, markup?: boolean): Anser.AnserJsonEntry; 184 | processChunk (text: string, options?: Anser.AnserOptions, markup?: boolean): string; 185 | } 186 | 187 | export = Anser; 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | # anser 21 | 22 | [![Support me on Patreon][badge_patreon]][patreon] [![Buy me a book][badge_amazon]][amazon] [![PayPal][badge_paypal_donate]][paypal-donations] [![Ask me anything](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](https://github.com/IonicaBizau/ama) [![Travis](https://img.shields.io/travis/IonicaBizau/anser.svg)](https://travis-ci.org/IonicaBizau/anser/) [![Version](https://img.shields.io/npm/v/anser.svg)](https://www.npmjs.com/package/anser) [![Downloads](https://img.shields.io/npm/dt/anser.svg)](https://www.npmjs.com/package/anser) [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@johnnyb?utm_source=github&utm_medium=button&utm_term=johnnyb&utm_campaign=github) 23 | 24 | Buy Me A Coffee 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | > A low level parser for ANSI sequences. 33 | 34 | 35 | 36 | 37 | 38 | 39 | ## :rocket: Features 40 | 41 | 42 | - Converts text containing [ANSI color escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) into equivalent HTML elements. 43 | - Allows converting the input into JSON output. 44 | - HTML escaping 45 | - Converts links into HTML elements 46 | - Friendly APIs to use with virtual dom libraries 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ## :cloud: Installation 61 | 62 | ```sh 63 | # Using npm 64 | npm install --save anser 65 | 66 | # Using yarn 67 | yarn add anser 68 | ``` 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ## :clipboard: Example 83 | 84 | 85 | 86 | ```js 87 | const Anser = require("anser"); 88 | 89 | const txt = "\u001b[38;5;196mHello\u001b[39m \u001b[48;5;226mWorld\u001b[49m"; 90 | 91 | console.log(Anser.ansiToHtml(txt)); 92 | // Hello World 93 | 94 | console.log(Anser.ansiToHtml(txt, { use_classes: true })); 95 | // Hello World 96 | 97 | console.log(Anser.ansiToJson(txt)); 98 | // [ { content: '', 99 | // fg: null, 100 | // bg: null, 101 | // fg_truecolor: null, 102 | // bg_truecolor: null, 103 | // clearLine: undefined, 104 | // decoration: null, 105 | // was_processed: false, 106 | // isEmpty: [Function: isEmpty] }, 107 | // { content: 'Hello', 108 | // fg: '255, 0, 0', 109 | // bg: null, 110 | // fg_truecolor: null, 111 | // bg_truecolor: null, 112 | // clearLine: false, 113 | // decoration: null, 114 | // was_processed: true, 115 | // isEmpty: [Function: isEmpty] }, 116 | // { content: ' ', 117 | // fg: null, 118 | // bg: null, 119 | // fg_truecolor: null, 120 | // bg_truecolor: null, 121 | // clearLine: false, 122 | // decoration: null, 123 | // was_processed: false, 124 | // isEmpty: [Function: isEmpty] }, 125 | // { content: 'World', 126 | // fg: null, 127 | // bg: '255, 255, 0', 128 | // fg_truecolor: null, 129 | // bg_truecolor: null, 130 | // clearLine: false, 131 | // decoration: null, 132 | // was_processed: true, 133 | // isEmpty: [Function: isEmpty] }, 134 | // { content: '', 135 | // fg: null, 136 | // bg: null, 137 | // fg_truecolor: null, 138 | // bg_truecolor: null, 139 | // clearLine: false, 140 | // decoration: null, 141 | // was_processed: false, 142 | // isEmpty: [Function: isEmpty] } ] 143 | ``` 144 | 145 | 146 | 147 | 148 | 149 | 150 | When using **TypeScript** without --esModuleInterop enabled you can do the following: 151 | ```ts 152 | import Anser = require('anser'); 153 | const txt = "\u001b[38;5;196mHello\u001b[39m \u001b[48;5;226mWorld\u001b[49m"; 154 | console.log(Anser.ansiToHtml(txt)); 155 | // Hello World 156 | ``` 157 | 158 | Or with --esModuleInterop enabled you can do the following: 159 | ```ts 160 | import Anser from 'anser'; 161 | const txt = "\u001b[38;5;196mHello\u001b[39m \u001b[48;5;226mWorld\u001b[49m"; 162 | console.log(Anser.ansiToHtml(txt)); 163 | // Hello World 164 | ``` 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | ## :memo: Documentation 173 | 174 | 175 | Add several widely used style codes 176 | 177 | ### `processChunk(text, options, markup)` 178 | Processes the current chunk of text. 179 | 180 | #### Params 181 | 182 | - **String** `text`: The input text. 183 | - **Object** `options`: An object containing the following fields: 184 | - `json` (Boolean): If `true`, the result will be an object. 185 | - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. 186 | - **Boolean** `markup`: If false, the colors will not be parsed. 187 | 188 | #### Return 189 | - **Object|String** The result (object if `json` is wanted back or string otherwise). 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | ## :question: Get Help 200 | 201 | There are few ways to get help: 202 | 203 | 204 | 205 | 1. Please [post questions on Stack Overflow](https://stackoverflow.com/questions/ask). You can open issues with questions, as long you add a link to your Stack Overflow question. 206 | 2. For bug reports and feature requests, open issues. :bug: 207 | 3. For direct and quick help, you can [use Codementor](https://www.codementor.io/johnnyb). :rocket: 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | ## :yum: How to contribute 223 | Have an idea? Found a bug? See [how to contribute][contributing]. 224 | 225 | 226 | ## :sparkling_heart: Support my projects 227 | I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, 228 | this takes time. You can integrate and use these projects in your applications *for free*! You can even change the source code and redistribute (even resell it). 229 | 230 | However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: 231 | 232 | 233 | - Starring and sharing the projects you like :rocket: 234 | - [![Buy me a book][badge_amazon]][amazon]—I love books! I will remember you after years if you buy me one. :grin: :book: 235 | - [![PayPal][badge_paypal]][paypal-donations]—You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: 236 | - [![Support me on Patreon][badge_patreon]][patreon]—Set up a recurring monthly donation and you will get interesting news about what I'm doing (things that I don't share with everyone). 237 | - **Bitcoin**—You can send me bitcoins at this address (or scanning the code below): `1P9BRsmazNQcuyTxEqveUsnf5CERdq35V6` 238 | 239 | ![](https://i.imgur.com/z6OQI95.png) 240 | 241 | 242 | Thanks! :heart: 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | ## :cake: Thanks 253 | This project is highly based on [`ansi_up`](https://github.com/drudru/ansi_up), by [@drudru](https://github.com/drudru/). Thanks! :cake: 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | ## :scroll: License 271 | 272 | [MIT][license] © [Ionică Bizău][website] 273 | 274 | 275 | 276 | 277 | 278 | 279 | [license]: /LICENSE 280 | [website]: https://ionicabizau.net 281 | [contributing]: /CONTRIBUTING.md 282 | [docs]: /DOCUMENTATION.md 283 | [badge_patreon]: https://ionicabizau.github.io/badges/patreon.svg 284 | [badge_amazon]: https://ionicabizau.github.io/badges/amazon.svg 285 | [badge_paypal]: https://ionicabizau.github.io/badges/paypal.svg 286 | [badge_paypal_donate]: https://ionicabizau.github.io/badges/paypal_donate.svg 287 | [patreon]: https://www.patreon.com/ionicabizau 288 | [amazon]: http://amzn.eu/hRo9sIZ 289 | [paypal-donations]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RVXDDLKKLQRJW 290 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // This file was originally written by @drudru (https://github.com/drudru/ansi_up), MIT, 2011 4 | 5 | const ANSI_COLORS = [ 6 | [ 7 | { color: "0, 0, 0", "class": "ansi-black" } 8 | , { color: "187, 0, 0", "class": "ansi-red" } 9 | , { color: "0, 187, 0", "class": "ansi-green" } 10 | , { color: "187, 187, 0", "class": "ansi-yellow" } 11 | , { color: "0, 0, 187", "class": "ansi-blue" } 12 | , { color: "187, 0, 187", "class": "ansi-magenta" } 13 | , { color: "0, 187, 187", "class": "ansi-cyan" } 14 | , { color: "255,255,255", "class": "ansi-white" } 15 | ] 16 | , [ 17 | { color: "85, 85, 85", "class": "ansi-bright-black" } 18 | , { color: "255, 85, 85", "class": "ansi-bright-red" } 19 | , { color: "0, 255, 0", "class": "ansi-bright-green" } 20 | , { color: "255, 255, 85", "class": "ansi-bright-yellow" } 21 | , { color: "85, 85, 255", "class": "ansi-bright-blue" } 22 | , { color: "255, 85, 255", "class": "ansi-bright-magenta" } 23 | , { color: "85, 255, 255", "class": "ansi-bright-cyan" } 24 | , { color: "255, 255, 255", "class": "ansi-bright-white" } 25 | ] 26 | ]; 27 | // https://datatracker.ietf.org/doc/html/rfc3986#appendix-A 28 | const linkRegex = /(https?:\/\/(?:[A-Za-z0-9#;/?:@=+$',_.!~*()[\]-]|&|%[A-Fa-f0-9]{2})+)/gm; 29 | 30 | class Anser { 31 | 32 | /** 33 | * Anser.escapeForHtml 34 | * Escape the input HTML. 35 | * 36 | * This does the minimum escaping of text to make it compliant with HTML. 37 | * In particular, the '&','<', and '>' characters are escaped. This should 38 | * be run prior to `ansiToHtml`. 39 | * 40 | * @name Anser.escapeForHtml 41 | * @function 42 | * @param {String} txt The input text (containing the ANSI snippets). 43 | * @returns {String} The escaped html. 44 | */ 45 | static escapeForHtml (txt) { 46 | return new Anser().escapeForHtml(txt); 47 | } 48 | 49 | /** 50 | * Anser.linkify 51 | * Adds the links in the HTML. 52 | * 53 | * This replaces any links in the text with anchor tags that display the 54 | * link. You should apply this after you have run `ansiToHtml` on the text. 55 | * 56 | * @name Anser.linkify 57 | * @function 58 | * @param {String} txt The input text. 59 | * @returns {String} The HTML containing the tags (unescaped). 60 | */ 61 | static linkify (txt) { 62 | return new Anser().linkify(txt); 63 | } 64 | 65 | /** 66 | * Anser.ansiToHtml 67 | * This replaces ANSI terminal escape codes with SPAN tags that wrap the 68 | * content. 69 | * 70 | * This function only interprets ANSI SGR (Select Graphic Rendition) codes 71 | * that can be represented in HTML. 72 | * For example, cursor movement codes are ignored and hidden from output. 73 | * The default style uses colors that are very close to the prescribed 74 | * standard. The standard assumes that the text will have a black 75 | * background. These colors are set as inline styles on the SPAN tags. 76 | * 77 | * Another option is to set `use_classes: true` in the options argument. 78 | * This will instead set classes on the spans so the colors can be set via 79 | * CSS. The class names used are of the format `ansi-*-fg/bg` and 80 | * `ansi-bright-*-fg/bg` where `*` is the color name, 81 | * i.e black/red/green/yellow/blue/magenta/cyan/white. 82 | * 83 | * @name Anser.ansiToHtml 84 | * @function 85 | * @param {String} txt The input text. 86 | * @param {Object} options The options passed to the ansiToHTML method. 87 | * @returns {String} The HTML output. 88 | */ 89 | static ansiToHtml (txt, options) { 90 | return new Anser().ansiToHtml(txt, options); 91 | } 92 | 93 | /** 94 | * Anser.ansiToJson 95 | * Converts ANSI input into JSON output. 96 | * 97 | * @name Anser.ansiToJson 98 | * @function 99 | * @param {String} txt The input text. 100 | * @param {Object} options The options passed to the ansiToHTML method. 101 | * @returns {String} The HTML output. 102 | */ 103 | static ansiToJson (txt, options) { 104 | return new Anser().ansiToJson(txt, options); 105 | } 106 | 107 | /** 108 | * Anser.ansiToText 109 | * Converts ANSI input into text output. 110 | * 111 | * @name Anser.ansiToText 112 | * @function 113 | * @param {String} txt The input text. 114 | * @returns {String} The text output. 115 | */ 116 | static ansiToText (txt) { 117 | return new Anser().ansiToText(txt); 118 | } 119 | 120 | /** 121 | * Anser 122 | * The `Anser` class. 123 | * 124 | * @name Anser 125 | * @function 126 | * @returns {Anser} 127 | */ 128 | constructor () { 129 | this.fg = this.bg = this.fg_truecolor = this.bg_truecolor = null; 130 | this.bright = 0; 131 | this.decorations = []; 132 | } 133 | 134 | /** 135 | * setupPalette 136 | * Sets up the palette. 137 | * 138 | * @name setupPalette 139 | * @function 140 | */ 141 | setupPalette () { 142 | this.PALETTE_COLORS = []; 143 | 144 | // Index 0..15 : System color 145 | for (let i = 0; i < 2; ++i) { 146 | for (let j = 0; j < 8; ++j) { 147 | this.PALETTE_COLORS.push(ANSI_COLORS[i][j].color); 148 | } 149 | } 150 | 151 | // Index 16..231 : RGB 6x6x6 152 | // https://gist.github.com/jasonm23/2868981#file-xterm-256color-yaml 153 | let levels = [0, 95, 135, 175, 215, 255]; 154 | let format = (r, g, b) => levels[r] + ", " + levels[g] + ", " + levels[b]; 155 | let r, g, b; 156 | for (let r = 0; r < 6; ++r) { 157 | for (let g = 0; g < 6; ++g) { 158 | for (let b = 0; b < 6; ++b) { 159 | this.PALETTE_COLORS.push(format(r, g, b)); 160 | } 161 | } 162 | } 163 | 164 | // Index 232..255 : Grayscale 165 | let level = 8; 166 | for (let i = 0; i < 24; ++i, level += 10) { 167 | this.PALETTE_COLORS.push(level + ", " + level + ", " + level); 168 | } 169 | } 170 | 171 | /** 172 | * escapeForHtml 173 | * Escapes the input text. 174 | * 175 | * @name escapeForHtml 176 | * @function 177 | * @param {String} txt The input text. 178 | * @returns {String} The escpaed HTML output. 179 | */ 180 | escapeForHtml (txt) { 181 | return txt.replace(/[&<>\"]/gm, str => 182 | str == "&" ? "&" : 183 | str == '"' ? """ : 184 | str == "<" ? "<" : 185 | str == ">" ? ">" : "" 186 | ); 187 | } 188 | 189 | /** 190 | * linkify 191 | * Adds HTML link elements. 192 | * 193 | * @name linkify 194 | * @function 195 | * @param {String} txt The input text. 196 | * @returns {String} The HTML output containing link elements. 197 | */ 198 | linkify (txt) { 199 | return txt.replace(linkRegex, str => `${str}`); 200 | } 201 | 202 | /** 203 | * ansiToHtml 204 | * Converts ANSI input into HTML output. 205 | * 206 | * @name ansiToHtml 207 | * @function 208 | * @param {String} txt The input text. 209 | * @param {Object} options The options passed ot the `process` method. 210 | * @returns {String} The HTML output. 211 | */ 212 | ansiToHtml (txt, options) { 213 | return this.process(txt, options, true); 214 | } 215 | 216 | /** 217 | * ansiToJson 218 | * Converts ANSI input into HTML output. 219 | * 220 | * @name ansiToJson 221 | * @function 222 | * @param {String} txt The input text. 223 | * @param {Object} options The options passed ot the `process` method. 224 | * @returns {String} The JSON output. 225 | */ 226 | ansiToJson (txt, options) { 227 | options = options || {}; 228 | options.json = true; 229 | options.clearLine = false; 230 | return this.process(txt, options, true); 231 | } 232 | 233 | /** 234 | * ansiToText 235 | * Converts ANSI input into HTML output. 236 | * 237 | * @name ansiToText 238 | * @function 239 | * @param {String} txt The input text. 240 | * @returns {String} The text output. 241 | */ 242 | ansiToText (txt) { 243 | return this.process(txt, {}, false); 244 | } 245 | 246 | /** 247 | * process 248 | * Processes the input. 249 | * 250 | * @name process 251 | * @function 252 | * @param {String} txt The input text. 253 | * @param {Object} options An object passed to `processChunk` method, extended with: 254 | * 255 | * - `json` (Boolean): If `true`, the result will be an object. 256 | * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. 257 | * 258 | * @param {Boolean} markup 259 | */ 260 | process (txt, options, markup) { 261 | let self = this; 262 | let raw_text_chunks = txt.split(/\033\[/); 263 | let first_chunk = raw_text_chunks.shift(); // the first chunk is not the result of the split 264 | 265 | if (options === undefined || options === null) { 266 | options = {}; 267 | } 268 | options.clearLine = /\r/.test(txt); // check for Carriage Return 269 | let color_chunks = raw_text_chunks.map(chunk => this.processChunk(chunk, options, markup)) 270 | 271 | if (options && options.json) { 272 | let first = self.processChunkJson(""); 273 | first.content = first_chunk; 274 | first.clearLine = options.clearLine; 275 | color_chunks.unshift(first); 276 | if (options.remove_empty) { 277 | color_chunks = color_chunks.filter(c => !c.isEmpty()); 278 | } 279 | return color_chunks; 280 | } else { 281 | color_chunks.unshift(first_chunk); 282 | } 283 | 284 | return color_chunks.join(""); 285 | } 286 | 287 | /** 288 | * processChunkJson 289 | * Processes the current chunk into json output. 290 | * 291 | * @name processChunkJson 292 | * @function 293 | * @param {String} text The input text. 294 | * @param {Object} options An object containing the following fields: 295 | * 296 | * - `json` (Boolean): If `true`, the result will be an object. 297 | * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. 298 | * 299 | * @param {Boolean} markup If false, the colors will not be parsed. 300 | * @return {Object} The result object: 301 | * 302 | * - `content` (String): The text. 303 | * - `fg` (String|null): The foreground color. 304 | * - `bg` (String|null): The background color. 305 | * - `fg_truecolor` (String|null): The foreground true color (if 16m color is enabled). 306 | * - `bg_truecolor` (String|null): The background true color (if 16m color is enabled). 307 | * - `clearLine` (Boolean): `true` if a carriageReturn \r was fount at end of line. 308 | * - `was_processed` (Bolean): `true` if the colors were processed, `false` otherwise. 309 | * - `isEmpty` (Function): A function returning `true` if the content is empty, or `false` otherwise. 310 | * 311 | */ 312 | processChunkJson (text, options, markup) { 313 | 314 | // Are we using classes or styles? 315 | options = typeof options == "undefined" ? {} : options; 316 | let use_classes = options.use_classes = typeof options.use_classes != "undefined" && options.use_classes; 317 | let key = options.key = use_classes ? "class" : "color"; 318 | 319 | let result = { 320 | content: text 321 | , fg: null 322 | , bg: null 323 | , fg_truecolor: null 324 | , bg_truecolor: null 325 | , isInverted: false 326 | , clearLine: options.clearLine 327 | , decoration: null 328 | , decorations: [] 329 | , was_processed: false 330 | , isEmpty: () => !result.content 331 | }; 332 | 333 | // Each "chunk" is the text after the CSI (ESC + "[") and before the next CSI/EOF. 334 | // 335 | // This regex matches four groups within a chunk. 336 | // 337 | // The first and third groups match code type. 338 | // We supported only SGR command. It has empty first group and "m" in third. 339 | // 340 | // The second group matches all of the number+semicolon command sequences 341 | // before the "m" (or other trailing) character. 342 | // These are the graphics or SGR commands. 343 | // 344 | // The last group is the text (including newlines) that is colored by 345 | // the other group"s commands. 346 | let matches = text.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m); 347 | 348 | if (!matches) return result; 349 | 350 | let orig_txt = result.content = matches[4]; 351 | let nums = matches[2].split(";"); 352 | 353 | // We currently support only "SGR" (Select Graphic Rendition) 354 | // Simply ignore if not a SGR command. 355 | if (matches[1] !== "" || matches[3] !== "m") { 356 | return result; 357 | } 358 | 359 | if (!markup) { 360 | return result; 361 | } 362 | 363 | let self = this; 364 | 365 | while (nums.length > 0) { 366 | let num_str = nums.shift(); 367 | let num = parseInt(num_str); 368 | 369 | if (isNaN(num) || num === 0) { 370 | self.fg = self.bg = null; 371 | self.decorations = []; 372 | } else if (num === 1) { 373 | self.decorations.push("bold"); 374 | } else if (num === 2) { 375 | self.decorations.push("dim"); 376 | // Enable code 2 to get string 377 | } else if (num === 3) { 378 | self.decorations.push("italic"); 379 | } else if (num === 4) { 380 | self.decorations.push("underline"); 381 | } else if (num === 5) { 382 | self.decorations.push("blink"); 383 | } else if (num === 7) { 384 | self.decorations.push("reverse"); 385 | } else if (num === 8) { 386 | self.decorations.push("hidden"); 387 | // Enable code 9 to get strikethrough 388 | } else if (num === 9) { 389 | self.decorations.push("strikethrough"); 390 | /** 391 | * Add several widely used style codes 392 | * @see https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters 393 | */ 394 | } else if (num === 21) { 395 | self.removeDecoration("bold"); 396 | } else if (num === 22) { 397 | self.removeDecoration("bold"); 398 | self.removeDecoration("dim"); 399 | } else if (num === 23) { 400 | self.removeDecoration("italic"); 401 | } else if (num === 24) { 402 | self.removeDecoration("underline"); 403 | } else if (num === 25) { 404 | self.removeDecoration("blink"); 405 | } else if (num === 27) { 406 | self.removeDecoration("reverse"); 407 | } else if (num === 28) { 408 | self.removeDecoration("hidden"); 409 | } else if (num === 29) { 410 | self.removeDecoration("strikethrough"); 411 | } else if (num === 39) { 412 | self.fg = null; 413 | } else if (num === 49) { 414 | self.bg = null; 415 | // Foreground color 416 | } else if ((num >= 30) && (num < 38)) { 417 | self.fg = ANSI_COLORS[0][(num % 10)][key]; 418 | // Foreground bright color 419 | } else if ((num >= 90) && (num < 98)) { 420 | self.fg = ANSI_COLORS[1][(num % 10)][key]; 421 | // Background color 422 | } else if ((num >= 40) && (num < 48)) { 423 | self.bg = ANSI_COLORS[0][(num % 10)][key]; 424 | // Background bright color 425 | } else if ((num >= 100) && (num < 108)) { 426 | self.bg = ANSI_COLORS[1][(num % 10)][key]; 427 | } else if (num === 38 || num === 48) { // extend color (38=fg, 48=bg) 428 | let is_foreground = (num === 38); 429 | if (nums.length >= 1) { 430 | let mode = nums.shift(); 431 | if (mode === "5" && nums.length >= 1) { // palette color 432 | let palette_index = parseInt(nums.shift()); 433 | if (palette_index >= 0 && palette_index <= 255) { 434 | if (!use_classes) { 435 | if (!this.PALETTE_COLORS) { 436 | self.setupPalette(); 437 | } 438 | if (is_foreground) { 439 | self.fg = this.PALETTE_COLORS[palette_index]; 440 | } else { 441 | self.bg = this.PALETTE_COLORS[palette_index]; 442 | } 443 | } else { 444 | let klass = (palette_index >= 16) 445 | ? ("ansi-palette-" + palette_index) 446 | : ANSI_COLORS[palette_index > 7 ? 1 : 0][palette_index % 8]["class"]; 447 | if (is_foreground) { 448 | self.fg = klass; 449 | } else { 450 | self.bg = klass; 451 | } 452 | } 453 | } 454 | } else if(mode === "2" && nums.length >= 3) { // true color 455 | let r = parseInt(nums.shift()); 456 | let g = parseInt(nums.shift()); 457 | let b = parseInt(nums.shift()); 458 | if ((r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255)) { 459 | let color = r + ", " + g + ", " + b; 460 | if (!use_classes) { 461 | if (is_foreground) { 462 | self.fg = color; 463 | } else { 464 | self.bg = color; 465 | } 466 | } else { 467 | if (is_foreground) { 468 | self.fg = "ansi-truecolor"; 469 | self.fg_truecolor = color; 470 | } else { 471 | self.bg = "ansi-truecolor"; 472 | self.bg_truecolor = color; 473 | } 474 | } 475 | } 476 | } 477 | } 478 | } 479 | } 480 | 481 | if ((self.fg === null) && (self.bg === null) && (self.decorations.length === 0)) { 482 | return result; 483 | } else { 484 | let styles = []; 485 | let classes = []; 486 | let data = {}; 487 | 488 | result.fg = self.fg; 489 | result.bg = self.bg; 490 | result.fg_truecolor = self.fg_truecolor; 491 | result.bg_truecolor = self.bg_truecolor; 492 | result.decorations = self.decorations; 493 | result.decoration = self.decorations.slice(-1).pop() || null; 494 | result.was_processed = true; 495 | 496 | return result; 497 | } 498 | } 499 | 500 | /** 501 | * processChunk 502 | * Processes the current chunk of text. 503 | * 504 | * @name processChunk 505 | * @function 506 | * @param {String} text The input text. 507 | * @param {Object} options An object containing the following fields: 508 | * 509 | * - `json` (Boolean): If `true`, the result will be an object. 510 | * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. 511 | * 512 | * @param {Boolean} markup If false, the colors will not be parsed. 513 | * @return {Object|String} The result (object if `json` is wanted back or string otherwise). 514 | */ 515 | processChunk (text, options, markup) { 516 | options = options || {}; 517 | let jsonChunk = this.processChunkJson(text, options, markup); 518 | let use_classes = options.use_classes; 519 | 520 | // "reverse" decoration reverses foreground and background colors 521 | jsonChunk.decorations = jsonChunk.decorations 522 | .filter((decoration) => { 523 | if (decoration === "reverse") { 524 | // when reversing, missing colors are defaulted to black (bg) and white (fg) 525 | if (!jsonChunk.fg) { 526 | jsonChunk.fg = ANSI_COLORS[0][7][use_classes ? "class" : "color"]; 527 | } 528 | if (!jsonChunk.bg) { 529 | jsonChunk.bg = ANSI_COLORS[0][0][use_classes ? "class" : "color"]; 530 | } 531 | let tmpFg = jsonChunk.fg; 532 | jsonChunk.fg = jsonChunk.bg; 533 | jsonChunk.bg = tmpFg; 534 | let tmpFgTrue = jsonChunk.fg_truecolor; 535 | jsonChunk.fg_truecolor = jsonChunk.bg_truecolor; 536 | jsonChunk.bg_truecolor = tmpFgTrue; 537 | jsonChunk.isInverted = true; 538 | return false; 539 | } 540 | return true; 541 | }); 542 | 543 | if (options.json) { return jsonChunk; } 544 | if (jsonChunk.isEmpty()) { return ""; } 545 | if (!jsonChunk.was_processed) { return jsonChunk.content; } 546 | 547 | let colors = []; 548 | let decorations = []; 549 | let textDecorations = []; 550 | let data = {}; 551 | 552 | let render_data = data => { 553 | let fragments = []; 554 | let key; 555 | for (key in data) { 556 | if (data.hasOwnProperty(key)) { 557 | fragments.push("data-" + key + "=\"" + this.escapeForHtml(data[key]) + "\""); 558 | } 559 | } 560 | return fragments.length > 0 ? " " + fragments.join(" ") : ""; 561 | }; 562 | 563 | if (jsonChunk.isInverted) { 564 | data["ansi-is-inverted"] = "true"; 565 | } 566 | 567 | if (jsonChunk.fg) { 568 | if (use_classes) { 569 | colors.push(jsonChunk.fg + "-fg"); 570 | if (jsonChunk.fg_truecolor !== null) { 571 | data["ansi-truecolor-fg"] = jsonChunk.fg_truecolor; 572 | jsonChunk.fg_truecolor = null; 573 | } 574 | } else { 575 | colors.push("color:rgb(" + jsonChunk.fg + ")"); 576 | } 577 | } 578 | 579 | if (jsonChunk.bg) { 580 | if (use_classes) { 581 | colors.push(jsonChunk.bg + "-bg"); 582 | if (jsonChunk.bg_truecolor !== null) { 583 | data["ansi-truecolor-bg"] = jsonChunk.bg_truecolor; 584 | jsonChunk.bg_truecolor = null; 585 | } 586 | } else { 587 | colors.push("background-color:rgb(" + jsonChunk.bg + ")"); 588 | } 589 | } 590 | 591 | jsonChunk.decorations.forEach((decoration) => { 592 | // use classes 593 | if (use_classes) { 594 | decorations.push("ansi-" + decoration); 595 | return; 596 | } 597 | // use styles 598 | if (decoration === "bold") { 599 | decorations.push("font-weight:bold"); 600 | } else if (decoration === "dim") { 601 | decorations.push("opacity:0.5"); 602 | } else if (decoration === "italic") { 603 | decorations.push("font-style:italic"); 604 | } else if (decoration === "hidden") { 605 | decorations.push("visibility:hidden"); 606 | } else if (decoration === "strikethrough") { 607 | textDecorations.push("line-through"); 608 | } else { 609 | // underline and blink are treated here 610 | textDecorations.push(decoration); 611 | } 612 | }); 613 | 614 | if (textDecorations.length) { 615 | decorations.push("text-decoration:" + textDecorations.join(" ")); 616 | } 617 | 618 | if (use_classes) { 619 | return "" + jsonChunk.content + ""; 620 | } else { 621 | return "" + jsonChunk.content + ""; 622 | } 623 | } 624 | 625 | removeDecoration(decoration) { 626 | const index = this.decorations.indexOf(decoration); 627 | 628 | if (index >= 0) { 629 | this.decorations.splice(index, 1); 630 | } 631 | } 632 | } 633 | 634 | module.exports = Anser; 635 | -------------------------------------------------------------------------------- /test/ansi_up-test.js: -------------------------------------------------------------------------------- 1 | // This file was originally written by @drudru (https://github.com/drudru/Anser), MIT, 2011 2 | 3 | const Anser = require(".."); 4 | const should = require("should"); 5 | 6 | describe("Anser", () => { 7 | describe("escapeForHtml", () => { 8 | describe("ampersands", () => { 9 | it("should escape a single ampersand", () => { 10 | const start = "&"; 11 | const expected = "&"; 12 | const l = Anser.escapeForHtml(start); 13 | l.should.eql(expected); 14 | }); 15 | it("should escape some text with ampersands", () => { 16 | const start = "abcd&efgh"; 17 | const expected = "abcd&efgh"; 18 | const l = Anser.escapeForHtml(start); 19 | l.should.eql(expected); 20 | }); 21 | it("should escape multiple ampersands", () => { 22 | const start = " & & "; 23 | const expected = " & & "; 24 | const l = Anser.escapeForHtml(start); 25 | l.should.eql(expected); 26 | }); 27 | it("should escape an already escaped ampersand", () => { 28 | const start = " & "; 29 | const expected = " &amp; "; 30 | const l = Anser.escapeForHtml(start); 31 | l.should.eql(expected); 32 | }); 33 | }); 34 | 35 | describe("less-than", () => { 36 | it("should escape a single less-than", () => { 37 | const start = "<"; 38 | const expected = "<"; 39 | const l = Anser.escapeForHtml(start); 40 | l.should.eql(expected); 41 | }); 42 | 43 | it("should escape some text with less-thans", () => { 44 | const start = "abcd { 51 | const start = " < < "; 52 | const expected = " < < "; 53 | const l = Anser.escapeForHtml(start); 54 | l.should.eql(expected); 55 | }); 56 | }); 57 | 58 | describe("greater-than", () => { 59 | it("should escape a single greater-than", () => { 60 | const start = ">"; 61 | const expected = ">"; 62 | 63 | const l = Anser.escapeForHtml(start); 64 | l.should.eql(expected); 65 | }); 66 | it("should escape some text with greater-thans", () => { 67 | const start = "abcd>efgh"; 68 | const expected = "abcd>efgh"; 69 | const l = Anser.escapeForHtml(start); 70 | l.should.eql(expected); 71 | }); 72 | it("should escape multiple greater-thans", () => { 73 | const start = " > > "; 74 | const expected = " > > "; 75 | const l = Anser.escapeForHtml(start); 76 | l.should.eql(expected); 77 | }); 78 | }); 79 | 80 | describe("mixed characters", () => { 81 | it("should escape a mix of characters that require escaping", () => { 82 | const start = "<&>/\\'\""; 83 | const expected = "<&>/\\'""; 84 | const l = Anser.escapeForHtml(start); 85 | l.should.eql(expected); 86 | }); 87 | }); 88 | }); 89 | 90 | describe("linkify", () => { 91 | it("should linkify a url", () => { 92 | const start = "http://link.to/me"; 93 | const expected = "http://link.to/me"; 94 | const l = Anser.linkify(start); 95 | l.should.eql(expected); 96 | }); 97 | 98 | }); 99 | 100 | describe("ansi to html", () => { 101 | describe("default colors", () => { 102 | it("should transform a foreground to html", () => { 103 | const attr = 0; 104 | const fg = 32; 105 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0m"; 106 | const expected = " " + fg + " "; 107 | const l = Anser.ansiToHtml(start); 108 | l.should.eql(expected); 109 | }); 110 | it("should transform a attr;foreground to html", () => { 111 | const attr = 0; 112 | const fg = 32; 113 | const start = "\x1B[" + attr + ";" + fg + "m " + fg + " \x1B[0m"; 114 | const expected = " " + fg + " "; 115 | const l = Anser.ansiToHtml(start); 116 | l.should.eql(expected); 117 | }); 118 | it("should transform an empty code to a normal/reset html", () => { 119 | const attr = 0; 120 | const fg = 32; 121 | const start = "\x1B[" + attr + ";" + fg + "m " + fg + " \x1B[m x"; 122 | const expected = " " + fg + " x"; 123 | const l = Anser.ansiToHtml(start); 124 | l.should.eql(expected); 125 | }); 126 | it("should transform a bold attr;foreground to html", () => { 127 | const attr = 1; 128 | const fg = 32; 129 | const start = "\x1B[" + attr + ";" + fg + "m " + attr + ";" + fg + " \x1B[0m"; 130 | const expected = " " + attr + ";" + fg + " "; 131 | const l = Anser.ansiToHtml(start); 132 | l.should.eql(expected); 133 | }); 134 | it("should transform a bright-foreground to html", () => { 135 | const fg = 92; 136 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0m"; 137 | const expected = " " + fg + " "; 138 | const l = Anser.ansiToHtml(start); 139 | l.should.eql(expected); 140 | }); 141 | it("should transform a bold attr;background;foreground to html", () => { 142 | const attr = 1; 143 | const fg = 33; 144 | const bg = 42; 145 | const start = "\x1B[" + attr + ";" + bg + ";" + fg + "m " + attr + ";" + bg + ";" + fg + " \x1B[0m"; 146 | const expected = " " + attr + ";" + bg + ";" + fg + " "; 147 | const l = Anser.ansiToHtml(start); 148 | l.should.eql(expected); 149 | }); 150 | it("should transform a bright-background;foreground to html", () => { 151 | const fg = 33; 152 | const bg = 102; 153 | const start = "\x1B[" + bg + ";" + fg + "m " + bg + ";" + fg + " \x1B[0m"; 154 | const expected = " " + bg + ";" + fg + " "; 155 | const l = Anser.ansiToHtml(start); 156 | l.should.eql(expected); 157 | }); 158 | it("should transform a complex multi-line sequence to html", () => { 159 | const attr = 1; 160 | const fg = 32; 161 | const bg = 42; 162 | const start = "\n \x1B[" + fg + "m " + fg + " \x1B[0m \n \x1B[" + bg + "m " + bg + " \x1B[0m \n zimpper "; 163 | const expected = "\n " + fg + " \n " + bg + " \n zimpper "; 164 | const l = Anser.ansiToHtml(start); 165 | l.should.eql(expected); 166 | }); 167 | it("should transform a foreground and background and reset foreground to html", () => { 168 | const fg = 37; 169 | const bg = 42; 170 | const start = "\n\x1B[40m \x1B[49m\x1B[" + fg + ";" + bg + "m " + bg + " \x1B[39m foobar "; 171 | const expected = "\n " + bg + " foobar "; 172 | const l = Anser.ansiToHtml(start); 173 | l.should.eql(expected); 174 | }); 175 | it("should transform a foreground and background and reset background to html", () => { 176 | const fg = 37; 177 | const bg = 42; 178 | const start = "\n\x1B[40m \x1B[49m\x1B[" + fg + ";" + bg + "m " + fg + " \x1B[49m foobar "; 179 | const expected = "\n " + fg + " foobar "; 180 | const l = Anser.ansiToHtml(start); 181 | l.should.eql(expected); 182 | }); 183 | it("should transform a foreground and background and reset them to html", () => { 184 | const fg = 37; 185 | const bg = 42; 186 | const start = "\n\x1B[40m \x1B[49m\x1B[" + fg + ";" + bg + "m " + fg + ";" + bg + " \x1B[39;49m foobar "; 187 | const expected = "\n " + fg + ";" + bg + " foobar "; 188 | const l = Anser.ansiToHtml(start); 189 | l.should.eql(expected); 190 | }); 191 | describe("transform extend colors (palette)", () => { 192 | it("system color, foreground", () => { 193 | const start = "\x1B[38;5;1m" + "red" + "\x1B[0m"; 194 | const expected = "red"; 195 | const l = Anser.ansiToHtml(start); 196 | l.should.eql(expected); 197 | }); 198 | it("system color, foreground (bright)", () => { 199 | const start = "\x1B[38;5;9m" + "red" + "\x1B[0m"; 200 | const expected = "red"; 201 | const l = Anser.ansiToHtml(start); 202 | l.should.eql(expected); 203 | }); 204 | it("system color, background", () => { 205 | const start = "\x1B[48;5;1m" + "red" + "\x1B[0m"; 206 | const expected = "red"; 207 | const l = Anser.ansiToHtml(start); 208 | l.should.eql(expected); 209 | }); 210 | it("system color, background (bright)", () => { 211 | const start = "\x1B[48;5;9m" + "red" + "\x1B[0m"; 212 | const expected = "red"; 213 | const l = Anser.ansiToHtml(start); 214 | l.should.eql(expected); 215 | }); 216 | it("palette, foreground", () => { 217 | const start = "\x1B[38;5;171m" + "foo" + "\x1B[0m"; 218 | const expected = "foo"; 219 | const l = Anser.ansiToHtml(start); 220 | l.should.eql(expected); 221 | }); 222 | it("palette, background", () => { 223 | const start = "\x1B[48;5;171m" + "foo" + "\x1B[0m"; 224 | const expected = "foo"; 225 | const l = Anser.ansiToHtml(start); 226 | l.should.eql(expected); 227 | }); 228 | it("combination of bold and palette", () => { 229 | const start = "\x1B[1;38;5;171m" + "foo" + "\x1B[0m"; 230 | const expected = "foo"; 231 | const l = Anser.ansiToHtml(start); 232 | l.should.eql(expected); 233 | }); 234 | it("combination of palette and bold", () => { 235 | const start = "\x1B[38;5;171;1m" + "foo" + "\x1B[0m"; 236 | const expected = "foo"; 237 | const l = Anser.ansiToHtml(start); 238 | l.should.eql(expected); 239 | }); 240 | it("grayscale, foreground (first: 232)", () => { 241 | const start = "\x1B[38;5;232m" + "gray" + "\x1B[0m"; 242 | const expected = "gray"; 243 | const l = Anser.ansiToHtml(start); 244 | l.should.eql(expected); 245 | }); 246 | it("grayscale, foreground (middle: 240)", () => { 247 | const start = "\x1B[38;5;240m" + "gray" + "\x1B[0m"; 248 | const expected = "gray"; 249 | const l = Anser.ansiToHtml(start); 250 | l.should.eql(expected); 251 | }); 252 | it("grayscale, foreground (last: 255)", () => { 253 | const start = "\x1B[38;5;255m" + "gray" + "\x1B[0m"; 254 | const expected = "gray"; 255 | const l = Anser.ansiToHtml(start); 256 | l.should.eql(expected); 257 | }); 258 | it("grayscale, background", () => { 259 | const start = "\x1B[48;5;240m" + "gray" + "\x1B[0m"; 260 | const expected = "gray"; 261 | const l = Anser.ansiToHtml(start); 262 | l.should.eql(expected); 263 | }); 264 | }); 265 | 266 | describe("transform extend colors (true color)", () => { 267 | it("foreground", () => { 268 | const start = "\x1B[38;2;42;142;242m" + "foo" + "\x1B[0m"; 269 | const expected = "foo"; 270 | const l = Anser.ansiToHtml(start); 271 | l.should.eql(expected); 272 | }); 273 | it("background", () => { 274 | const start = "\x1B[48;2;42;142;242m" + "foo" + "\x1B[0m"; 275 | const expected = "foo"; 276 | const l = Anser.ansiToHtml(start); 277 | l.should.eql(expected); 278 | }); 279 | it("both foreground and background", () => { 280 | const start = "\x1B[38;2;42;142;242;48;2;1;2;3m" + "foo" + "\x1B[0m"; 281 | const expected = "foo"; 282 | const l = Anser.ansiToHtml(start); 283 | l.should.eql(expected); 284 | }); 285 | }); 286 | }); 287 | 288 | describe("themed colors", () => { 289 | it("should transform a foreground to html", () => { 290 | const attr = 0; 291 | const fg = 32; 292 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0m"; 293 | const expected = " " + fg + " "; 294 | const l = Anser.ansiToHtml(start, {use_classes: true}); 295 | l.should.eql(expected); 296 | }); 297 | 298 | 299 | it("should transform a attr;foreground to html", () => { 300 | const attr = 0; 301 | const fg = 32; 302 | const start = "\x1B[" + attr + ";" + fg + "m " + fg + " \x1B[0m"; 303 | 304 | const expected = " " + fg + " "; 305 | 306 | const l = Anser.ansiToHtml(start, {use_classes: true}); 307 | l.should.eql(expected); 308 | }); 309 | 310 | it("should transform a bold attr;foreground to html", () => { 311 | const attr = 1; 312 | const fg = 32; 313 | const start = "\x1B[" + attr + ";" + fg + "m " + attr + ";" + fg + " \x1B[0m"; 314 | const expected = " " + attr + ";" + fg + " "; 315 | const l = Anser.ansiToHtml(start, {use_classes: true}); 316 | l.should.eql(expected); 317 | }); 318 | 319 | it("should transform a bold attr;background;foreground to html", () => { 320 | const attr = 1; 321 | const fg = 33; 322 | const bg = 42; 323 | const start = "\x1B[" + attr + ";" + bg + ";" + fg + "m " + attr + ";" + bg + ";" + fg + " \x1B[0m"; 324 | const expected = " " + attr + ";" + bg + ";" + fg + " "; 325 | const l = Anser.ansiToHtml(start, {use_classes: true}); 326 | l.should.eql(expected); 327 | }); 328 | 329 | it("should transform a complex multi-line sequence to html", () => { 330 | const attr = 1; 331 | const fg = 32; 332 | const bg = 42; 333 | const start = "\n \x1B[" + fg + "m " + fg + " \x1B[0m \n \x1B[" + bg + "m " + bg + " \x1B[0m \n zimpper "; 334 | const expected = "\n " + fg + " \n " + bg + " \n zimpper "; 335 | const l = Anser.ansiToHtml(start, {use_classes: true}); 336 | l.should.eql(expected); 337 | }); 338 | 339 | describe("transform extend colors (palette)", () => { 340 | it("system color, foreground", () => { 341 | const start = "\x1B[38;5;1m" + "red" + "\x1B[0m"; 342 | const expected = "red"; 343 | const l = Anser.ansiToHtml(start, {use_classes: true}); 344 | l.should.eql(expected); 345 | }); 346 | 347 | it("system color, foreground (bright)", () => { 348 | const start = "\x1B[38;5;9m" + "red" + "\x1B[0m"; 349 | const expected = "red"; 350 | const l = Anser.ansiToHtml(start, {use_classes: true}); 351 | l.should.eql(expected); 352 | }); 353 | 354 | it("system color, background", () => { 355 | const start = "\x1B[48;5;1m" + "red" + "\x1B[0m"; 356 | const expected = "red"; 357 | const l = Anser.ansiToHtml(start, {use_classes: true}); 358 | l.should.eql(expected); 359 | }); 360 | 361 | it("system color, background (bright)", () => { 362 | const start = "\x1B[48;5;9m" + "red" + "\x1B[0m"; 363 | const expected = "red"; 364 | const l = Anser.ansiToHtml(start, {use_classes: true}); 365 | l.should.eql(expected); 366 | }); 367 | 368 | it("palette, foreground", () => { 369 | const start = "\x1B[38;5;171m" + "foo" + "\x1B[0m"; 370 | const expected = "foo"; 371 | const l = Anser.ansiToHtml(start, {use_classes: true}); 372 | l.should.eql(expected); 373 | }); 374 | 375 | it("palette, background", () => { 376 | const start = "\x1B[48;5;171m" + "foo" + "\x1B[0m"; 377 | const expected = "foo"; 378 | const l = Anser.ansiToHtml(start, {use_classes: true}); 379 | l.should.eql(expected); 380 | }); 381 | 382 | it("combination of bold and palette", () => { 383 | const start = "\x1B[1;38;5;171m" + "foo" + "\x1B[0m"; 384 | const expected = "foo"; 385 | const l = Anser.ansiToHtml(start, {use_classes: true}); 386 | l.should.eql(expected); 387 | }); 388 | 389 | it("combination of palette and bold", () => { 390 | const start = "\x1B[38;5;171;1m" + "foo" + "\x1B[0m"; 391 | const expected = "foo"; 392 | const l = Anser.ansiToHtml(start, {use_classes: true}); 393 | l.should.eql(expected); 394 | }); 395 | }); 396 | 397 | describe("transform extend colors (true color)", () => { 398 | it("foreground", () => { 399 | const start = "\x1B[38;2;42;142;242m" + "foo" + "\x1B[0m"; 400 | const expected = "foo"; 401 | const l = Anser.ansiToHtml(start, {use_classes: true}); 402 | l.should.eql(expected); 403 | }); 404 | it("background", () => { 405 | const start = "\x1B[48;2;42;142;242m" + "foo" + "\x1B[0m"; 406 | const expected = "foo"; 407 | const l = Anser.ansiToHtml(start, {use_classes: true}); 408 | l.should.eql(expected); 409 | }); 410 | it("both foreground and background", () => { 411 | const start = "\x1B[38;2;42;142;242;48;2;1;2;3m" + "foo" + "\x1B[0m"; 412 | const expected = "foo"; 413 | const l = Anser.ansiToHtml(start, {use_classes: true}); 414 | l.should.eql(expected); 415 | }); 416 | }); 417 | }); 418 | 419 | describe("transform text attributes", () => { 420 | describe("default", () => { 421 | it("underline", () => { 422 | const start = "\x1B[4m" + "foo" + "\x1B[0m"; 423 | const expected = "foo"; 424 | const l = Anser.ansiToHtml(start); 425 | l.should.eql(expected); 426 | }); 427 | it("blink", () => { 428 | const start = "\x1B[5m" + "foo" + "\x1B[0m"; 429 | const expected = "foo"; 430 | const l = Anser.ansiToHtml(start); 431 | l.should.eql(expected); 432 | }); 433 | it("dim", () => { 434 | const start = "\x1B[2m" + "foo" + "\x1B[0m"; 435 | const expected = "foo"; 436 | const l = Anser.ansiToHtml(start); 437 | l.should.eql(expected); 438 | }); 439 | }); 440 | 441 | describe("with classes", () => { 442 | it("underline", () => { 443 | const start = "\x1B[4m" + "foo" + "\x1B[0m"; 444 | const expected = "foo"; 445 | const l = Anser.ansiToHtml(start, {use_classes: true}); 446 | l.should.eql(expected); 447 | }); 448 | it("blink", () => { 449 | const start = "\x1B[5m" + "foo" + "\x1B[0m"; 450 | const expected = "foo"; 451 | const l = Anser.ansiToHtml(start, {use_classes: true}); 452 | l.should.eql(expected); 453 | }); 454 | it("dim", () => { 455 | const start = "\x1B[2m" + "foo" + "\x1B[0m"; 456 | const expected = "foo"; 457 | const l = Anser.ansiToHtml(start, {use_classes: true}); 458 | l.should.eql(expected); 459 | }); 460 | }); 461 | }); 462 | 463 | describe("use multiple text styles", () => { 464 | describe("default", () => { 465 | it("underline, blinking, bold, blue text on red background", () => { 466 | const start = "\x1B[4m" + "\x1B[5m" + "\x1B[1;34m" + "\x1B[41m" + "foo" + "\x1B[0m" + "bar"; 467 | const expected = 'foobar'; 468 | const l = Anser.ansiToHtml(start); 469 | l.should.eql(expected); 470 | }); 471 | }); 472 | describe("with classes", () => { 473 | it("underline, blinking, bold, blue text on red background", () => { 474 | const start = "\x1B[4m" + "\x1B[5m" + "\x1B[1;34m" + "\x1B[41m" + "foo" + "\x1B[0m" + "bar"; 475 | const expected = 'foobar'; 476 | const l = Anser.ansiToHtml(start, {use_classes: true}); 477 | l.should.eql(expected); 478 | }); 479 | }); 480 | }); 481 | 482 | describe("reverse/inverse colors", () => { 483 | describe("default", () => { 484 | it("blue text on red background, to red text on blue background", () => { 485 | const start = "\x1B[7m" + "\x1B[34m" + "\x1B[41m" + "foo" + "\x1B[0m"; 486 | const expected = 'foo'; 487 | const l = Anser.ansiToHtml(start); 488 | l.should.eql(expected); 489 | }); 490 | it("blue text inversed to be blue background", () => { 491 | const start = "\x1B[7m" + "\x1B[34m" + "foo" + "\x1B[0m"; 492 | const expected = 'foo'; 493 | const l = Anser.ansiToHtml(start); 494 | l.should.eql(expected); 495 | }); 496 | it("red background inversed to be red text", () => { 497 | const start = "\x1B[7m" + "\x1B[41m" + "foo" + "\x1B[0m"; 498 | const expected = 'foo'; 499 | const l = Anser.ansiToHtml(start); 500 | l.should.eql(expected); 501 | }); 502 | }); 503 | describe("with classes", () => { 504 | it("blue text on red background, to red text on blue background", () => { 505 | const start = "\x1B[7m" + "\x1B[34m" + "\x1B[41m" + "foo" + "\x1B[0m"; 506 | const expected = 'foo'; 507 | const l = Anser.ansiToHtml(start, {use_classes: true}); 508 | l.should.eql(expected); 509 | }); 510 | it("blue text inversed to be blue background", () => { 511 | const start = "\x1B[7m" + "\x1B[34m" + "foo" + "\x1B[0m"; 512 | const expected = 'foo'; 513 | const l = Anser.ansiToHtml(start, {use_classes: true}); 514 | l.should.eql(expected); 515 | }); 516 | it("red background inversed to be red text", () => { 517 | const start = "\x1B[7m" + "\x1B[41m" + "foo" + "\x1B[0m"; 518 | const expected = 'foo'; 519 | const l = Anser.ansiToHtml(start, {use_classes: true}); 520 | l.should.eql(expected); 521 | }); 522 | }); 523 | }); 524 | 525 | describe("reset text styles with specific codes", () => { 526 | describe("default", () => { 527 | it('Normal intensity', () => { 528 | const start = "\x1B[1m" + "\x1B[2m" + "foo" + "\x1B[22m" + "bar"; 529 | const expected = 'foobar'; 530 | const l = Anser.ansiToHtml(start); 531 | l.should.eql(expected); 532 | }); 533 | }); 534 | describe("with classes", () => { 535 | it('Normal intensity', () => { 536 | const start = "\x1B[1m" + "\x1B[2m" + "foo" + "\x1B[22m" + "bar"; 537 | const expected = 'foobar'; 538 | const l = Anser.ansiToHtml(start, {use_classes: true}); 539 | l.should.eql(expected); 540 | }); 541 | }); 542 | }); 543 | 544 | describe("ignore unsupported CSI", () => { 545 | it("should correctly convert a string similar to CSI", () => { 546 | // https://github.com/drudru/Anser/pull/15 547 | // "[1;31m" is a plain text. not an escape sequence. 548 | const start = "foo\x1B[1@bar[1;31mbaz\x1B[0m"; 549 | const l = Anser.ansiToHtml(start); 550 | 551 | // is all plain texts exist? 552 | l.should.containEql("foo"); 553 | l.should.containEql("bar"); 554 | l.should.containEql("baz"); 555 | l.should.containEql("1;31m"); 556 | }); 557 | it("(italic)", () => { 558 | const start = "foo\x1B[3mbar\x1B[0mbaz"; 559 | const l = Anser.ansiToHtml(start); 560 | l.should.eql("foobarbaz"); 561 | }); 562 | it("(cursor-up)", () => { 563 | const start = "foo\x1B[1Abar"; 564 | const l = Anser.ansiToHtml(start); 565 | l.should.eql("foobar"); 566 | }); 567 | it("(scroll-left)", () => { 568 | // [1 @ (including ascii space) 569 | const start = "foo\x1B[1 @bar"; 570 | const l = Anser.ansiToHtml(start); 571 | l.should.eql("foobar"); 572 | }); 573 | it("(DECMC)", () => { 574 | const start = "foo\x1B[?11ibar"; 575 | const l = Anser.ansiToHtml(start); 576 | l.should.eql("foobar"); 577 | }); 578 | it("(RLIMGCP)", () => { 579 | const start = "foo\x1B[ { 584 | const start = "foo\x1B[61;0\"pbar" 585 | const l = Anser.ansiToHtml(start); 586 | l.should.eql("foobar"); 587 | }); 588 | }); 589 | }); 590 | 591 | describe("ansi to text", () => { 592 | it("should remove color sequence", () => { 593 | const start = "foo \x1B[1;32mbar\x1B[0m baz"; 594 | const l = Anser.ansiToText(start); 595 | l.should.eql("foo bar baz"); 596 | }); 597 | it("should remove unsupported sequence", () => { 598 | const start = "foo \x1B[1Abar"; 599 | const l = Anser.ansiToText(start); 600 | l.should.eql("foo bar"); 601 | }); 602 | it("should keep multiline", () => { 603 | const start = "foo \x1B[1;32mbar\nbaz\x1B[0m qux"; 604 | const l = Anser.ansiToText(start); 605 | l.should.eql("foo bar\nbaz qux"); 606 | }); 607 | }); 608 | 609 | describe("ansi to json", () => { 610 | it("should convert ansi to json", () => { 611 | const attr = 0; 612 | const fg = 32; 613 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0m"; 614 | const output = Anser.ansiToJson(start, { 615 | remove_empty: true 616 | }); 617 | output[0].fg.should.eql("0, 187, 0"); 618 | output[0].content.should.eql(" 32 "); 619 | }); 620 | it("should convert ansi with carriageReturn to json with positive clearLine flag", () => { 621 | const attr = 0; 622 | const fg = 32; 623 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0mfoo\r"; 624 | const output = Anser.ansiToJson(start, { 625 | remove_empty: true 626 | }); 627 | output[0].fg.should.eql("0, 187, 0"); 628 | output[0].content.should.eql(" 32 "); 629 | output[0].clearLine.should.eql(true); 630 | output[1].clearLine.should.eql(true); 631 | }); 632 | it("should convert ansi without carriageReturn to json with negative clearLine flag", () => { 633 | const attr = 0; 634 | const fg = 32; 635 | const start = "\x1B[" + fg + "m " + fg + " \x1B[0mfoo"; 636 | const output = Anser.ansiToJson(start, { 637 | remove_empty: true 638 | }); 639 | output[0].fg.should.eql("0, 187, 0"); 640 | output[0].content.should.eql(" 32 "); 641 | output[0].clearLine.should.eql(false); 642 | output[1].clearLine.should.eql(false); 643 | }); 644 | it("should convert ansi with carriageReturn at start of line to json with positive clearLine", () => { 645 | const start = "\r zc = 9 zm = 9 zs = 3 f = 4"; 646 | const output = Anser.ansiToJson(start, { 647 | remove_empty: true 648 | }); 649 | output[0].clearLine.should.eql(true); 650 | }); 651 | }); 652 | }); 653 | --------------------------------------------------------------------------------