katana-render-runtime-cli 0.3.2

CLI front-end for katana-render-runtime: render, reference-update, compare, bench.
use katana_render_runtime::DiagramKind;

const MIN_MARKDOWN_FENCE_MARKERS: usize = 3;

pub(crate) struct DiagramSourceOps;

impl DiagramSourceOps {
    pub(crate) fn prepare(kind: DiagramKind, source: String) -> String {
        match kind {
            DiagramKind::Mermaid => MermaidMarkdownOps::extract(source),
            DiagramKind::Drawio => source,
            DiagramKind::PlantUml => PlantUmlMarkdownOps::extract(source),
            DiagramKind::MathJax => source,
        }
    }
}

pub(crate) struct MermaidMarkdownOps;

impl MermaidMarkdownOps {
    pub(crate) fn extract(source: String) -> String {
        Self::extract_for_languages(source, &["mermaid", "zenuml"])
    }

    pub(crate) fn extract_for_languages(source: String, languages: &[&'static str]) -> String {
        let mut lines: Vec<&str> = Vec::new();
        let mut block_lang: Option<&'static str> = None;
        for line in source.lines() {
            if block_lang.is_none() {
                block_lang = Self::starts_block(line, languages);
                continue;
            }
            if Self::ends_block(line) {
                let content = lines.join("\n");
                return if block_lang == Some("zenuml") {
                    format!("zenuml\n{content}")
                } else {
                    content
                };
            }
            lines.push(line);
        }
        source
    }

    fn starts_block(line: &str, languages: &[&'static str]) -> Option<&'static str> {
        let trimmed = line.trim();
        Self::language_of(trimmed, b'`', languages)
            .or_else(|| Self::language_of(trimmed, b'~', languages))
    }

    fn language_of(line: &str, marker: u8, languages: &[&'static str]) -> Option<&'static str> {
        let bytes = line.as_bytes();
        let marker_count = bytes.iter().take_while(|it| **it == marker).count();
        if marker_count < MIN_MARKDOWN_FENCE_MARKERS {
            return None;
        }
        let language = line[marker_count..].split_whitespace().next()?;
        languages.iter().copied().find(|it| *it == language)
    }

    fn ends_block(line: &str) -> bool {
        matches!(line.trim(), "```" | "~~~")
    }
}

struct PlantUmlMarkdownOps;

impl PlantUmlMarkdownOps {
    fn extract(source: String) -> String {
        MermaidMarkdownOps::extract_for_languages(source, &["plantuml"])
    }
}