1#![cfg_attr(test, deny(warnings))]
2#![forbid(unsafe_code, missing_debug_implementations)]
3
4const DOCTYPE: &str = "<!DOCTYPE html>";
50const CHARSET: &str = r#"<meta charset="utf-8">"#;
51const VIEWPORT: &str = r#"<meta name="viewport" content="width=device-width, initial-scale=1.0">"#;
52const HTML_CLOSE: &str = "</html>";
53const HEAD_OPEN: &str = "<head>";
54const HEAD_CLOSE: &str = "</head>";
55
56use std::default::Default;
57
58pub fn new<'a>() -> Builder<'a> {
60    Builder::new()
61}
62
63#[derive(Debug, Clone, Default)]
65pub struct Builder<'b> {
66    color: Option<String>,
67    desc: Option<String>,
68    lang: &'b str,
69    favicon: Option<String>,
70    fonts: Vec<String>,
71    manifest: Option<String>,
72    scripts: Vec<String>,
73    styles: Vec<String>,
74    title: Option<String>,
75    body: Option<&'b str>,
76    has_async_style: bool,
77}
78
79impl<'b> Builder<'b> {
80    pub fn new() -> Self {
82        Self {
83            lang: "en-US",
84            ..Default::default()
85        }
86    }
87
88    pub fn raw_body(mut self, body: &'b str) -> Self {
90        self.body = Some(body);
91        self
92    }
93
94    pub fn lang(mut self, lang: &'b str) -> Self {
96        self.lang = lang;
97        self
98    }
99
100    pub fn description(mut self, desc: &str) -> Self {
102        let val = format!(r#"<meta name="description" content="{}">"#, desc);
103        self.desc = Some(val);
104        self
105    }
106
107    pub fn theme_color(mut self, color: &str) -> Self {
109        let val = format!(r#"<meta name="theme-color" content="{}">"#, color);
110        self.color = Some(val);
111        self
112    }
113
114    pub fn title(mut self, title: &str) -> Self {
116        let val = format!(r#"<title>{}</title>"#, title);
117        self.title = Some(val);
118        self
119    }
120
121    pub fn script(mut self, src: &str) -> Self {
126        let val = format!(r#"<script src="{}" defer></script>"#, src);
127        self.scripts.push(val);
128        self
129    }
130
131    pub fn module(mut self, src: &str) -> Self {
134        let val = format!(r#"<script src="{}" type="module"></script>"#, src);
135        self.scripts.push(val);
136        self
137    }
138
139    pub fn inline_module(mut self, src: &str) -> Self {
142        let val = format!(r#"<script type="module">{}</script>"#, src);
143        self.scripts.push(val);
144        self
145    }
146
147    pub fn lazy_script(mut self, src: &str) -> Self {
151        let val = format!(r#"<link rel="prefetch" href="{}">"#, src);
152        self.scripts.push(val);
153        self
154    }
155
156    pub fn blocking_script(mut self, src: &str) -> Self {
160        let val = format!(r#"<script src="{}"></script>"#, src);
161        self.scripts.push(val);
162        self
163    }
164
165    pub fn inline_script(mut self, src: &str) -> Self {
169        let val = format!(r#"<script>{}</script>"#, src);
170        self.scripts.push(val);
171        self
172    }
173
174    pub fn style(mut self, src: &str) -> Self {
183        let val = format!(
184            r#"<link rel="preload" as="style" href="{}" onload="this.rel='stylesheet'" onerror="this.rel='stylesheet'">"#,
185            src
186        );
187        self.styles.push(val);
188
189        if !self.has_async_style {
190            self = self.inline_script(css_rel_preload::CSS_REL_PRELOAD);
191            self.has_async_style = true;
192        }
193
194        self
195    }
196
197    pub fn inline_style(mut self, src: &str) -> Self {
204        let val = format!(r#"<style>{}</style>"#, src);
205        self.styles.push(val);
206        self
207    }
208
209    pub fn blocking_style(mut self, src: &str) -> Self {
214        let val = format!(r#"<link rel="stylesheet" href="{}">"#, src);
215        self.styles.push(val);
216        self
217    }
218
219    pub fn favicon(mut self, src: &str) -> Self {
221        let val = format!(r#"<link rel="icon" type="image/x-icon" href="{}">"#, src);
222        self.favicon = Some(val);
223        self
224    }
225
226    pub fn manifest(mut self, src: &str) -> Self {
228        let val = format!(r#"<link rel="manifest" href="{}">"#, src);
229        self.manifest = Some(val);
230        self
231    }
232
233    pub fn font(mut self, src: &str) -> Self {
235        let val = format!(
236            r#"<link rel="preload" as="font" crossorigin href="{}">"#,
237            src
238        );
239        self.fonts.push(val);
240        self
241    }
242
243    pub fn build(self) -> String {
245        let mut html: String = DOCTYPE.into();
246        html.push_str(&format!(r#"<html lang="{}">"#, self.lang));
247        html.push_str(HEAD_OPEN);
248        html.push_str(CHARSET);
249        html.push_str(VIEWPORT);
250        if let Some(title) = self.title {
251            html.push_str(&title);
252        }
253        if let Some(desc) = self.desc {
254            html.push_str(&desc);
255        }
256
257        for script in self.scripts {
258            html.push_str(&script);
259        }
260        for style in self.styles {
261            html.push_str(&style);
262        }
263        for font in self.fonts {
264            html.push_str(&font);
265        }
266        if let Some(manifest) = self.manifest {
267            html.push_str(&manifest);
268        }
269
270        if let Some(color) = self.color {
271            html.push_str(&color);
272        }
273        if let Some(favicon) = self.favicon {
274            html.push_str(&favicon);
275        }
276        html.push_str(HEAD_CLOSE);
277        if let Some(body) = self.body {
278            html.push_str(&body);
279        }
280        html.push_str(HTML_CLOSE);
281        html
282    }
283}
284
285#[cfg(feature = "http-types")]
286impl Into<http_types::Response> for Builder<'_> {
287    fn into(self) -> http_types::Response {
288        let mut res = http_types::Response::new(200);
289        res.set_content_type(http_types::mime::HTML);
290        res.set_body(self.build());
291        res
292    }
293}