markdown_builder/types/
markdown.rs

1use crate::{
2    traits::{AsFooter, MarkdownElement},
3    types::{header::Header, link::Link, list::List, paragraph::Paragraph},
4    Image,
5};
6use std::fmt;
7use tousize::ToUsize;
8
9/// A markdown document.
10#[derive(Default)]
11pub struct Markdown {
12    /// The markdown elements.
13    pub elements: Vec<Box<dyn MarkdownElement>>,
14    /// The markdown footer elements.
15    pub footers: Vec<Box<dyn MarkdownElement>>,
16}
17
18impl Markdown {
19    /// Creates a new default `Markdown` instance.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Creates a new `Markdown` instance with the given elements and footers.
25    pub fn with(
26        elements: Vec<Box<dyn MarkdownElement>>,
27        footers: Vec<Box<dyn MarkdownElement>>,
28    ) -> Self {
29        Self { elements, footers }
30    }
31
32    /// Adds a header to the document.
33    ///
34    /// # Arguments
35    ///
36    /// - `text`: The header's text.
37    /// - `level`: The header's level.
38    ///
39    /// # Panics
40    ///
41    /// Panics if the header level is not valid (one to six inclusive).
42    pub fn header(&mut self, text: impl Into<String>, level: impl ToUsize) -> &mut Self {
43        let header = Header::from(text, level);
44        self.elements.push(Box::new(header));
45        self
46    }
47
48    /// Adds a header with level 1 to the document.
49    ///
50    /// # Arguments
51    ///
52    /// - `text`: The header's text.
53    pub fn header1(&mut self, text: impl Into<String>) -> &mut Self {
54        self.header(text, 1usize);
55        self
56    }
57
58    /// Adds a header with level 2 to the document.
59    ///
60    /// # Arguments
61    ///
62    /// - `text`: The header's text.
63    pub fn header2(&mut self, text: impl Into<String>) -> &mut Self {
64        self.header(text, 2usize);
65        self
66    }
67
68    /// Adds a header with level 3 to the document.
69    ///
70    /// # Arguments
71    ///
72    /// - `text`: The header's text.
73    pub fn header3(&mut self, text: impl Into<String>) -> &mut Self {
74        self.header(text, 3usize);
75        self
76    }
77
78    /// Adds a header with level 4 to the document.
79    ///
80    /// # Arguments
81    ///
82    /// - `text`: The header's text.
83    pub fn header4(&mut self, text: impl Into<String>) -> &mut Self {
84        self.header(text, 4usize);
85        self
86    }
87
88    /// Adds a header with level 5 to the document.
89    ///
90    /// # Arguments
91    ///
92    /// - `text`: The header's text.
93    pub fn header5(&mut self, text: impl Into<String>) -> &mut Self {
94        self.header(text, 5usize);
95        self
96    }
97
98    /// Adds a header with level 6 to the document.
99    ///
100    /// # Arguments
101    ///
102    /// - `text`: The header's text.
103    pub fn header6(&mut self, text: impl Into<String>) -> &mut Self {
104        self.header(text, 6usize);
105        self
106    }
107
108    /// Adds a list to the document.
109    ///
110    /// # Arguments
111    ///
112    /// - `list`: The list instance to add.
113    pub fn list(&mut self, list: List) -> &mut Self {
114        self.elements.push(Box::new(list));
115        self
116    }
117
118    /// Adds a link to the document.
119    ///
120    /// # Arguments
121    ///
122    /// - `link`: The link instance to add.
123    ///
124    /// # Note
125    ///
126    /// The associated footer element is added as well if the passed link is
127    /// marked as footer.
128    pub fn link(&mut self, link: Link) -> &mut Self {
129        if link.footer {
130            self.footers.push(link.as_footer());
131        }
132        self.elements.push(Box::new(link));
133        self
134    }
135
136    /// Adds an image to the document.
137    ///
138    /// ### Argument
139    ///
140    /// - `image`: The image instance to add.
141    ///
142    /// # Note
143    ///
144    /// The associated footer element is added as well if the passed link is
145    /// marked as footer.
146    pub fn image(&mut self, image: Image) -> &mut Self {
147        if image.footer {
148            self.footers.push(image.as_footer());
149        }
150        self.elements.push(Box::new(image));
151        self
152    }
153
154    /// Adds a paragraph to the document.
155    ///
156    /// # Arguments
157    ///
158    /// - `text`: The paragraph's text.
159    pub fn paragraph(&mut self, text: impl Into<String>) -> &mut Self {
160        self.elements.push(Box::new(Paragraph::from(text)));
161        self
162    }
163
164    /// Renders the markdown document to a `String`.
165    ///
166    /// The method does render each
167    /// [element](struct.Markdown.structfield.elements) in order, followed by
168    /// each [footer](struct.Markdown.structfield.footers).
169    pub fn render(&self) -> String {
170        self.to_string()
171    }
172}
173
174impl fmt::Display for Markdown {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        for (index, element) in self.elements.iter().enumerate() {
177            if index == self.elements.len() - 1 {
178                write!(f, "{}", element.render())?;
179            } else {
180                writeln!(f, "{}", element.render())?;
181            }
182        }
183
184        if !self.footers.is_empty() {
185            writeln!(f, "")?;
186        }
187
188        for footer in &self.footers {
189            writeln!(f, "{}", footer.render())?;
190        }
191
192        Ok(())
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use crate::ImageBuilder;
200
201    #[test]
202    fn document_with_one_paragraph() {
203        assert_eq!(
204            Markdown::new().paragraph("Hello World").render(),
205            "Hello World\n"
206        );
207    }
208
209    #[test]
210    fn document_with_two_paragraphs() {
211        assert_eq!(
212            Markdown::new()
213                .paragraph("Hello World")
214                .paragraph("Two paragraphs")
215                .render(),
216            "Hello World\n\nTwo paragraphs\n"
217        );
218    }
219
220    #[test]
221    fn document_with_image() {
222        let mut doc = Markdown::new();
223        doc.image(
224            ImageBuilder::new()
225                .url("https://example.com/picture.png")
226                .text("A cute picture of a sandcat")
227                .build(),
228        );
229
230        assert_eq!(
231            doc.render(),
232            "![A cute picture of a sandcat](https://example.com/picture.png)\n"
233        );
234    }
235
236    #[test]
237    fn document_with_image_footer() {
238        let mut doc = Markdown::new();
239        doc.image(
240            ImageBuilder::new()
241                .url("https://example.com/picture.png")
242                .text("A cute picture of a sandcat")
243                .footer()
244                .build(),
245        );
246
247        assert_eq!(doc.render(), "![A cute picture of a sandcat][A cute picture of a sandcat]\n\n[A cute picture of a sandcat]: https://example.com/picture.png\n");
248    }
249}