mdx/
lib.rs

1//! MDX: A minimal, elegant Markdown to UI renderer with custom components
2
3pub mod component;
4pub mod config;
5pub mod error;
6pub mod parser;
7pub mod renderer;
8pub mod theme;
9
10pub use component::{Component, ComponentRegistry};
11pub use config::Config;
12pub use error::Error;
13pub use parser::{parse, InlineNode, Node, ParseOptions, ParsedDocument};
14pub use renderer::{render, RenderOptions};
15pub use theme::{Theme, ThemeOptions};
16
17/// Renders a Markdown string to HTML
18pub fn render_to_html(markdown: &str, config: Option<Config>) -> Result<String, error::Error> {
19    let config = config.unwrap_or_default();
20    let parse_options = ParseOptions::from(&config);
21    let registry = ComponentRegistry::default();
22
23    // Preprocess the markdown to handle custom component syntax
24    let preprocessed_markdown = preprocess_components(markdown);
25
26    let ast = parser::parse(&preprocessed_markdown, &parse_options)?;
27    renderer::render(&ast, &registry, &config)
28}
29
30/// Renders a Markdown string to HTML with custom component registry
31pub fn render_to_html_with_registry(
32    markdown: &str,
33    config: Option<Config>,
34    registry: &ComponentRegistry,
35) -> Result<String, error::Error> {
36    let config = config.unwrap_or_default();
37    let parse_options = ParseOptions::from(&config);
38
39    // Preprocess the markdown to handle custom component syntax
40    let preprocessed_markdown = preprocess_components(markdown);
41
42    let ast = parser::parse(&preprocessed_markdown, &parse_options)?;
43    renderer::render(&ast, registry, &config)
44}
45
46/// Preprocesses markdown content to transform custom component syntax to HTML
47fn preprocess_components(markdown: &str) -> String {
48    use regex::Regex;
49
50    // Handle components with :: syntax
51    let component_regex = Regex::new(r"(?m)^::([a-zA-Z0-9_-]+)(\{([^}]*)\})?\s*$").unwrap();
52    let component_end_regex = Regex::new(r"(?m)^::\s*$").unwrap();
53
54    // Handle nested components with ::: syntax
55    let nested_component_regex = Regex::new(r"(?m)^:::([a-zA-Z0-9_-]+)(\{([^}]*)\})?\s*$").unwrap();
56    let nested_component_end_regex = Regex::new(r"(?m)^:::\s*$").unwrap();
57
58    // First, replace all component starts with HTML comments with metadata
59    let mut result = component_regex
60        .replace_all(markdown, |caps: &regex::Captures| {
61            let component_name = &caps[1];
62            let attributes = caps.get(3).map_or("", |m| m.as_str());
63            format!(
64                "<!-- component_start:{}:{} -->\n",
65                component_name, attributes
66            )
67        })
68        .to_string();
69
70    // Replace all component ends with HTML comments
71    result = component_end_regex
72        .replace_all(&result, |_: &regex::Captures| {
73            "<!-- component_end -->\n".to_string()
74        })
75        .to_string();
76
77    // Handle nested components
78    result = nested_component_regex
79        .replace_all(&result, |caps: &regex::Captures| {
80            let component_name = &caps[1];
81            let attributes = caps.get(3).map_or("", |m| m.as_str());
82            format!(
83                "<!-- nested_component_start:{}:{} -->\n",
84                component_name, attributes
85            )
86        })
87        .to_string();
88
89    // Replace all nested component ends with HTML comments
90    result = nested_component_end_regex
91        .replace_all(&result, |_: &regex::Captures| {
92            "<!-- nested_component_end -->\n".to_string()
93        })
94        .to_string();
95
96    result
97}
98
99/// Renders a Markdown file to HTML
100pub fn render_file(path: &str, config: Option<Config>) -> Result<String, error::Error> {
101    use std::fs;
102    let markdown = fs::read_to_string(path).map_err(error::Error::IoError)?;
103    render_to_html(&markdown, config)
104}
105
106/// Renders a Markdown file to an HTML file
107pub fn render_file_to_file(
108    input: &str,
109    output: &str,
110    config: Option<Config>,
111) -> Result<(), error::Error> {
112    use std::fs;
113    let html = render_file(input, config)?;
114    fs::write(output, html).map_err(error::Error::IoError)?;
115    Ok(())
116}