pulldown_html_ext/html/
default.rs

1use pulldown_cmark_escape::StrWrite;
2
3use crate::html::config::HtmlConfig;
4use crate::html::state::HtmlState;
5use crate::html::writer::HtmlWriter;
6use crate::html_writer;
7
8/// Base type for HTML writers that handles common functionality
9pub struct HtmlWriterBase<W: StrWrite> {
10    writer: W,
11    config: HtmlConfig,
12    state: HtmlState,
13}
14
15impl<W: StrWrite> HtmlWriterBase<W> {
16    /// Create a new HtmlWriterBase with the given writer and configuration
17    pub fn new(writer: W, config: HtmlConfig) -> Self {
18        Self {
19            writer,
20            config,
21            state: HtmlState::new(),
22        }
23    }
24
25    /// Get a mutable reference to the underlying writer
26    pub fn get_writer(&mut self) -> &mut W {
27        &mut self.writer
28    }
29
30    /// Get a reference to the configuration
31    pub fn get_config(&self) -> &HtmlConfig {
32        &self.config
33    }
34
35    /// Get a mutable reference to the state
36    pub fn get_state(&mut self) -> &mut HtmlState {
37        &mut self.state
38    }
39}
40
41/// Default HTML writer implementation that can work with any StrWrite-compatible writer
42/// This should be the approximate amount of code any custom implementation needs to
43/// provide
44#[html_writer]
45pub struct DefaultHtmlWriter<W: StrWrite> {
46    base: HtmlWriterBase<W>,
47}
48
49impl<W: StrWrite> DefaultHtmlWriter<W> {
50    /// Create a new DefaultHtmlWriter with the given writer and configuration
51    pub fn new(writer: W, config: HtmlConfig) -> Self {
52        Self {
53            base: HtmlWriterBase::new(writer, config.clone()),
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use pulldown_cmark_escape::{escape_html, FmtWriter};
62    use std::fmt;
63
64    #[test]
65    fn test_basic_writing() {
66        let mut output = String::new();
67        let config = HtmlConfig::default();
68        let mut writer = DefaultHtmlWriter::new(FmtWriter(&mut output), config);
69
70        writer.write_str("<p>").unwrap();
71        let _ = escape_html(&mut writer.get_writer(), "Hello & World");
72        writer.write_str("</p>").unwrap();
73
74        assert_eq!(output, "<p>Hello &amp; World</p>");
75    }
76
77    #[test]
78    fn test_attributes() {
79        let mut output = String::new();
80        let mut config = HtmlConfig::default();
81        config.attributes.element_attributes.insert(
82            "p".to_string(),
83            [("class".to_string(), "test".to_string())]
84                .into_iter()
85                .collect(),
86        );
87
88        let mut writer = DefaultHtmlWriter::new(FmtWriter(&mut output), config);
89        writer.start_paragraph().unwrap();
90        writer.text("Test").unwrap();
91        writer.end_paragraph().unwrap();
92
93        assert_eq!(output, r#"<p class="test">Test</p>"#);
94    }
95
96    struct TestWriter(String);
97
98    impl StrWrite for TestWriter {
99        type Error = fmt::Error;
100
101        fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
102            self.0.push_str(s);
103            Ok(())
104        }
105
106        fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), Self::Error> {
107            fmt::write(self, args)
108        }
109    }
110
111    impl fmt::Write for TestWriter {
112        fn write_str(&mut self, s: &str) -> fmt::Result {
113            self.0.push_str(s);
114            Ok(())
115        }
116    }
117
118    #[test]
119    fn test_custom_writer() {
120        let config = HtmlConfig::default();
121        let mut writer = DefaultHtmlWriter::new(TestWriter(String::new()), config);
122
123        writer.write_str("Test").unwrap();
124        assert_eq!(writer.get_writer().0, "Test");
125    }
126
127    #[test]
128    fn test_state_tracking() {
129        let mut output = String::new();
130        let mut config = HtmlConfig::default();
131        config.html.escape_html = true;
132        let mut writer = DefaultHtmlWriter::new(FmtWriter(&mut output), config);
133
134        assert!(!writer.get_state().currently_in_code_block);
135        writer.get_state().currently_in_code_block = true;
136        writer
137            .start_code_block(pulldown_cmark::CodeBlockKind::Fenced("rust".into()))
138            .unwrap();
139        assert!(writer.get_state().currently_in_code_block);
140        writer.end_code_block().unwrap();
141        writer.get_state().currently_in_code_block = false;
142        assert!(!writer.get_state().currently_in_code_block);
143    }
144}