1#![no_std]
2
3#![doc(html_root_url = "https://docs.rs/maud/0.27.0")]
11
12extern crate alloc;
13
14use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
15use core::fmt::{self, Arguments, Display, Write};
16
17pub use maud_macros::html;
18
19mod escape;
20
21pub struct Escaper<'a>(&'a mut String);
45
46impl<'a> Escaper<'a> {
47 pub fn new(buffer: &'a mut String) -> Escaper<'a> {
49 Escaper(buffer)
50 }
51}
52
53impl fmt::Write for Escaper<'_> {
54 fn write_str(&mut self, s: &str) -> fmt::Result {
55 escape::escape_to_string(s, self.0);
56 Ok(())
57 }
58}
59
60pub trait Render {
90 fn render(&self) -> Markup {
92 let mut buffer = String::new();
93 self.render_to(&mut buffer);
94 PreEscaped(buffer)
95 }
96
97 fn render_to(&self, buffer: &mut String) {
107 buffer.push_str(&self.render().into_string());
108 }
109}
110
111impl Render for str {
112 fn render_to(&self, w: &mut String) {
113 escape::escape_to_string(self, w);
114 }
115}
116
117impl Render for String {
118 fn render_to(&self, w: &mut String) {
119 str::render_to(self, w);
120 }
121}
122
123impl Render for Cow<'_, str> {
124 fn render_to(&self, w: &mut String) {
125 str::render_to(self, w);
126 }
127}
128
129impl Render for Arguments<'_> {
130 fn render_to(&self, w: &mut String) {
131 let _ = Escaper::new(w).write_fmt(*self);
132 }
133}
134
135impl<T: Render + ?Sized> Render for &T {
136 fn render_to(&self, w: &mut String) {
137 T::render_to(self, w);
138 }
139}
140
141impl<T: Render + ?Sized> Render for &mut T {
142 fn render_to(&self, w: &mut String) {
143 T::render_to(self, w);
144 }
145}
146
147impl<T: Render + ?Sized> Render for Box<T> {
148 fn render_to(&self, w: &mut String) {
149 T::render_to(self, w);
150 }
151}
152
153impl<T: Render + ?Sized> Render for Arc<T> {
154 fn render_to(&self, w: &mut String) {
155 T::render_to(self, w);
156 }
157}
158
159macro_rules! impl_render_with_display {
160 ($($ty:ty)*) => {
161 $(
162 impl Render for $ty {
163 fn render_to(&self, w: &mut String) {
164 format_args!("{self}", self = self).render_to(w);
166 }
167 }
168 )*
169 };
170}
171
172impl_render_with_display! {
173 char f32 f64
174}
175
176macro_rules! impl_render_with_itoa {
177 ($($ty:ty)*) => {
178 $(
179 impl Render for $ty {
180 fn render_to(&self, w: &mut String) {
181 w.push_str(itoa::Buffer::new().format(*self));
182 }
183 }
184 )*
185 };
186}
187
188impl_render_with_itoa! {
189 i8 i16 i32 i64 i128 isize
190 u8 u16 u32 u64 u128 usize
191}
192
193pub fn display(value: impl Display) -> impl Render {
211 struct DisplayWrapper<T>(T);
212
213 impl<T: Display> Render for DisplayWrapper<T> {
214 fn render_to(&self, w: &mut String) {
215 format_args!("{0}", self.0).render_to(w);
216 }
217 }
218
219 DisplayWrapper(value)
220}
221
222#[derive(Debug, Clone, Copy)]
224pub struct PreEscaped<T>(pub T);
225
226impl<T: AsRef<str>> Render for PreEscaped<T> {
227 fn render_to(&self, w: &mut String) {
228 w.push_str(self.0.as_ref());
229 }
230}
231
232pub type Markup = PreEscaped<String>;
236
237impl<T: Into<String>> PreEscaped<T> {
238 pub fn into_string(self) -> String {
240 self.0.into()
241 }
242}
243
244impl<T: Into<String>> From<PreEscaped<T>> for String {
245 fn from(value: PreEscaped<T>) -> String {
246 value.into_string()
247 }
248}
249
250impl<T: Default> Default for PreEscaped<T> {
251 fn default() -> Self {
252 Self(Default::default())
253 }
254}
255
256pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
279
280#[cfg(feature = "rocket")]
281mod rocket_support {
282 extern crate std;
283
284 use crate::PreEscaped;
285 use alloc::string::String;
286 use rocket::{
287 http::ContentType,
288 request::Request,
289 response::{Responder, Response},
290 };
291 use std::io::Cursor;
292
293 impl Responder<'_, 'static> for PreEscaped<String> {
294 fn respond_to(self, _: &Request) -> rocket::response::Result<'static> {
295 Response::build()
296 .header(ContentType::HTML)
297 .sized_body(self.0.len(), Cursor::new(self.0))
298 .ok()
299 }
300 }
301}
302
303#[cfg(feature = "actix-web")]
304mod actix_support {
305 use core::{
306 pin::Pin,
307 task::{Context, Poll},
308 };
309
310 use crate::PreEscaped;
311 use actix_web_dep::{
312 body::{BodySize, MessageBody},
313 http::header,
314 web::Bytes,
315 HttpRequest, HttpResponse, Responder,
316 };
317 use alloc::string::String;
318
319 impl MessageBody for PreEscaped<String> {
320 type Error = <String as MessageBody>::Error;
321
322 fn size(&self) -> BodySize {
323 self.0.size()
324 }
325
326 fn poll_next(
327 mut self: Pin<&mut Self>,
328 cx: &mut Context<'_>,
329 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
330 Pin::new(&mut self.0).poll_next(cx)
331 }
332 }
333
334 impl Responder for PreEscaped<String> {
335 type Body = String;
336
337 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
338 HttpResponse::Ok()
339 .content_type(header::ContentType::html())
340 .message_body(self.0)
341 .unwrap()
342 }
343 }
344}
345
346#[cfg(feature = "tide")]
347mod tide_support {
348 use crate::PreEscaped;
349 use alloc::string::String;
350 use tide::{http::mime, Response, StatusCode};
351
352 impl From<PreEscaped<String>> for Response {
353 fn from(markup: PreEscaped<String>) -> Response {
354 Response::builder(StatusCode::Ok)
355 .body(markup.into_string())
356 .content_type(mime::HTML)
357 .build()
358 }
359 }
360}
361
362#[cfg(feature = "poem")]
363mod poem_support {
364 use crate::PreEscaped;
365 use alloc::string::String;
366 use poem::{web::Html, IntoResponse, Response};
367
368 impl IntoResponse for PreEscaped<String> {
369 fn into_response(self) -> Response {
370 Html(self.into_string()).into_response()
371 }
372 }
373}
374
375#[cfg(feature = "axum")]
376mod axum_support {
377 use crate::PreEscaped;
378 use alloc::string::String;
379 use axum_core::response::{IntoResponse, Response};
380 use http::{header, HeaderMap, HeaderValue};
381
382 impl IntoResponse for PreEscaped<String> {
383 fn into_response(self) -> Response {
384 let headers = [(
385 header::CONTENT_TYPE,
386 HeaderValue::from_static("text/html; charset=utf-8"),
387 )];
388 (headers, self.0).into_response()
389 }
390 }
391}
392
393#[cfg(feature = "warp")]
394mod warp_support {
395 use crate::PreEscaped;
396 use alloc::string::String;
397 use warp::reply::{self, Reply, Response};
398
399 impl Reply for PreEscaped<String> {
400 fn into_response(self) -> Response {
401 reply::html(self.into_string()).into_response()
402 }
403 }
404}
405
406#[cfg(feature = "submillisecond")]
407mod submillisecond_support {
408 use crate::PreEscaped;
409 use alloc::string::String;
410 use submillisecond::{
411 http::{header, HeaderMap, HeaderValue},
412 response::{IntoResponse, Response},
413 };
414
415 impl IntoResponse for PreEscaped<String> {
416 fn into_response(self) -> Response {
417 let mut headers = HeaderMap::new();
418 headers.insert(
419 header::CONTENT_TYPE,
420 HeaderValue::from_static("text/html; charset=utf-8"),
421 );
422 (headers, self.0).into_response()
423 }
424 }
425}
426
427#[doc(hidden)]
428pub mod macro_private {
429 use crate::{display, Render};
430 use alloc::string::String;
431 use core::fmt::Display;
432
433 #[doc(hidden)]
434 #[macro_export]
435 macro_rules! render_to {
436 ($x:expr, $buffer:expr) => {{
437 use $crate::macro_private::*;
438 match ChooseRenderOrDisplay($x) {
439 x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
440 }
441 }};
442 }
443
444 pub use render_to;
445
446 pub struct ChooseRenderOrDisplay<T>(pub T);
447
448 pub struct ViaRenderTag;
449 pub struct ViaDisplayTag;
450
451 pub trait ViaRender {
452 fn implements_render_or_display(&self) -> ViaRenderTag {
453 ViaRenderTag
454 }
455 }
456 pub trait ViaDisplay {
457 fn implements_render_or_display(&self) -> ViaDisplayTag {
458 ViaDisplayTag
459 }
460 }
461
462 impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
463 impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
464
465 impl ViaRenderTag {
466 pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
467 value.render_to(buffer);
468 }
469 }
470
471 impl ViaDisplayTag {
472 pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
473 display(value).render_to(buffer);
474 }
475 }
476}