core/parsers/diagram.parser.ts

core/parsers/diagram.parser.ts is a file in GovLab Docs. 58 lines of code and 19 definitions.

import { NO_FENCE, splitLines, stepFence } from "#core/parsers/markdown.parser";
import type { Fence } from "#types/markdown.types";
import type { MermaidBlock } from "#types/diagram.types";

const FENCE_BODY_OFFSET = 2;
const MERMAID_LANG = "mermaid";
const INLINE_SPACE: ReadonlySet<string> = new Set([" ", "\t"]);

const fenceInfoLang = function fenceInfoLang(trimmed: string, len: number): string {
    let at = len;
    while (at < trimmed.length && INLINE_SPACE.has(trimmed.charAt(at))) {
        at += 1;
    }
    const start = at;
    while (at < trimmed.length && !INLINE_SPACE.has(trimmed.charAt(at))) {
        at += 1;
    }
    return trimmed.slice(start, at).toLowerCase();
};

class MermaidBlockReader {
    public readonly blocks: MermaidBlock[] = [];
    private fence: Fence = NO_FENCE;
    private inMermaid = false;
    private buffer: string[] = [];
    private startLine = 0;

    public feed(line: string, index: number): void {
        const trimmed = line.trim();
        const wasOpen = this.fence.open;
        this.fence = stepFence(trimmed, this.fence);
        if (!wasOpen && this.fence.open) {
            this.openFence(trimmed, index);
            return;
        }
        if (wasOpen && !this.fence.open) {
            this.closeFence();
            return;
        }
        if (wasOpen && this.inMermaid) {
            this.buffer.push(line);
        }
    }

    private openFence(trimmed: string, index: number): void {
        this.inMermaid = fenceInfoLang(trimmed, this.fence.len) === MERMAID_LANG;
        this.buffer = [];
        this.startLine = index + FENCE_BODY_OFFSET;
    }

    private closeFence(): void {
        if (this.inMermaid) {
            this.blocks.push({ code: this.buffer.join("\n"), startLine: this.startLine });
        }
        this.inMermaid = false;
    }
}

export const mermaidBlocks = function mermaidBlocks(source: string): MermaidBlock[] {
    const reader = new MermaidBlockReader();
    splitLines(source).forEach((line, index) => {
        reader.feed(line, index);
    });
    return reader.blocks;
};