/* jslint node: true */ 'use strict'; const ANSI = require('./ansi_term.js'); const { getPredefinedMCIValue } = require('./predefined_mci.js'); // deps const _ = require('lodash'); exports.stripMciColorCodes = stripMciColorCodes; exports.pipeStringLength = pipeStringLength; exports.pipeToAnsi = exports.renegadeToAnsi = renegadeToAnsi; exports.controlCodesToAnsi = controlCodesToAnsi; // :TODO: Not really happy with the module name of "color_codes". Would like something better ... control_code_string? function stripMciColorCodes(s) { return s.replace(/\|[A-Z\d]{2}/g, ''); } function pipeStringLength(s) { return stripMciColorCodes(s).length; } function ansiSgrFromRenegadeColorCode(cc) { return ANSI.sgr( { 0: ['reset', 'black'], 1: ['reset', 'blue'], 2: ['reset', 'green'], 3: ['reset', 'cyan'], 4: ['reset', 'red'], 5: ['reset', 'magenta'], 6: ['reset', 'yellow'], 7: ['reset', 'white'], 8: ['bold', 'black'], 9: ['bold', 'blue'], 10: ['bold', 'green'], 11: ['bold', 'cyan'], 12: ['bold', 'red'], 13: ['bold', 'magenta'], 14: ['bold', 'yellow'], 15: ['bold', 'white'], 16: ['blackBG'], 17: ['blueBG'], 18: ['greenBG'], 19: ['cyanBG'], 20: ['redBG'], 21: ['magentaBG'], 22: ['yellowBG'], 23: ['whiteBG'], 24: ['blink', 'blackBG'], 25: ['blink', 'blueBG'], 26: ['blink', 'greenBG'], 27: ['blink', 'cyanBG'], 28: ['blink', 'redBG'], 29: ['blink', 'magentaBG'], 30: ['blink', 'yellowBG'], 31: ['blink', 'whiteBG'], }[cc] || 'normal' ); } function ansiSgrFromCnetStyleColorCode(cc) { return ANSI.sgr( { c0: ['reset', 'black'], c1: ['reset', 'red'], c2: ['reset', 'green'], c3: ['reset', 'yellow'], c4: ['reset', 'blue'], c5: ['reset', 'magenta'], c6: ['reset', 'cyan'], c7: ['reset', 'white'], c8: ['bold', 'black'], c9: ['bold', 'red'], ca: ['bold', 'green'], cb: ['bold', 'yellow'], cc: ['bold', 'blue'], cd: ['bold', 'magenta'], ce: ['bold', 'cyan'], cf: ['bold', 'white'], z0: ['blackBG'], z1: ['redBG'], z2: ['greenBG'], z3: ['yellowBG'], z4: ['blueBG'], z5: ['magentaBG'], z6: ['cyanBG'], z7: ['whiteBG'], }[cc] || 'normal' ); } function renegadeToAnsi(s, client) { if (-1 == s.indexOf('|')) { return s; // no pipe codes present } let result = ''; const re = /\|(?:(C[FBUD])([0-9]{1,2})|([0-9]{2})|([A-Z]{2})|(\|))/g; let m; let lastIndex = 0; while ((m = re.exec(s))) { if (m[3]) { // |## color const val = parseInt(m[3], 10); const attr = ansiSgrFromRenegadeColorCode(val); result += s.substr(lastIndex, m.index - lastIndex) + attr; } else if (m[4] || m[1]) { // |AA MCI code or |Cx## movement where ## is in m[1] let val = getPredefinedMCIValue(client, m[4] || m[1], m[2]); val = _.isString(val) ? val : m[0]; // value itself or literal result += s.substr(lastIndex, m.index - lastIndex) + val; } else if (m[5]) { // || -- literal '|', that is. result += '|'; } lastIndex = re.lastIndex; } return 0 === result.length ? s : result + s.substr(lastIndex); } // // Converts various control codes popular in BBS packages // to ANSI escape sequences. Additionally supports ENiGMA style // MCI codes. // // Supported control code formats: // * Renegade : |## // * PCBoard : @X## where the first number/char is BG color, and second is FG // * WildCat! : @##@ the same as PCBoard without the X prefix, but with a @ suffix // * WWIV : ^# // * CNET Control-Y: AKA Y-Style -- 0x19## where ## is a specific set of codes (older format) // * CNET Control-Q: AKA Q-style -- 0x11##} where ## is a specific set of codes (newer format) // // TODO: Add Synchronet and Celerity format support // // Resources: // * http://wiki.synchro.net/custom:colors // * https://archive.org/stream/C-Net_Pro_3.0_1994_Perspective_Software/C-Net_Pro_3.0_1994_Perspective_Software_djvu.txt // function controlCodesToAnsi(s, client) { const RE = /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)|(\x19(c[0-9a-f]|z[0-7]|n1|f1|q1)|\x19)|(\x11(c[0-9a-f]|z[0-7]|n1|f1|q1)}|\x11)/g; // eslint-disable-line no-control-regex let m; let result = ''; let lastIndex = 0; let v; let fg; let bg; while ((m = RE.exec(s))) { switch (m[0].charAt(0)) { case '|': // Renegade |## v = parseInt(m[2], 10); if (isNaN(v)) { v = getPredefinedMCIValue(client, m[2]) || m[0]; // value itself or literal } if (_.isString(v)) { result += s.substr(lastIndex, m.index - lastIndex) + v; } else { v = ansiSgrFromRenegadeColorCode(v); result += s.substr(lastIndex, m.index - lastIndex) + v; } break; case '@': // PCBoard @X## or Wildcat! @##@ if ('@' === m[0].substr(-1)) { // Wildcat! v = m[6]; } else { v = m[4]; } bg = { 0: ['blackBG'], 1: ['blueBG'], 2: ['greenBG'], 3: ['cyanBG'], 4: ['redBG'], 5: ['magentaBG'], 6: ['yellowBG'], 7: ['whiteBG'], 8: ['bold', 'blackBG'], 9: ['bold', 'blueBG'], A: ['bold', 'greenBG'], B: ['bold', 'cyanBG'], C: ['bold', 'redBG'], D: ['bold', 'magentaBG'], E: ['bold', 'yellowBG'], F: ['bold', 'whiteBG'], }[v.charAt(0)] || ['normal']; fg = { 0: ['reset', 'black'], 1: ['reset', 'blue'], 2: ['reset', 'green'], 3: ['reset', 'cyan'], 4: ['reset', 'red'], 5: ['reset', 'magenta'], 6: ['reset', 'yellow'], 7: ['reset', 'white'], 8: ['blink', 'black'], 9: ['blink', 'blue'], A: ['blink', 'green'], B: ['blink', 'cyan'], C: ['blink', 'red'], D: ['blink', 'magenta'], E: ['blink', 'yellow'], F: ['blink', 'white'], }[v.charAt(1)] || ['normal']; v = ANSI.sgr(fg.concat(bg)); result += s.substr(lastIndex, m.index - lastIndex) + v; break; case '\x03': // WWIV v = parseInt(m[8], 10); if (isNaN(v)) { v += m[0]; } else { v = ANSI.sgr( { 0: ['reset', 'black'], 1: ['bold', 'cyan'], 2: ['bold', 'yellow'], 3: ['reset', 'magenta'], 4: ['bold', 'white', 'blueBG'], 5: ['reset', 'green'], 6: ['bold', 'blink', 'red'], 7: ['bold', 'blue'], 8: ['reset', 'blue'], 9: ['reset', 'cyan'], }[v] || 'normal' ); } result += s.substr(lastIndex, m.index - lastIndex) + v; break; case '\x19': case '\0x11': // CNET "Y-Style" & "Q-Style" v = m[9] || m[11]; if (v) { if ('n1' === v) { v = '\n'; } else if ('f1' === v) { v = ANSI.clearScreen(); } else { v = ansiSgrFromCnetStyleColorCode(v); } } else { v = m[0]; } result += s.substr(lastIndex, m.index - lastIndex) + v; break; } lastIndex = RE.lastIndex; } return 0 === result.length ? s : result + s.substr(lastIndex); }