heart_patched_maud/
lib.rs

1#![no_std]
2
3//! A macro for writing HTML templates.
4//!
5//! This documentation only describes the runtime API. For a general
6//! guide, check out the [book] instead.
7//!
8//! [book]: https://maud.lambda.xyz/
9
10#![doc(html_root_url = "https://docs.rs/maud/0.26.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
21/// An adapter that escapes HTML special characters.
22///
23/// The following characters are escaped:
24///
25/// * `&` is escaped as `&`
26/// * `<` is escaped as `&lt;`
27/// * `>` is escaped as `&gt;`
28/// * `"` is escaped as `&quot;`
29///
30/// All other characters are passed through unchanged.
31///
32/// **Note:** In versions prior to 0.13, the single quote (`'`) was
33/// escaped as well.
34///
35/// # Example
36///
37/// ```rust
38/// use maud::Escaper;
39/// use std::fmt::Write;
40/// let mut s = String::new();
41/// write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap();
42/// assert_eq!(s, "&lt;script&gt;launchMissiles()&lt;/script&gt;");
43/// ```
44pub struct Escaper<'a>(&'a mut String);
45
46impl<'a> Escaper<'a> {
47    /// Creates an `Escaper` from a `String`.
48    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
60/// Represents a type that can be rendered as HTML.
61///
62/// To implement this for your own type, override either the `.render()`
63/// or `.render_to()` methods; since each is defined in terms of the
64/// other, you only need to implement one of them. See the example below.
65///
66/// # Minimal implementation
67///
68/// An implementation of this trait must override at least one of
69/// `.render()` or `.render_to()`. Since the default definitions of
70/// these methods call each other, not doing this will result in
71/// infinite recursion.
72///
73/// # Example
74///
75/// ```rust
76/// use maud::{html, Markup, Render};
77///
78/// /// Provides a shorthand for linking to a CSS stylesheet.
79/// pub struct Stylesheet(&'static str);
80///
81/// impl Render for Stylesheet {
82///     fn render(&self) -> Markup {
83///         html! {
84///             link rel="stylesheet" type="text/css" href=(self.0);
85///         }
86///     }
87/// }
88/// ```
89pub trait Render {
90    /// Renders `self` as a block of `Markup`.
91    fn render(&self) -> Markup {
92        let mut buffer = String::new();
93        self.render_to(&mut buffer);
94        PreEscaped(buffer)
95    }
96
97    /// Appends a representation of `self` to the given buffer.
98    ///
99    /// Its default implementation just calls `.render()`, but you may
100    /// override it with something more efficient.
101    ///
102    /// Note that no further escaping is performed on data written to
103    /// the buffer. If you override this method, you must make sure that
104    /// any data written is properly escaped, whether by hand or using
105    /// the [`Escaper`](struct.Escaper.html) wrapper struct.
106    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                    // TODO: remove the explicit arg when Rust 1.58 is released
165                    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
193/// Renders a value using its [`Display`] impl.
194///
195/// # Example
196///
197/// ```rust
198/// use maud::html;
199/// use std::net::Ipv4Addr;
200///
201/// let ip_address = Ipv4Addr::new(127, 0, 0, 1);
202///
203/// let markup = html! {
204///     "My IP address is: "
205///     (maud::display(ip_address))
206/// };
207///
208/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1");
209/// ```
210pub 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/// A wrapper that renders the inner value without escaping.
223#[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
232/// A block of markup is a string that does not need to be escaped.
233///
234/// The `html!` macro expands to an expression of this type.
235pub type Markup = PreEscaped<String>;
236
237impl<T: Into<String>> PreEscaped<T> {
238    /// Converts the inner value to a string.
239    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
256/// The literal string `<!DOCTYPE html>`.
257///
258/// # Example
259///
260/// A minimal web page:
261///
262/// ```rust
263/// use maud::{DOCTYPE, html};
264///
265/// let markup = html! {
266///     (DOCTYPE)
267///     html {
268///         head {
269///             meta charset="utf-8";
270///             title { "Test page" }
271///         }
272///         body {
273///             p { "Hello, world!" }
274///         }
275///     }
276/// };
277/// ```
278pub 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 = "axum")]
363mod axum_support {
364    use crate::PreEscaped;
365    use alloc::string::String;
366    use axum_core::response::{IntoResponse, Response};
367    use http::{header, HeaderMap, HeaderValue};
368
369    impl IntoResponse for PreEscaped<String> {
370        fn into_response(self) -> Response {
371            let mut headers = HeaderMap::new();
372            headers.insert(
373                header::CONTENT_TYPE,
374                HeaderValue::from_static("text/html; charset=utf-8"),
375            );
376            (headers, self.0).into_response()
377        }
378    }
379}
380
381#[cfg(feature = "warp")]
382mod warp_support {
383    use crate::PreEscaped;
384    use alloc::string::String;
385    use warp::reply::{self, Reply, Response};
386
387    impl Reply for PreEscaped<String> {
388        fn into_response(self) -> Response {
389            reply::html(self.into_string()).into_response()
390        }
391    }
392}
393
394#[cfg(feature = "submillisecond")]
395mod submillisecond_support {
396    use crate::PreEscaped;
397    use alloc::string::String;
398    use submillisecond::{
399        http::{header, HeaderMap, HeaderValue},
400        response::{IntoResponse, Response},
401    };
402
403    impl IntoResponse for PreEscaped<String> {
404        fn into_response(self) -> Response {
405            let mut headers = HeaderMap::new();
406            headers.insert(
407                header::CONTENT_TYPE,
408                HeaderValue::from_static("text/html; charset=utf-8"),
409            );
410            (headers, self.0).into_response()
411        }
412    }
413}
414
415#[doc(hidden)]
416pub mod macro_private {
417    use crate::{display, Render};
418    use alloc::string::String;
419    use core::fmt::Display;
420
421    #[doc(hidden)]
422    #[macro_export]
423    macro_rules! render_to {
424        ($x:expr, $buffer:expr) => {{
425            use $crate::macro_private::*;
426            match ChooseRenderOrDisplay($x) {
427                x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
428            }
429        }};
430    }
431
432    pub use render_to;
433
434    pub struct ChooseRenderOrDisplay<T>(pub T);
435
436    pub struct ViaRenderTag;
437    pub struct ViaDisplayTag;
438
439    pub trait ViaRender {
440        fn implements_render_or_display(&self) -> ViaRenderTag {
441            ViaRenderTag
442        }
443    }
444    pub trait ViaDisplay {
445        fn implements_render_or_display(&self) -> ViaDisplayTag {
446            ViaDisplayTag
447        }
448    }
449
450    impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
451    impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
452
453    impl ViaRenderTag {
454        pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
455            value.render_to(buffer);
456        }
457    }
458
459    impl ViaDisplayTag {
460        pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
461            display(value).render_to(buffer);
462        }
463    }
464}