1pub 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
17pub 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 let preprocessed_markdown = preprocess_components(markdown);
25
26 let ast = parser::parse(&preprocessed_markdown, &parse_options)?;
27 renderer::render(&ast, ®istry, &config)
28}
29
30pub 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 let preprocessed_markdown = preprocess_components(markdown);
41
42 let ast = parser::parse(&preprocessed_markdown, &parse_options)?;
43 renderer::render(&ast, registry, &config)
44}
45
46fn preprocess_components(markdown: &str) -> String {
48 use regex::Regex;
49
50 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 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 let mut result = component_regex
60 .replace_all(markdown, |caps: ®ex::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 result = component_end_regex
72 .replace_all(&result, |_: ®ex::Captures| {
73 "<!-- component_end -->\n".to_string()
74 })
75 .to_string();
76
77 result = nested_component_regex
79 .replace_all(&result, |caps: ®ex::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 result = nested_component_end_regex
91 .replace_all(&result, |_: ®ex::Captures| {
92 "<!-- nested_component_end -->\n".to_string()
93 })
94 .to_string();
95
96 result
97}
98
99pub 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
106pub 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}