Skip to main content

laterite_core/
markdown.rs

1//! Markdown to HTML, one renderer for the admin's preview, the theme's
2//! `markdown` filter and content blocks, so what an author previews is what
3//! the site shows.
4//!
5//! The dialect is CommonMark plus tables, strikethrough, footnotes, task
6//! lists and heading attributes (`{#id}`). Raw HTML in the text passes
7//! through, as it does on the site; a surface that shows untrusted text cleans
8//! the result itself.
9
10/// Renders markdown to an HTML fragment.
11pub fn render(text: &str) -> String {
12    use pulldown_cmark::{html, Options, Parser};
13    let mut options = Options::empty();
14    options.insert(Options::ENABLE_TABLES);
15    options.insert(Options::ENABLE_STRIKETHROUGH);
16    options.insert(Options::ENABLE_FOOTNOTES);
17    options.insert(Options::ENABLE_TASKLISTS);
18    options.insert(Options::ENABLE_HEADING_ATTRIBUTES);
19    options.insert(Options::ENABLE_SMART_PUNCTUATION);
20    let parser = Parser::new_ext(text, options);
21    let mut out = String::with_capacity(text.len() * 2);
22    html::push_html(&mut out, parser);
23    out
24}
25
26#[cfg(test)]
27mod tests {
28    use super::render;
29
30    #[test]
31    fn renders_the_dialect() {
32        let html = render(
33            "# Title {#top}\n\nSome ~~old~~ *new* text[^1].\n\n- [x] done\n- [ ] open\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\n[^1]: A note.\n",
34        );
35        assert!(html.contains(r##"<h1 id="top">Title</h1>"##), "{html}");
36        assert!(html.contains("<del>old</del>"), "{html}");
37        assert!(html.contains("<em>new</em>"), "{html}");
38        assert!(html.contains(r#"type="checkbox""#), "{html}");
39        assert!(html.contains("<table>"), "{html}");
40        assert!(html.contains("footnote"), "{html}");
41    }
42
43    #[test]
44    fn empty_text_renders_nothing() {
45        assert_eq!(render(""), "");
46        assert_eq!(render("   \n"), "");
47    }
48}