1#![no_std]
2#![doc(html_root_url = "https://docs.rs/maud/0.26.0")]
10
11extern crate alloc;
12
13pub use wini_maud_macros::html;
14use {
15 alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc},
16 core::{
17 fmt::{self, Arguments, Display, Write},
18 ops::Deref,
19 },
20 hashbrown::HashSet,
21};
22
23mod escape;
24
25pub struct Escaper<'a>(&'a mut String);
49
50impl<'a> Escaper<'a> {
51 pub fn new(buffer: &'a mut String) -> Escaper<'a> {
53 Escaper(buffer)
54 }
55}
56
57impl fmt::Write for Escaper<'_> {
58 fn write_str(&mut self, s: &str) -> fmt::Result {
59 escape::escape_to_string(s, self.0);
60 Ok(())
61 }
62}
63
64pub trait Render {
94 fn render(&self) -> Markup {
96 let mut buffer = String::new();
97 self.render_to(&mut buffer);
98 Markup {
99 content: PreEscaped(buffer),
100 linked_files: HashSet::new(),
101 }
102 }
103
104 fn render_to(&self, buffer: &mut String) {
114 buffer.push_str(&self.render().content.into_string());
115 }
116}
117
118impl Render for str {
119 fn render_to(&self, w: &mut String) {
120 escape::escape_to_string(self, w);
121 }
122}
123
124impl Render for String {
125 fn render_to(&self, w: &mut String) {
126 str::render_to(self, w);
127 }
128}
129
130impl Render for Cow<'_, str> {
131 fn render_to(&self, w: &mut String) {
132 str::render_to(self, w);
133 }
134}
135
136impl Render for Arguments<'_> {
137 fn render_to(&self, w: &mut String) {
138 let _ = Escaper::new(w).write_fmt(*self);
139 }
140}
141
142impl<T: Render + ?Sized> Render for &T {
143 fn render_to(&self, w: &mut String) {
144 T::render_to(self, w);
145 }
146}
147
148impl<T: Render + ?Sized> Render for &mut T {
149 fn render_to(&self, w: &mut String) {
150 T::render_to(self, w);
151 }
152}
153
154impl<T: Render + ?Sized> Render for Box<T> {
155 fn render_to(&self, w: &mut String) {
156 T::render_to(self, w);
157 }
158}
159
160impl<T: Render + ?Sized> Render for Arc<T> {
161 fn render_to(&self, w: &mut String) {
162 T::render_to(self, w);
163 }
164}
165
166macro_rules! impl_render_with_display {
167 ($($ty:ty)*) => {
168 $(
169 impl Render for $ty {
170 fn render_to(&self, w: &mut String) {
171 format_args!("{self}", self = self).render_to(w);
173 }
174 }
175 )*
176 };
177}
178
179impl_render_with_display! {
180 char f32 f64
181}
182
183macro_rules! impl_render_with_itoa {
184 ($($ty:ty)*) => {
185 $(
186 impl Render for $ty {
187 fn render_to(&self, w: &mut String) {
188 w.push_str(itoa::Buffer::new().format(*self));
189 }
190 }
191 )*
192 };
193}
194
195impl_render_with_itoa! {
196 i8 i16 i32 i64 i128 isize
197 u8 u16 u32 u64 u128 usize
198}
199
200pub fn display(value: impl Display) -> impl Render {
218 struct DisplayWrapper<T>(T);
219
220 impl<T: Display> Render for DisplayWrapper<T> {
221 fn render_to(&self, w: &mut String) {
222 format_args!("{0}", self.0).render_to(w);
223 }
224 }
225
226 DisplayWrapper(value)
227}
228
229#[derive(Debug, Clone, Copy)]
231pub struct PreEscaped<T>(pub T);
232
233impl<T: AsRef<str>> Render for PreEscaped<T> {
234 fn render_to(&self, w: &mut String) {
235 w.push_str(self.0.as_ref());
236 }
237}
238
239#[derive(Debug, Default)]
243pub struct Markup {
244 pub content: PreEscaped<String>,
245 pub linked_files: HashSet<String>,
246}
247impl Markup {
248 pub fn into_string(self) -> String {
250 self.content.into()
251 }
252}
253
254impl Render for Markup {
255 fn render_to(&self, w: &mut String) {
256 w.push_str(&self.content.0)
257 }
258}
259
260impl From<Markup> for PreEscaped<String> {
261 fn from(val: Markup) -> Self {
262 val.content
263 }
264}
265
266impl Clone for Markup {
267 fn clone(&self) -> Self {
268 Markup {
269 content: self.content.clone(),
270 linked_files: self.linked_files.clone(),
271 }
272 }
273}
274
275
276impl Deref for Markup {
277 type Target = PreEscaped<String>;
278
279 fn deref(&self) -> &Self::Target {
280 &self.content
281 }
282}
283
284impl<T: Into<String>> PreEscaped<T> {
285 pub fn into_string(self) -> String {
287 self.0.into()
288 }
289}
290
291impl<T: Into<String>> From<PreEscaped<T>> for String {
292 fn from(value: PreEscaped<T>) -> String {
293 value.into_string()
294 }
295}
296
297impl<T: Default> Default for PreEscaped<T> {
298 fn default() -> Self {
299 Self(Default::default())
300 }
301}
302
303pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
326
327#[cfg(feature = "rocket")]
328mod rocket_support {
329 extern crate std;
330
331 use {
332 crate::PreEscaped,
333 alloc::string::String,
334 rocket::{
335 http::ContentType,
336 request::Request,
337 response::{Responder, Response},
338 },
339 std::io::Cursor,
340 };
341
342 impl Responder<'_, 'static> for PreEscaped<String> {
343 fn respond_to(self, _: &Request) -> rocket::response::Result<'static> {
344 Response::build()
345 .header(ContentType::HTML)
346 .sized_body(self.0.len(), Cursor::new(self.0))
347 .ok()
348 }
349 }
350}
351
352#[cfg(feature = "actix-web")]
353mod actix_support {
354 use {
355 crate::PreEscaped,
356 actix_web_dep::{
357 body::{BodySize, MessageBody},
358 http::header,
359 web::Bytes,
360 HttpRequest,
361 HttpResponse,
362 Responder,
363 },
364 alloc::string::String,
365 core::{
366 pin::Pin,
367 task::{Context, Poll},
368 },
369 };
370
371 impl MessageBody for PreEscaped<String> {
372 type Error = <String as MessageBody>::Error;
373
374 fn size(&self) -> BodySize {
375 self.0.size()
376 }
377
378 fn poll_next(
379 mut self: Pin<&mut Self>,
380 cx: &mut Context<'_>,
381 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
382 Pin::new(&mut self.0).poll_next(cx)
383 }
384 }
385
386 impl Responder for PreEscaped<String> {
387 type Body = String;
388
389 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
390 HttpResponse::Ok()
391 .content_type(header::ContentType::html())
392 .message_body(self.0)
393 .unwrap()
394 }
395 }
396}
397
398#[cfg(feature = "tide")]
399mod tide_support {
400 use {
401 crate::PreEscaped,
402 alloc::string::String,
403 tide::{http::mime, Response, StatusCode},
404 };
405
406 impl From<PreEscaped<String>> for Response {
407 fn from(markup: PreEscaped<String>) -> Response {
408 Response::builder(StatusCode::Ok)
409 .body(markup.into_string())
410 .content_type(mime::HTML)
411 .build()
412 }
413 }
414}
415
416#[cfg(feature = "axum")]
417mod axum_support {
418 use {
419 crate::Markup,
420 axum_core::response::{IntoResponse, Response},
421 http::{header, HeaderMap, HeaderValue},
422 };
423
424 impl IntoResponse for Markup {
425 fn into_response(self) -> Response {
426 let mut headers = HeaderMap::new();
427 headers.insert(
428 header::CONTENT_TYPE,
429 HeaderValue::from_static("text/html; charset=utf-8"),
430 );
431 (headers, self.content.0).into_response()
432 }
433 }
434}
435
436#[cfg(feature = "warp")]
437mod warp_support {
438 use {
439 crate::PreEscaped,
440 alloc::string::String,
441 warp::reply::{self, Reply, Response},
442 };
443
444 impl Reply for PreEscaped<String> {
445 fn into_response(self) -> Response {
446 reply::html(self.into_string()).into_response()
447 }
448 }
449}
450
451#[cfg(feature = "submillisecond")]
452mod submillisecond_support {
453 use {
454 crate::PreEscaped,
455 alloc::string::String,
456 submillisecond::{
457 http::{header, HeaderMap, HeaderValue},
458 response::{IntoResponse, Response},
459 },
460 };
461
462 impl IntoResponse for PreEscaped<String> {
463 fn into_response(self) -> Response {
464 let mut headers = HeaderMap::new();
465 headers.insert(
466 header::CONTENT_TYPE,
467 HeaderValue::from_static("text/html; charset=utf-8"),
468 );
469 (headers, self.0).into_response()
470 }
471 }
472}
473
474
475
476#[doc(hidden)]
477pub mod macro_private {
478 pub use hashbrown::HashSet;
479 use {
480 crate::{display, Render},
481 alloc::string::String,
482 core::fmt::Display,
483 };
484
485 #[doc(hidden)]
486 #[macro_export]
487 macro_rules! render_to {
488 ($x:expr, $buffer:expr) => {{
489 use $crate::macro_private::*;
490 match ChooseRenderOrDisplay($x) {
491 x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
492 }
493 }};
494 }
495
496 pub use render_to;
497
498 pub struct ChooseRenderOrDisplay<T>(pub T);
499
500 pub struct ViaRenderTag;
501 pub struct ViaDisplayTag;
502
503 pub trait ViaRender {
504 fn implements_render_or_display(&self) -> ViaRenderTag {
505 ViaRenderTag
506 }
507 }
508 pub trait ViaDisplay {
509 fn implements_render_or_display(&self) -> ViaDisplayTag {
510 ViaDisplayTag
511 }
512 }
513
514 impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
515 impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
516
517 impl ViaRenderTag {
518 pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
519 value.render_to(buffer);
520 }
521 }
522
523 impl ViaDisplayTag {
524 pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
525 display(value).render_to(buffer);
526 }
527 }
528}