index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. "use strict";
  2. // This file was originally written by @drudru (https://github.com/drudru/ansi_up), MIT, 2011
  3. 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; }; }();
  4. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  5. 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" }]];
  6. var Anser = function () {
  7. _createClass(Anser, null, [{
  8. key: "escapeForHtml",
  9. /**
  10. * Anser.escapeForHtml
  11. * Escape the input HTML.
  12. *
  13. * This does the minimum escaping of text to make it compliant with HTML.
  14. * In particular, the '&','<', and '>' characters are escaped. This should
  15. * be run prior to `ansiToHtml`.
  16. *
  17. * @name Anser.escapeForHtml
  18. * @function
  19. * @param {String} txt The input text (containing the ANSI snippets).
  20. * @returns {String} The escaped html.
  21. */
  22. value: function escapeForHtml(txt) {
  23. return new Anser().escapeForHtml(txt);
  24. }
  25. /**
  26. * Anser.linkify
  27. * Adds the links in the HTML.
  28. *
  29. * This replaces any links in the text with anchor tags that display the
  30. * link. The links should have at least one whitespace character
  31. * surrounding it. Also, you should apply this after you have run
  32. * `ansiToHtml` on the text.
  33. *
  34. * @name Anser.linkify
  35. * @function
  36. * @param {String} txt The input text.
  37. * @returns {String} The HTML containing the <a> tags (unescaped).
  38. */
  39. }, {
  40. key: "linkify",
  41. value: function linkify(txt) {
  42. return new Anser().linkify(txt);
  43. }
  44. /**
  45. * Anser.ansiToHtml
  46. * This replaces ANSI terminal escape codes with SPAN tags that wrap the
  47. * content.
  48. *
  49. * This function only interprets ANSI SGR (Select Graphic Rendition) codes
  50. * that can be represented in HTML.
  51. * For example, cursor movement codes are ignored and hidden from output.
  52. * The default style uses colors that are very close to the prescribed
  53. * standard. The standard assumes that the text will have a black
  54. * background. These colors are set as inline styles on the SPAN tags.
  55. *
  56. * Another option is to set `use_classes: true` in the options argument.
  57. * This will instead set classes on the spans so the colors can be set via
  58. * CSS. The class names used are of the format `ansi-*-fg/bg` and
  59. * `ansi-bright-*-fg/bg` where `*` is the color name,
  60. * i.e black/red/green/yellow/blue/magenta/cyan/white.
  61. *
  62. * @name Anser.ansiToHtml
  63. * @function
  64. * @param {String} txt The input text.
  65. * @param {Object} options The options passed to the ansiToHTML method.
  66. * @returns {String} The HTML output.
  67. */
  68. }, {
  69. key: "ansiToHtml",
  70. value: function ansiToHtml(txt, options) {
  71. return new Anser().ansiToHtml(txt, options);
  72. }
  73. /**
  74. * Anser.ansiToJson
  75. * Converts ANSI input into JSON output.
  76. *
  77. * @name Anser.ansiToJson
  78. * @function
  79. * @param {String} txt The input text.
  80. * @param {Object} options The options passed to the ansiToHTML method.
  81. * @returns {String} The HTML output.
  82. */
  83. }, {
  84. key: "ansiToJson",
  85. value: function ansiToJson(txt, options) {
  86. return new Anser().ansiToJson(txt, options);
  87. }
  88. /**
  89. * Anser.ansiToText
  90. * Converts ANSI input into text output.
  91. *
  92. * @name Anser.ansiToText
  93. * @function
  94. * @param {String} txt The input text.
  95. * @returns {String} The text output.
  96. */
  97. }, {
  98. key: "ansiToText",
  99. value: function ansiToText(txt) {
  100. return new Anser().ansiToText(txt);
  101. }
  102. /**
  103. * Anser
  104. * The `Anser` class.
  105. *
  106. * @name Anser
  107. * @function
  108. * @returns {Anser}
  109. */
  110. }]);
  111. function Anser() {
  112. _classCallCheck(this, Anser);
  113. this.fg = this.bg = this.fg_truecolor = this.bg_truecolor = null;
  114. this.bright = 0;
  115. }
  116. /**
  117. * setupPalette
  118. * Sets up the palette.
  119. *
  120. * @name setupPalette
  121. * @function
  122. */
  123. _createClass(Anser, [{
  124. key: "setupPalette",
  125. value: function setupPalette() {
  126. this.PALETTE_COLORS = [];
  127. // Index 0..15 : System color
  128. for (var i = 0; i < 2; ++i) {
  129. for (var j = 0; j < 8; ++j) {
  130. this.PALETTE_COLORS.push(ANSI_COLORS[i][j].color);
  131. }
  132. }
  133. // Index 16..231 : RGB 6x6x6
  134. // https://gist.github.com/jasonm23/2868981#file-xterm-256color-yaml
  135. var levels = [0, 95, 135, 175, 215, 255];
  136. var format = function format(r, g, b) {
  137. return levels[r] + ", " + levels[g] + ", " + levels[b];
  138. };
  139. var r = void 0,
  140. g = void 0,
  141. b = void 0;
  142. for (var _r = 0; _r < 6; ++_r) {
  143. for (var _g = 0; _g < 6; ++_g) {
  144. for (var _b = 0; _b < 6; ++_b) {
  145. this.PALETTE_COLORS.push(format(_r, _g, _b));
  146. }
  147. }
  148. }
  149. // Index 232..255 : Grayscale
  150. var level = 8;
  151. for (var _i = 0; _i < 24; ++_i, level += 10) {
  152. this.PALETTE_COLORS.push(format(level, level, level));
  153. }
  154. }
  155. /**
  156. * escapeForHtml
  157. * Escapes the input text.
  158. *
  159. * @name escapeForHtml
  160. * @function
  161. * @param {String} txt The input text.
  162. * @returns {String} The escpaed HTML output.
  163. */
  164. }, {
  165. key: "escapeForHtml",
  166. value: function escapeForHtml(txt) {
  167. return txt.replace(/[&<>]/gm, function (str) {
  168. return str == "&" ? "&amp;" : str == "<" ? "&lt;" : str == ">" ? "&gt;" : "";
  169. });
  170. }
  171. /**
  172. * linkify
  173. * Adds HTML link elements.
  174. *
  175. * @name linkify
  176. * @function
  177. * @param {String} txt The input text.
  178. * @returns {String} The HTML output containing link elements.
  179. */
  180. }, {
  181. key: "linkify",
  182. value: function linkify(txt) {
  183. return txt.replace(/(https?:\/\/[^\s]+)/gm, function (str) {
  184. return "<a href=\"" + str + "\">" + str + "</a>";
  185. });
  186. }
  187. /**
  188. * ansiToHtml
  189. * Converts ANSI input into HTML output.
  190. *
  191. * @name ansiToHtml
  192. * @function
  193. * @param {String} txt The input text.
  194. * @param {Object} options The options passed ot the `process` method.
  195. * @returns {String} The HTML output.
  196. */
  197. }, {
  198. key: "ansiToHtml",
  199. value: function ansiToHtml(txt, options) {
  200. return this.process(txt, options, true);
  201. }
  202. /**
  203. * ansiToJson
  204. * Converts ANSI input into HTML output.
  205. *
  206. * @name ansiToJson
  207. * @function
  208. * @param {String} txt The input text.
  209. * @param {Object} options The options passed ot the `process` method.
  210. * @returns {String} The JSON output.
  211. */
  212. }, {
  213. key: "ansiToJson",
  214. value: function ansiToJson(txt, options) {
  215. options = options || {};
  216. options.json = true;
  217. options.clearLine = false;
  218. return this.process(txt, options, true);
  219. }
  220. /**
  221. * ansiToText
  222. * Converts ANSI input into HTML output.
  223. *
  224. * @name ansiToText
  225. * @function
  226. * @param {String} txt The input text.
  227. * @returns {String} The text output.
  228. */
  229. }, {
  230. key: "ansiToText",
  231. value: function ansiToText(txt) {
  232. return this.process(txt, {}, false);
  233. }
  234. /**
  235. * process
  236. * Processes the input.
  237. *
  238. * @name process
  239. * @function
  240. * @param {String} txt The input text.
  241. * @param {Object} options An object passed to `processChunk` method, extended with:
  242. *
  243. * - `json` (Boolean): If `true`, the result will be an object.
  244. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output.
  245. *
  246. * @param {Boolean} markup
  247. */
  248. }, {
  249. key: "process",
  250. value: function process(txt, options, markup) {
  251. var _this = this;
  252. var self = this;
  253. var raw_text_chunks = txt.split(/\033\[/);
  254. var first_chunk = raw_text_chunks.shift(); // the first chunk is not the result of the split
  255. if (options === undefined || options === null) {
  256. options = {};
  257. }
  258. options.clearLine = /\r/.test(txt); // check for Carriage Return
  259. var color_chunks = raw_text_chunks.map(function (chunk) {
  260. return _this.processChunk(chunk, options, markup);
  261. });
  262. if (options && options.json) {
  263. var first = self.processChunkJson("");
  264. first.content = first_chunk;
  265. first.clearLine = options.clearLine;
  266. color_chunks.unshift(first);
  267. if (options.remove_empty) {
  268. color_chunks = color_chunks.filter(function (c) {
  269. return !c.isEmpty();
  270. });
  271. }
  272. return color_chunks;
  273. } else {
  274. color_chunks.unshift(first_chunk);
  275. }
  276. return color_chunks.join("");
  277. }
  278. /**
  279. * processChunkJson
  280. * Processes the current chunk into json output.
  281. *
  282. * @name processChunkJson
  283. * @function
  284. * @param {String} text The input text.
  285. * @param {Object} options An object containing the following fields:
  286. *
  287. * - `json` (Boolean): If `true`, the result will be an object.
  288. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output.
  289. *
  290. * @param {Boolean} markup If false, the colors will not be parsed.
  291. * @return {Object} The result object:
  292. *
  293. * - `content` (String): The text.
  294. * - `fg` (String|null): The foreground color.
  295. * - `bg` (String|null): The background color.
  296. * - `fg_truecolor` (String|null): The foreground true color (if 16m color is enabled).
  297. * - `bg_truecolor` (String|null): The background true color (if 16m color is enabled).
  298. * - `clearLine` (Boolean): `true` if a carriageReturn \r was fount at end of line.
  299. * - `was_processed` (Bolean): `true` if the colors were processed, `false` otherwise.
  300. * - `isEmpty` (Function): A function returning `true` if the content is empty, or `false` otherwise.
  301. *
  302. */
  303. }, {
  304. key: "processChunkJson",
  305. value: function processChunkJson(text, options, markup) {
  306. // Are we using classes or styles?
  307. options = typeof options == "undefined" ? {} : options;
  308. var use_classes = options.use_classes = typeof options.use_classes != "undefined" && options.use_classes;
  309. var key = options.key = use_classes ? "class" : "color";
  310. var result = {
  311. content: text,
  312. fg: null,
  313. bg: null,
  314. fg_truecolor: null,
  315. bg_truecolor: null,
  316. clearLine: options.clearLine,
  317. decoration: null,
  318. was_processed: false,
  319. isEmpty: function isEmpty() {
  320. return !result.content;
  321. }
  322. };
  323. // Each "chunk" is the text after the CSI (ESC + "[") and before the next CSI/EOF.
  324. //
  325. // This regex matches four groups within a chunk.
  326. //
  327. // The first and third groups match code type.
  328. // We supported only SGR command. It has empty first group and "m" in third.
  329. //
  330. // The second group matches all of the number+semicolon command sequences
  331. // before the "m" (or other trailing) character.
  332. // These are the graphics or SGR commands.
  333. //
  334. // The last group is the text (including newlines) that is colored by
  335. // the other group"s commands.
  336. var matches = text.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m);
  337. if (!matches) return result;
  338. var orig_txt = result.content = matches[4];
  339. var nums = matches[2].split(";");
  340. // We currently support only "SGR" (Select Graphic Rendition)
  341. // Simply ignore if not a SGR command.
  342. if (matches[1] !== "" || matches[3] !== "m") {
  343. return result;
  344. }
  345. if (!markup) {
  346. return result;
  347. }
  348. var self = this;
  349. self.decoration = null;
  350. while (nums.length > 0) {
  351. var num_str = nums.shift();
  352. var num = parseInt(num_str);
  353. if (isNaN(num) || num === 0) {
  354. self.fg = self.bg = self.decoration = null;
  355. } else if (num === 1) {
  356. self.decoration = "bold";
  357. } else if (num === 2) {
  358. self.decoration = "dim";
  359. // Enable code 2 to get string
  360. } else if (num == 3) {
  361. self.decoration = "italic";
  362. } else if (num == 4) {
  363. self.decoration = "underline";
  364. } else if (num == 5) {
  365. self.decoration = "blink";
  366. } else if (num === 7) {
  367. self.decoration = "reverse";
  368. } else if (num === 8) {
  369. self.decoration = "hidden";
  370. // Enable code 9 to get strikethrough
  371. } else if (num === 9) {
  372. self.decoration = "strikethrough";
  373. } else if (num == 39) {
  374. self.fg = null;
  375. } else if (num == 49) {
  376. self.bg = null;
  377. // Foreground color
  378. } else if (num >= 30 && num < 38) {
  379. self.fg = ANSI_COLORS[0][num % 10][key];
  380. // Foreground bright color
  381. } else if (num >= 90 && num < 98) {
  382. self.fg = ANSI_COLORS[1][num % 10][key];
  383. // Background color
  384. } else if (num >= 40 && num < 48) {
  385. self.bg = ANSI_COLORS[0][num % 10][key];
  386. // Background bright color
  387. } else if (num >= 100 && num < 108) {
  388. self.bg = ANSI_COLORS[1][num % 10][key];
  389. } else if (num === 38 || num === 48) {
  390. // extend color (38=fg, 48=bg)
  391. var is_foreground = num === 38;
  392. if (nums.length >= 1) {
  393. var mode = nums.shift();
  394. if (mode === "5" && nums.length >= 1) {
  395. // palette color
  396. var palette_index = parseInt(nums.shift());
  397. if (palette_index >= 0 && palette_index <= 255) {
  398. if (!use_classes) {
  399. if (!this.PALETTE_COLORS) {
  400. self.setupPalette();
  401. }
  402. if (is_foreground) {
  403. self.fg = this.PALETTE_COLORS[palette_index];
  404. } else {
  405. self.bg = this.PALETTE_COLORS[palette_index];
  406. }
  407. } else {
  408. var klass = palette_index >= 16 ? "ansi-palette-" + palette_index : ANSI_COLORS[palette_index > 7 ? 1 : 0][palette_index % 8]["class"];
  409. if (is_foreground) {
  410. self.fg = klass;
  411. } else {
  412. self.bg = klass;
  413. }
  414. }
  415. }
  416. } else if (mode === "2" && nums.length >= 3) {
  417. // true color
  418. var r = parseInt(nums.shift());
  419. var g = parseInt(nums.shift());
  420. var b = parseInt(nums.shift());
  421. if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
  422. var color = r + ", " + g + ", " + b;
  423. if (!use_classes) {
  424. if (is_foreground) {
  425. self.fg = color;
  426. } else {
  427. self.bg = color;
  428. }
  429. } else {
  430. if (is_foreground) {
  431. self.fg = "ansi-truecolor";
  432. self.fg_truecolor = color;
  433. } else {
  434. self.bg = "ansi-truecolor";
  435. self.bg_truecolor = color;
  436. }
  437. }
  438. }
  439. }
  440. }
  441. }
  442. }
  443. if (self.fg === null && self.bg === null && self.decoration === null) {
  444. return result;
  445. } else {
  446. var styles = [];
  447. var classes = [];
  448. var data = {};
  449. result.fg = self.fg;
  450. result.bg = self.bg;
  451. result.fg_truecolor = self.fg_truecolor;
  452. result.bg_truecolor = self.bg_truecolor;
  453. result.decoration = self.decoration;
  454. result.was_processed = true;
  455. return result;
  456. }
  457. }
  458. /**
  459. * processChunk
  460. * Processes the current chunk of text.
  461. *
  462. * @name processChunk
  463. * @function
  464. * @param {String} text The input text.
  465. * @param {Object} options An object containing the following fields:
  466. *
  467. * - `json` (Boolean): If `true`, the result will be an object.
  468. * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output.
  469. *
  470. * @param {Boolean} markup If false, the colors will not be parsed.
  471. * @return {Object|String} The result (object if `json` is wanted back or string otherwise).
  472. */
  473. }, {
  474. key: "processChunk",
  475. value: function processChunk(text, options, markup) {
  476. var _this2 = this;
  477. var self = this;
  478. options = options || {};
  479. var jsonChunk = this.processChunkJson(text, options, markup);
  480. if (options.json) {
  481. return jsonChunk;
  482. }
  483. if (jsonChunk.isEmpty()) {
  484. return "";
  485. }
  486. if (!jsonChunk.was_processed) {
  487. return jsonChunk.content;
  488. }
  489. var use_classes = options.use_classes;
  490. var styles = [];
  491. var classes = [];
  492. var data = {};
  493. var render_data = function render_data(data) {
  494. var fragments = [];
  495. var key = void 0;
  496. for (key in data) {
  497. if (data.hasOwnProperty(key)) {
  498. fragments.push("data-" + key + "=\"" + _this2.escapeForHtml(data[key]) + "\"");
  499. }
  500. }
  501. return fragments.length > 0 ? " " + fragments.join(" ") : "";
  502. };
  503. if (jsonChunk.fg) {
  504. if (use_classes) {
  505. classes.push(jsonChunk.fg + "-fg");
  506. if (jsonChunk.fg_truecolor !== null) {
  507. data["ansi-truecolor-fg"] = jsonChunk.fg_truecolor;
  508. jsonChunk.fg_truecolor = null;
  509. }
  510. } else {
  511. styles.push("color:rgb(" + jsonChunk.fg + ")");
  512. }
  513. }
  514. if (jsonChunk.bg) {
  515. if (use_classes) {
  516. classes.push(jsonChunk.bg + "-bg");
  517. if (jsonChunk.bg_truecolor !== null) {
  518. data["ansi-truecolor-bg"] = jsonChunk.bg_truecolor;
  519. jsonChunk.bg_truecolor = null;
  520. }
  521. } else {
  522. styles.push("background-color:rgb(" + jsonChunk.bg + ")");
  523. }
  524. }
  525. if (jsonChunk.decoration) {
  526. if (use_classes) {
  527. classes.push("ansi-" + jsonChunk.decoration);
  528. } else if (jsonChunk.decoration === "bold") {
  529. styles.push("font-weight:bold");
  530. } else if (jsonChunk.decoration === "dim") {
  531. styles.push("opacity:0.5");
  532. } else if (jsonChunk.decoration === "italic") {
  533. styles.push("font-style:italic");
  534. // underline and blink are treated bellow
  535. } else if (jsonChunk.decoration === "reverse") {
  536. styles.push("filter:invert(100%)");
  537. } else if (jsonChunk.decoration === "hidden") {
  538. styles.push("visibility:hidden");
  539. } else if (jsonChunk.decoration === "strikethrough") {
  540. styles.push("text-decoration:line-through");
  541. } else {
  542. styles.push("text-decoration:" + jsonChunk.decoration);
  543. }
  544. }
  545. if (use_classes) {
  546. return "<span class=\"" + classes.join(" ") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
  547. } else {
  548. return "<span style=\"" + styles.join(";") + "\"" + render_data(data) + ">" + jsonChunk.content + "</span>";
  549. }
  550. }
  551. }]);
  552. return Anser;
  553. }();
  554. ;
  555. module.exports = Anser;