mdforge 0.1.0

Define, validate, and render typed Markdown extensions for LLM-generated content.
Documentation
use std::collections::HashMap;

/// Byte span in the original input.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
    /// Inclusive start byte offset.
    pub start: usize,
    /// Exclusive end byte offset.
    pub end: usize,
}

/// Markdown event captured by mdforge's minimal Markdown representation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MdEvent {
    /// Plain Markdown text to be handled by a renderer.
    Text(String),
}

/// Parsed argument value.
#[derive(Debug, Clone, PartialEq)]
pub enum ArgValue {
    /// Integer argument.
    Int(i64),
    /// String-like argument.
    String(String),
}

/// Supported argument types for block and inline extension definitions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgType {
    /// Signed integer value.
    Int,
    /// String value without whitespace.
    String,
    /// String value constrained to a fixed set.
    StaticEnum(&'static [&'static str]),
    /// String value constrained by runtime values in [`crate::EvalContext`].
    DynamicEnum(&'static str),
}

/// Argument type plus required/optional metadata.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArgSpec {
    /// Expected argument type.
    pub arg_type: ArgType,
    /// Whether the argument must be present.
    pub required: bool,
}

impl ArgType {
    /// Mark this argument type as required.
    pub fn required(self) -> ArgSpec {
        ArgSpec {
            arg_type: self,
            required: true,
        }
    }

    /// Mark this argument type as optional.
    pub fn optional(self) -> ArgSpec {
        ArgSpec {
            arg_type: self,
            required: false,
        }
    }

    /// Render this type as a compact label for [`crate::Forge::signature`].
    pub fn signature_label(&self, required: bool) -> String {
        let core = match self {
            ArgType::Int => "<int>".to_string(),
            ArgType::String => "<string>".to_string(),
            ArgType::StaticEnum(values) => format!("<{}>", values.join("|")),
            ArgType::DynamicEnum(name) => format!("<dynamic:{}>", name),
        };

        if required {
            core
        } else {
            format!("{}?>", core.trim_end_matches('>'))
        }
    }
}

/// Parsed mdforge document.
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
    /// Top-level nodes in source order.
    pub nodes: Vec<Node>,
}

/// Document node.
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
    /// Ordinary Markdown text.
    Markdown(Vec<MdEvent>),
    /// mdforge block extension.
    Block(BlockNode),
}

/// Parsed block extension node.
#[derive(Debug, Clone, PartialEq)]
pub struct BlockNode {
    /// Block extension name.
    pub name: String,
    /// Parsed arguments.
    pub args: HashMap<String, ArgValue>,
    /// Nested body nodes.
    pub body: Vec<Node>,
    /// Source span for the opening line.
    pub span: Span,
}

/// Parsed inline extension.
#[derive(Debug, Clone, PartialEq)]
pub struct InlineExt {
    /// Inline extension name.
    pub name: String,
    /// Parsed arguments.
    pub args: HashMap<String, ArgValue>,
    /// Source span within the Markdown text segment.
    pub span: Span,
}