Skip to main content

socketry_markdown/
renderer.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Traits for rendering Markdown AST nodes.
5mod html;
6mod markdown;
7
8pub use html::HTMLRenderer;
9pub use markdown::MarkdownRenderer;
10
11use crate::mdast::Node;
12use alloc::string::String;
13
14/// A renderer for an AST node tree.
15///
16/// Implement [`render_node`][Self::render_node] to decide how each node is
17/// emitted. Call [`render_children`][Self::render_children] for transparent
18/// containers or when a node's children should be rendered recursively.
19/// Renderers choose their output format and escaping rules.
20///
21/// This is separate from [`crate::to_html`], which parses Markdown source and
22/// renders the parser's events with the built-in HTML compiler.
23///
24/// # Example
25///
26/// ```ignore
27/// use socketry_markdown::{mdast::Node, renderer::Renderer};
28///
29/// struct PlainText;
30///
31/// impl Renderer for PlainText {
32///     fn render_node(&mut self, node: &Node) -> String {
33///         match node {
34///             Node::Text(text) => text.value.clone(),
35///             Node::InlineCode(code) => code.value.clone(),
36///             Node::Code(code) => code.value.clone(),
37///             Node::Break(_) => "\n".into(),
38///             Node::Image(image) => image.alt.clone(),
39///             Node::ImageReference(image) => image.alt.clone(),
40///             Node::Definition(_) | Node::FootnoteReference(_) | Node::ThematicBreak(_) => {
41///                 String::new()
42///             }
43///             _ => self.render_children(node),
44///         }
45///     }
46/// }
47///
48/// let node = Node::Fragment(socketry_markdown::mdast::Fragment {
49///     children: vec![Node::Text(socketry_markdown::mdast::Text {
50///         value: "hello".into(),
51///         position: None,
52///     })],
53/// });
54/// let mut renderer = PlainText;
55/// let plain_text = node.render_with(&mut renderer);
56/// ```
57pub trait Renderer {
58    /// Render one AST node into this renderer's output.
59    fn render_node(&mut self, node: &Node) -> String;
60
61    /// Render a node and its descendants.
62    fn render(&mut self, node: &Node) -> String {
63        self.render_node(node)
64    }
65
66    /// Render the direct children of a parent node in order.
67    fn render_children(&mut self, node: &Node) -> String {
68        let mut output = String::new();
69
70        if let Some(children) = node.children() {
71            for child in children {
72                output.push_str(&self.render(child));
73            }
74        }
75
76        output
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::Renderer;
83    use crate::mdast::{Node, Root, Text};
84    use alloc::string::String;
85    use alloc::vec;
86
87    struct PlainTextRenderer;
88
89    impl Renderer for PlainTextRenderer {
90        fn render_node(&mut self, node: &Node) -> String {
91            match node {
92                Node::Text(text) => text.value.clone(),
93                _ => self.render_children(node),
94            }
95        }
96    }
97
98    #[test]
99    fn renders_nodes_and_their_children_by_default() {
100        let node = Node::Root(Root {
101            position: None,
102            children: vec![
103                Node::Text(Text {
104                    value: "one".into(),
105                    position: None,
106                }),
107                Node::Text(Text {
108                    value: "two".into(),
109                    position: None,
110                }),
111            ],
112        });
113        let mut renderer = PlainTextRenderer;
114
115        assert_eq!(renderer.render(&node), "onetwo");
116        assert_eq!(
117            renderer.render_children(&Node::ThematicBreak(crate::mdast::ThematicBreak {
118                position: None,
119            })),
120            ""
121        );
122    }
123}