"use strict"; // This file was originally written by @drudru (https://github.com/drudru/ansi_up), MIT, 2011 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var ANSI_COLORS = [[{ color: "0, 0, 0", "class": "ansi-black" }, { color: "187, 0, 0", "class": "ansi-red" }, { color: "0, 187, 0", "class": "ansi-green" }, { color: "187, 187, 0", "class": "ansi-yellow" }, { color: "0, 0, 187", "class": "ansi-blue" }, { color: "187, 0, 187", "class": "ansi-magenta" }, { color: "0, 187, 187", "class": "ansi-cyan" }, { color: "255,255,255", "class": "ansi-white" }], [{ color: "85, 85, 85", "class": "ansi-bright-black" }, { color: "255, 85, 85", "class": "ansi-bright-red" }, { color: "0, 255, 0", "class": "ansi-bright-green" }, { color: "255, 255, 85", "class": "ansi-bright-yellow" }, { color: "85, 85, 255", "class": "ansi-bright-blue" }, { color: "255, 85, 255", "class": "ansi-bright-magenta" }, { color: "85, 255, 255", "class": "ansi-bright-cyan" }, { color: "255, 255, 255", "class": "ansi-bright-white" }]]; var Anser = function () { _createClass(Anser, null, [{ key: "escapeForHtml", /** * Anser.escapeForHtml * Escape the input HTML. * * This does the minimum escaping of text to make it compliant with HTML. * In particular, the '&','<', and '>' characters are escaped. This should * be run prior to `ansiToHtml`. * * @name Anser.escapeForHtml * @function * @param {String} txt The input text (containing the ANSI snippets). * @returns {String} The escaped html. */ value: function escapeForHtml(txt) { return new Anser().escapeForHtml(txt); } /** * Anser.linkify * Adds the links in the HTML. * * This replaces any links in the text with anchor tags that display the * link. The links should have at least one whitespace character * surrounding it. Also, you should apply this after you have run * `ansiToHtml` on the text. * * @name Anser.linkify * @function * @param {String} txt The input text. * @returns {String} The HTML containing the tags (unescaped). */ }, { key: "linkify", value: function linkify(txt) { return new Anser().linkify(txt); } /** * Anser.ansiToHtml * This replaces ANSI terminal escape codes with SPAN tags that wrap the * content. * * This function only interprets ANSI SGR (Select Graphic Rendition) codes * that can be represented in HTML. * For example, cursor movement codes are ignored and hidden from output. * The default style uses colors that are very close to the prescribed * standard. The standard assumes that the text will have a black * background. These colors are set as inline styles on the SPAN tags. * * Another option is to set `use_classes: true` in the options argument. * This will instead set classes on the spans so the colors can be set via * CSS. The class names used are of the format `ansi-*-fg/bg` and * `ansi-bright-*-fg/bg` where `*` is the color name, * i.e black/red/green/yellow/blue/magenta/cyan/white. * * @name Anser.ansiToHtml * @function * @param {String} txt The input text. * @param {Object} options The options passed to the ansiToHTML method. * @returns {String} The HTML output. */ }, { key: "ansiToHtml", value: function ansiToHtml(txt, options) { return new Anser().ansiToHtml(txt, options); } /** * Anser.ansiToJson * Converts ANSI input into JSON output. * * @name Anser.ansiToJson * @function * @param {String} txt The input text. * @param {Object} options The options passed to the ansiToHTML method. * @returns {String} The HTML output. */ }, { key: "ansiToJson", value: function ansiToJson(txt, options) { return new Anser().ansiToJson(txt, options); } /** * Anser.ansiToText * Converts ANSI input into text output. * * @name Anser.ansiToText * @function * @param {String} txt The input text. * @returns {String} The text output. */ }, { key: "ansiToText", value: function ansiToText(txt) { return new Anser().ansiToText(txt); } /** * Anser * The `Anser` class. * * @name Anser * @function * @returns {Anser} */ }]); function Anser() { _classCallCheck(this, Anser); this.fg = this.bg = this.fg_truecolor = this.bg_truecolor = null; this.bright = 0; } /** * setupPalette * Sets up the palette. * * @name setupPalette * @function */ _createClass(Anser, [{ key: "setupPalette", value: function setupPalette() { this.PALETTE_COLORS = []; // Index 0..15 : System color for (var i = 0; i < 2; ++i) { for (var j = 0; j < 8; ++j) { this.PALETTE_COLORS.push(ANSI_COLORS[i][j].color); } } // Index 16..231 : RGB 6x6x6 // https://gist.github.com/jasonm23/2868981#file-xterm-256color-yaml var levels = [0, 95, 135, 175, 215, 255]; var format = function format(r, g, b) { return levels[r] + ", " + levels[g] + ", " + levels[b]; }; var r = void 0, g = void 0, b = void 0; for (var _r = 0; _r < 6; ++_r) { for (var _g = 0; _g < 6; ++_g) { for (var _b = 0; _b < 6; ++_b) { this.PALETTE_COLORS.push(format(_r, _g, _b)); } } } // Index 232..255 : Grayscale var level = 8; for (var _i = 0; _i < 24; ++_i, level += 10) { this.PALETTE_COLORS.push(format(level, level, level)); } } /** * escapeForHtml * Escapes the input text. * * @name escapeForHtml * @function * @param {String} txt The input text. * @returns {String} The escpaed HTML output. */ }, { key: "escapeForHtml", value: function escapeForHtml(txt) { return txt.replace(/[&<>]/gm, function (str) { return str == "&" ? "&" : str == "<" ? "<" : str == ">" ? ">" : ""; }); } /** * linkify * Adds HTML link elements. * * @name linkify * @function * @param {String} txt The input text. * @returns {String} The HTML output containing link elements. */ }, { key: "linkify", value: function linkify(txt) { return txt.replace(/(https?:\/\/[^\s]+)/gm, function (str) { return "" + str + ""; }); } /** * ansiToHtml * Converts ANSI input into HTML output. * * @name ansiToHtml * @function * @param {String} txt The input text. * @param {Object} options The options passed ot the `process` method. * @returns {String} The HTML output. */ }, { key: "ansiToHtml", value: function ansiToHtml(txt, options) { return this.process(txt, options, true); } /** * ansiToJson * Converts ANSI input into HTML output. * * @name ansiToJson * @function * @param {String} txt The input text. * @param {Object} options The options passed ot the `process` method. * @returns {String} The JSON output. */ }, { key: "ansiToJson", value: function ansiToJson(txt, options) { options = options || {}; options.json = true; options.clearLine = false; return this.process(txt, options, true); } /** * ansiToText * Converts ANSI input into HTML output. * * @name ansiToText * @function * @param {String} txt The input text. * @returns {String} The text output. */ }, { key: "ansiToText", value: function ansiToText(txt) { return this.process(txt, {}, false); } /** * process * Processes the input. * * @name process * @function * @param {String} txt The input text. * @param {Object} options An object passed to `processChunk` method, extended with: * * - `json` (Boolean): If `true`, the result will be an object. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. * * @param {Boolean} markup */ }, { key: "process", value: function process(txt, options, markup) { var _this = this; var self = this; var raw_text_chunks = txt.split(/\033\[/); var first_chunk = raw_text_chunks.shift(); // the first chunk is not the result of the split if (options === undefined || options === null) { options = {}; } options.clearLine = /\r/.test(txt); // check for Carriage Return var color_chunks = raw_text_chunks.map(function (chunk) { return _this.processChunk(chunk, options, markup); }); if (options && options.json) { var first = self.processChunkJson(""); first.content = first_chunk; first.clearLine = options.clearLine; color_chunks.unshift(first); if (options.remove_empty) { color_chunks = color_chunks.filter(function (c) { return !c.isEmpty(); }); } return color_chunks; } else { color_chunks.unshift(first_chunk); } return color_chunks.join(""); } /** * processChunkJson * Processes the current chunk into json output. * * @name processChunkJson * @function * @param {String} text The input text. * @param {Object} options An object containing the following fields: * * - `json` (Boolean): If `true`, the result will be an object. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. * * @param {Boolean} markup If false, the colors will not be parsed. * @return {Object} The result object: * * - `content` (String): The text. * - `fg` (String|null): The foreground color. * - `bg` (String|null): The background color. * - `fg_truecolor` (String|null): The foreground true color (if 16m color is enabled). * - `bg_truecolor` (String|null): The background true color (if 16m color is enabled). * - `clearLine` (Boolean): `true` if a carriageReturn \r was fount at end of line. * - `was_processed` (Bolean): `true` if the colors were processed, `false` otherwise. * - `isEmpty` (Function): A function returning `true` if the content is empty, or `false` otherwise. * */ }, { key: "processChunkJson", value: function processChunkJson(text, options, markup) { // Are we using classes or styles? options = typeof options == "undefined" ? {} : options; var use_classes = options.use_classes = typeof options.use_classes != "undefined" && options.use_classes; var key = options.key = use_classes ? "class" : "color"; var result = { content: text, fg: null, bg: null, fg_truecolor: null, bg_truecolor: null, clearLine: options.clearLine, decoration: null, was_processed: false, isEmpty: function isEmpty() { return !result.content; } }; // Each "chunk" is the text after the CSI (ESC + "[") and before the next CSI/EOF. // // This regex matches four groups within a chunk. // // The first and third groups match code type. // We supported only SGR command. It has empty first group and "m" in third. // // The second group matches all of the number+semicolon command sequences // before the "m" (or other trailing) character. // These are the graphics or SGR commands. // // The last group is the text (including newlines) that is colored by // the other group"s commands. var matches = text.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m); if (!matches) return result; var orig_txt = result.content = matches[4]; var nums = matches[2].split(";"); // We currently support only "SGR" (Select Graphic Rendition) // Simply ignore if not a SGR command. if (matches[1] !== "" || matches[3] !== "m") { return result; } if (!markup) { return result; } var self = this; self.decoration = null; while (nums.length > 0) { var num_str = nums.shift(); var num = parseInt(num_str); if (isNaN(num) || num === 0) { self.fg = self.bg = self.decoration = null; } else if (num === 1) { self.decoration = "bold"; } else if (num === 2) { self.decoration = "dim"; // Enable code 2 to get string } else if (num == 3) { self.decoration = "italic"; } else if (num == 4) { self.decoration = "underline"; } else if (num == 5) { self.decoration = "blink"; } else if (num === 7) { self.decoration = "reverse"; } else if (num === 8) { self.decoration = "hidden"; // Enable code 9 to get strikethrough } else if (num === 9) { self.decoration = "strikethrough"; } else if (num == 39) { self.fg = null; } else if (num == 49) { self.bg = null; // Foreground color } else if (num >= 30 && num < 38) { self.fg = ANSI_COLORS[0][num % 10][key]; // Foreground bright color } else if (num >= 90 && num < 98) { self.fg = ANSI_COLORS[1][num % 10][key]; // Background color } else if (num >= 40 && num < 48) { self.bg = ANSI_COLORS[0][num % 10][key]; // Background bright color } else if (num >= 100 && num < 108) { self.bg = ANSI_COLORS[1][num % 10][key]; } else if (num === 38 || num === 48) { // extend color (38=fg, 48=bg) var is_foreground = num === 38; if (nums.length >= 1) { var mode = nums.shift(); if (mode === "5" && nums.length >= 1) { // palette color var palette_index = parseInt(nums.shift()); if (palette_index >= 0 && palette_index <= 255) { if (!use_classes) { if (!this.PALETTE_COLORS) { self.setupPalette(); } if (is_foreground) { self.fg = this.PALETTE_COLORS[palette_index]; } else { self.bg = this.PALETTE_COLORS[palette_index]; } } else { var klass = palette_index >= 16 ? "ansi-palette-" + palette_index : ANSI_COLORS[palette_index > 7 ? 1 : 0][palette_index % 8]["class"]; if (is_foreground) { self.fg = klass; } else { self.bg = klass; } } } } else if (mode === "2" && nums.length >= 3) { // true color var r = parseInt(nums.shift()); var g = parseInt(nums.shift()); var b = parseInt(nums.shift()); if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { var color = r + ", " + g + ", " + b; if (!use_classes) { if (is_foreground) { self.fg = color; } else { self.bg = color; } } else { if (is_foreground) { self.fg = "ansi-truecolor"; self.fg_truecolor = color; } else { self.bg = "ansi-truecolor"; self.bg_truecolor = color; } } } } } } } if (self.fg === null && self.bg === null && self.decoration === null) { return result; } else { var styles = []; var classes = []; var data = {}; result.fg = self.fg; result.bg = self.bg; result.fg_truecolor = self.fg_truecolor; result.bg_truecolor = self.bg_truecolor; result.decoration = self.decoration; result.was_processed = true; return result; } } /** * processChunk * Processes the current chunk of text. * * @name processChunk * @function * @param {String} text The input text. * @param {Object} options An object containing the following fields: * * - `json` (Boolean): If `true`, the result will be an object. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output. * * @param {Boolean} markup If false, the colors will not be parsed. * @return {Object|String} The result (object if `json` is wanted back or string otherwise). */ }, { key: "processChunk", value: function processChunk(text, options, markup) { var _this2 = this; var self = this; options = options || {}; var jsonChunk = this.processChunkJson(text, options, markup); if (options.json) { return jsonChunk; } if (jsonChunk.isEmpty()) { return ""; } if (!jsonChunk.was_processed) { return jsonChunk.content; } var use_classes = options.use_classes; var styles = []; var classes = []; var data = {}; var render_data = function render_data(data) { var fragments = []; var key = void 0; for (key in data) { if (data.hasOwnProperty(key)) { fragments.push("data-" + key + "=\"" + _this2.escapeForHtml(data[key]) + "\""); } } return fragments.length > 0 ? " " + fragments.join(" ") : ""; }; if (jsonChunk.fg) { if (use_classes) { classes.push(jsonChunk.fg + "-fg"); if (jsonChunk.fg_truecolor !== null) { data["ansi-truecolor-fg"] = jsonChunk.fg_truecolor; jsonChunk.fg_truecolor = null; } } else { styles.push("color:rgb(" + jsonChunk.fg + ")"); } } if (jsonChunk.bg) { if (use_classes) { classes.push(jsonChunk.bg + "-bg"); if (jsonChunk.bg_truecolor !== null) { data["ansi-truecolor-bg"] = jsonChunk.bg_truecolor; jsonChunk.bg_truecolor = null; } } else { styles.push("background-color:rgb(" + jsonChunk.bg + ")"); } } if (jsonChunk.decoration) { if (use_classes) { classes.push("ansi-" + jsonChunk.decoration); } else if (jsonChunk.decoration === "bold") { styles.push("font-weight:bold"); } else if (jsonChunk.decoration === "dim") { styles.push("opacity:0.5"); } else if (jsonChunk.decoration === "italic") { styles.push("font-style:italic"); // underline and blink are treated bellow } else if (jsonChunk.decoration === "reverse") { styles.push("filter:invert(100%)"); } else if (jsonChunk.decoration === "hidden") { styles.push("visibility:hidden"); } else if (jsonChunk.decoration === "strikethrough") { styles.push("text-decoration:line-through"); } else { styles.push("text-decoration:" + jsonChunk.decoration); } } if (use_classes) { return "" + jsonChunk.content + ""; } else { return "" + jsonChunk.content + ""; } } }]); return Anser; }(); ; module.exports = Anser;