Skip to main content

orbit_md/parser/
mod.rs

1//! Pull-based Markdown-to-HTML conversion via `pulldown-cmark`.
2
3use pulldown_cmark::{Options, Parser, html};
4
5use crate::components::{ComponentRegistry, expand_components};
6use crate::error::PageError;
7use crate::models::{CompiledPage, UncompiledPage};
8
9/// Shared pulldown-cmark options for page bodies.
10fn markdown_options() -> Options {
11    Options::ENABLE_STRIKETHROUGH
12        | Options::ENABLE_TABLES
13        | Options::ENABLE_FOOTNOTES
14        | Options::ENABLE_TASKLISTS
15}
16
17/// Converts Markdown source text into an HTML fragment.
18pub fn markdown_to_html(body: &str) -> String {
19    let parser = Parser::new_ext(body, markdown_options());
20    let mut content_html = String::with_capacity(body.len().saturating_mul(2));
21    html::push_html(&mut content_html, parser);
22    content_html
23}
24
25/// Converts an [`UncompiledPage`] into a [`CompiledPage`] HTML fragment.
26///
27/// JSX-style component tags are expanded before Markdown parsing.
28///
29/// # Examples
30///
31/// ```
32/// use orbit_md::components::ComponentRegistry;
33/// use orbit_md::config::Config;
34/// use orbit_md::models::{FrontMatter, SourcePage, UncompiledPage};
35/// use orbit_md::parser::compile_markdown;
36/// use std::path::PathBuf;
37///
38/// let registry = ComponentRegistry::from_config(&Config::default()).unwrap();
39/// let page = UncompiledPage::new(SourcePage {
40///     source_path: PathBuf::from("content/post.md"),
41///     relative_path: PathBuf::from("post.md"),
42///     front_matter: FrontMatter::default(),
43///     body: "# Hello".to_owned(),
44/// });
45///
46/// let compiled = compile_markdown(page, &registry).unwrap();
47/// assert!(compiled.content_html.contains("<h1>"));
48/// ```
49pub fn compile_markdown(
50    page: UncompiledPage,
51    registry: &ComponentRegistry,
52) -> Result<CompiledPage, PageError> {
53    let source = page.into_inner();
54    let path = source.source_path.clone();
55
56    // NOTE: component expansion first — slots compile MD to HTML inside the registry.
57    let expanded = expand_components(&source.body, registry, &path)?;
58
59    // NOTE: Parser borrows `expanded` — no extra clone beyond this stage.
60    let parser = Parser::new_ext(&expanded, markdown_options());
61    let mut content_html = String::with_capacity(expanded.len().saturating_mul(2));
62    html::push_html(&mut content_html, parser);
63
64    Ok(CompiledPage::new(source, content_html))
65}
66
67/// Parallel map-reduce over all uncompiled pages.
68///
69/// Failures are returned as the first error encountered; callers may extend
70/// this to collect all errors if needed.
71pub fn compile_all(
72    pages: Vec<UncompiledPage>,
73    registry: &ComponentRegistry,
74) -> Result<Vec<CompiledPage>, PageError> {
75    use rayon::prelude::*;
76
77    pages
78        .into_par_iter()
79        .map(|page| compile_markdown(page, registry))
80        .collect()
81}