Skip to main content

composable_html/elements/
page.rs

1use crate::{elements::utils::{clean_blank_lines, indent_lines, trim_indent}, render::Render};
2
3/// Basic HTML page template
4pub struct Page {
5    title: String,
6    extra_head_elements: Vec<Box<dyn Render>>,
7    body_content: Box<dyn Render>,
8}
9
10impl Page {
11    /// Return builder.
12    pub fn builder() -> PageBuilder {
13        PageBuilder::new()
14    }
15}
16
17impl Render for Page {
18    fn render(&self) -> String {
19        clean_blank_lines(&trim_indent(&format!(
20            r#"
21            <!DOCTYPE html>
22            <html lang="en">
23                <head>
24                    <meta charset="UTF-8">
25                    <meta name="viewport" content="width=device-width, initial-scale=1">
26                    <title>{}</title>
27                    <link href="css/style.css" rel="stylesheet">
28                    {}
29                </head>
30                <body>
31                    {}
32                </body>
33            </html>
34            "#,
35            self.title,
36            self.extra_head_elements
37                .iter()
38                .map(|it| it.render())
39                .reduce(|acc, elem| acc + &elem)
40                .unwrap_or_else(String::new),
41            indent_lines(&trim_indent(&self.body_content.render()), "    ", 2, false),
42        )))
43        .to_string()
44    }
45}
46
47/// A builder for [Page]s.
48pub struct PageBuilder {
49    title: Option<String>,
50    extra_head_elements: Vec<Box<dyn Render>>,
51    body_content: Option<Box<dyn Render>>,
52}
53
54impl PageBuilder {
55    /// Create a new, pristine builder
56    pub fn new() -> Self {
57        PageBuilder {
58            title: None,
59            extra_head_elements: vec![],
60            body_content: None,
61        }
62    }
63
64    /// Set the title of the page.
65    pub fn set_title(mut self, title: String) -> Self {
66        self.title = Some(title);
67        self
68    }
69
70    /// Set the body content of the page.
71    pub fn set_body_content(mut self, body_content: Box<dyn Render>) -> Self {
72        self.body_content = Some(body_content);
73        self
74    }
75
76    /// Add a `<head>` element.
77    pub fn add_head_element(mut self, head_element: Box<dyn Render>) -> Self {
78        self.extra_head_elements.push(head_element);
79        self
80    }
81
82    /// Build the page. After calling this method, the builder can no longer be used.
83    pub fn build(self) -> Result<Page, ()> {
84        Ok(Page {
85            title: self
86                .title
87                .clone()
88                .expect("The title presence is already checked"),
89            extra_head_elements: self.extra_head_elements,
90            body_content: self
91                .body_content
92                .expect("The body content is already checked"),
93        })
94    }
95}
96
97impl Default for PageBuilder {
98    fn default() -> Self {
99        PageBuilder::new()
100    }
101}
102
103#[cfg(test)]
104mod test {
105    use crate::{elements::{page::Page, utils::{clean_blank_lines, trim_indent}}, render::Render};
106
107    struct Paragraph {
108        content: String,
109    }
110
111    impl Render for Paragraph {
112        fn render(&self) -> String {
113            format!("<p>{}</p>", self.content)
114        }
115    }
116
117    #[test]
118    fn test_page_render() {
119        let p = Page::builder()
120            .set_title(String::from("Test title"))
121            .set_body_content(Box::new(Paragraph {
122                content: String::from("test"),
123            }))
124            .build()
125            .expect("The page builder has all the required values");
126        assert_eq!(p.render(),
127            clean_blank_lines(&trim_indent(r#"
128            <!DOCTYPE html>
129            <html lang="en">
130                <head>
131                    <meta charset="UTF-8">
132                    <meta name="viewport" content="width=device-width, initial-scale=1">
133                    <title>Test title</title>
134                    <link href="css/style.css" rel="stylesheet">
135
136                </head>
137                <body>
138                    <p>test</p>
139                </body>
140            </html>
141            "#)));
142    }
143}