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}