UNPKG

@sarahisweird/hmoog

Version:

Out-of-game automation for Hackmud

115 lines (91 loc) 3.95 kB
import { ColorNode, Node, NodeVisitor, TextNode } from './types.js'; import { ColorDepth, defaultTextColorHex, defaultTextColorRgba, vgaTranslationTable } from './colors.js'; import { corruptionCharReplacements, CorruptionReplacementTable, isCorruptionChar } from './corruption.js'; import { ShellParser } from './parsing.js'; export type AnsiConverterOptions = { colorDepth: ColorDepth, replaceCorruption: boolean, corruptionReplacements: CorruptionReplacementTable, }; export class AnsiConverter extends NodeVisitor { private readonly colorDepth: ColorDepth; private readonly replaceCorruption: boolean; private readonly corruptionReplacements: CorruptionReplacementTable; private readonly colorStack: string[] = []; private result: string = ''; constructor(options?: Partial<AnsiConverterOptions>) { super(); const defaultedOptions: AnsiConverterOptions = { colorDepth: ColorDepth.TRUE_COLOR, replaceCorruption: true, corruptionReplacements: corruptionCharReplacements, ...options }; this.colorDepth = defaultedOptions.colorDepth; this.replaceCorruption = defaultedOptions.replaceCorruption; this.corruptionReplacements = defaultedOptions.corruptionReplacements; const defaultColor = this.makeAnsiColor({ colorHex: defaultTextColorHex, colorRgba: defaultTextColorRgba }); this.colorStack.push(defaultColor); this.result += defaultColor; } static convert(nodes: Node[], options?: Partial<AnsiConverterOptions>): string { const converter = new AnsiConverter(options); converter.visitAll(nodes); return converter.getResult(); } static convertFromShellText(input: string, options?: Partial<AnsiConverterOptions>): string { const nodes: Node[] = ShellParser.parse(input); return this.convert(nodes, options); } getResult(): string { if (this.colorDepth !== ColorDepth.NONE) { this.result += '\x1b[0m'; } return this.result; } visitColor(node: ColorNode): void { const prevColor: string = this.colorStack[this.colorStack.length - 1]; const newColor: string = this.makeAnsiColor(node); this.result += newColor; this.colorStack.push(newColor); this.visitAll(node.children); this.colorStack.pop(); this.result += prevColor; } visitText(node: TextNode): void { const text: string = this.replaceCorruption ? this.convertCorruption(node.text) : node.text; this.result += text; } private convertCorruption(text: string): string { let newText: string = ''; for (const char of text) { if (isCorruptionChar(char)) { newText += this.corruptionReplacements[char]; } else { newText += char; } } return newText; } private makeAnsiColor(node: Pick<ColorNode, 'colorHex' | 'colorRgba'>): string { switch (this.colorDepth) { case ColorDepth.NONE: return ''; case ColorDepth.EIGHT_BIT: return this.makeEightBitColor(node); case ColorDepth.TRUE_COLOR: return this.makeTrueColor(node); } } private makeEightBitColor(node: Pick<ColorNode, 'colorHex'>): string { const vgaColor: string = vgaTranslationTable[node.colorHex.substring(0, 6)]; if (!vgaColor) throw new Error(`Unknown color: ${node.colorHex}!`); return `\x1b[${vgaColor}m`; } private makeTrueColor(node: Pick<ColorNode, 'colorRgba'>): string { const color = node.colorRgba; return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m`; } }