mdforge 0.1.0

Define, validate, and render typed Markdown extensions for LLM-generated content.
Documentation
use crate::{Diagnostic, Document, ErrorCode, Level, MdEvent, Node, VNode};

use super::{args::parse_inline_exts, DomRenderer, EvalContext, Forge};

pub(super) fn render_document(
    forge: &Forge,
    doc: &Document,
    ctx: &EvalContext,
    renderer: &dyn DomRenderer,
) -> Result<Vec<VNode>, Vec<Diagnostic>> {
    let mut diagnostics = Vec::new();
    let nodes = render_nodes(forge, &doc.nodes, ctx, renderer, &mut diagnostics);
    if diagnostics.is_empty() {
        Ok(nodes)
    } else {
        Err(diagnostics)
    }
}

fn render_nodes(
    forge: &Forge,
    nodes: &[Node],
    ctx: &EvalContext,
    renderer: &dyn DomRenderer,
    diagnostics: &mut Vec<Diagnostic>,
) -> Vec<VNode> {
    let mut out = Vec::new();
    for node in nodes {
        match node {
            Node::Markdown(events) => {
                for event in events {
                    let MdEvent::Text(text) = event;
                    out.extend(render_markdown_text(
                        forge,
                        text,
                        ctx,
                        renderer,
                        diagnostics,
                    ));
                }
            }
            Node::Block(block) => {
                if forge.blocks.iter().any(|b| b.name == block.name) {
                    let children = render_nodes(forge, &block.body, ctx, renderer, diagnostics);
                    out.push(renderer.render_block(block, ctx, children));
                } else {
                    diagnostics.push(Diagnostic {
                        level: Level::Error,
                        code: ErrorCode::UnknownBlock,
                        message: format!("unknown block '{}'", block.name),
                        span: block.span.clone(),
                        suggestion: None,
                    });
                }
            }
        }
    }
    out
}

fn render_markdown_text(
    forge: &Forge,
    text: &str,
    ctx: &EvalContext,
    renderer: &dyn DomRenderer,
    diagnostics: &mut Vec<Diagnostic>,
) -> Vec<VNode> {
    let mut out = Vec::new();
    let mut last = 0;
    for inline in parse_inline_exts(text) {
        let local_start = inline.span.start;
        let local_end = inline.span.end;
        if local_start > last {
            out.push(VNode::Text(text[last..local_start].to_string()));
        }
        if forge.inlines.iter().any(|i| i.name == inline.name) {
            out.push(renderer.render_inline(&inline, ctx));
        } else {
            diagnostics.push(Diagnostic {
                level: Level::Error,
                code: ErrorCode::UnknownInline,
                message: format!("unknown inline '{}'", inline.name),
                span: inline.span,
                suggestion: None,
            });
        }
        last = local_end;
    }
    if last < text.len() {
        out.push(VNode::Text(text[last..].to_string()));
    }
    out
}