markdown_builder/types/
markdown.rs1use 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#[derive(Default)]
11pub struct Markdown {
12 pub elements: Vec<Box<dyn MarkdownElement>>,
14 pub footers: Vec<Box<dyn MarkdownElement>>,
16}
17
18impl Markdown {
19 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn with(
26 elements: Vec<Box<dyn MarkdownElement>>,
27 footers: Vec<Box<dyn MarkdownElement>>,
28 ) -> Self {
29 Self { elements, footers }
30 }
31
32 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 pub fn header1(&mut self, text: impl Into<String>) -> &mut Self {
54 self.header(text, 1usize);
55 self
56 }
57
58 pub fn header2(&mut self, text: impl Into<String>) -> &mut Self {
64 self.header(text, 2usize);
65 self
66 }
67
68 pub fn header3(&mut self, text: impl Into<String>) -> &mut Self {
74 self.header(text, 3usize);
75 self
76 }
77
78 pub fn header4(&mut self, text: impl Into<String>) -> &mut Self {
84 self.header(text, 4usize);
85 self
86 }
87
88 pub fn header5(&mut self, text: impl Into<String>) -> &mut Self {
94 self.header(text, 5usize);
95 self
96 }
97
98 pub fn header6(&mut self, text: impl Into<String>) -> &mut Self {
104 self.header(text, 6usize);
105 self
106 }
107
108 pub fn list(&mut self, list: List) -> &mut Self {
114 self.elements.push(Box::new(list));
115 self
116 }
117
118 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 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 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 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 "\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}