composable_html/elements/
page.rs1use crate::{elements::utils::{clean_blank_lines, indent_lines, trim_indent}, render::Render};
2
3pub struct Page {
5 title: String,
6 extra_head_elements: Vec<Box<dyn Render>>,
7 body_content: Box<dyn Render>,
8}
9
10impl Page {
11 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
47pub 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 pub fn new() -> Self {
57 PageBuilder {
58 title: None,
59 extra_head_elements: vec![],
60 body_content: None,
61 }
62 }
63
64 pub fn set_title(mut self, title: String) -> Self {
66 self.title = Some(title);
67 self
68 }
69
70 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 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 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}