enigma-bbs/core/color_codes.js

274 lines
9.1 KiB
JavaScript

/* 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));
}