// #![no_std]
//! A macro for writing HTML templates.
//!
//! This documentation only describes the runtime API. For a general
//! guide, check out the [book] instead.
//!
//! [book]: https://maud.lambda.xyz/
// #![doc(html_root_url = "https://docs.rs/maud/0.27.0")]
extern crate alloc;
use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
use core::fmt::{self, Arguments, Display, Write};
pub use pagetop_macros::html;
mod escape;
/// Adaptador que escapa los caracteres especiales de HTML.
///
/// The following characters are escaped:
///
/// * `&` is escaped as `&`
/// * `<` is escaped as `<`
/// * `>` is escaped as `>`
/// * `"` is escaped as `"`
///
/// All other characters are passed through unchanged.
///
/// **Note:** In versions prior to 0.13, the single quote (`'`) was
/// escaped as well.
///
/// # Example
///
/// ```rust
/// use pagetop::html::Escaper;
/// use std::fmt::Write;
/// let mut s = String::new();
/// write!(Escaper::new(&mut s), "").unwrap();
/// assert_eq!(s, "<script>launchMissiles()</script>");
/// ```
pub struct Escaper<'a>(&'a mut String);
impl<'a> Escaper<'a> {
/// Creates an `Escaper` from a `String`.
pub fn new(buffer: &'a mut String) -> Escaper<'a> {
Escaper(buffer)
}
}
impl fmt::Write for Escaper<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
escape::escape_to_string(s, self.0);
Ok(())
}
}
/// Representa un tipo que puede renderizarse como HTML.
///
/// To implement this for your own type, override either the `.render()`
/// or `.render_to()` methods; since each is defined in terms of the
/// other, you only need to implement one of them. See the example below.
///
/// # Minimal implementation
///
/// An implementation of this trait must override at least one of
/// `.render()` or `.render_to()`. Since the default definitions of
/// these methods call each other, not doing this will result in
/// infinite recursion.
///
/// # Example
///
/// ```rust
/// use pagetop::prelude::*;
///
/// /// Provides a shorthand for linking to a CSS stylesheet.
/// pub struct Stylesheet(&'static str);
///
/// impl Render for Stylesheet {
/// fn render(&self) -> Markup {
/// html! {
/// link rel="stylesheet" type="text/css" href=(self.0);
/// }
/// }
/// }
/// ```
pub trait Render {
/// Renders `self` as a block of `Markup`.
fn render(&self) -> Markup {
let mut buffer = String::new();
self.render_to(&mut buffer);
PreEscaped(buffer)
}
/// Appends a representation of `self` to the given buffer.
///
/// Its default implementation just calls `.render()`, but you may
/// override it with something more efficient.
///
/// Note that no further escaping is performed on data written to
/// the buffer. If you override this method, you must make sure that
/// any data written is properly escaped, whether by hand or using
/// the [`Escaper`](struct.Escaper.html) wrapper struct.
fn render_to(&self, buffer: &mut String) {
buffer.push_str(&self.render().into_string());
}
}
impl Render for str {
fn render_to(&self, w: &mut String) {
escape::escape_to_string(self, w);
}
}
impl Render for String {
fn render_to(&self, w: &mut String) {
str::render_to(self, w);
}
}
impl Render for Cow<'_, str> {
fn render_to(&self, w: &mut String) {
str::render_to(self, w);
}
}
impl Render for Arguments<'_> {
fn render_to(&self, w: &mut String) {
let _ = Escaper::new(w).write_fmt(*self);
}
}
impl Render for &T {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
impl Render for &mut T {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
impl Render for Box {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
impl Render for Arc {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
macro_rules! impl_render_with_display {
($($ty:ty)*) => {
$(
impl Render for $ty {
fn render_to(&self, w: &mut String) {
// TODO: remove the explicit arg when Rust 1.58 is released
format_args!("{self}", self = self).render_to(w);
}
}
)*
};
}
impl_render_with_display! {
char f32 f64
}
macro_rules! impl_render_with_itoa {
($($ty:ty)*) => {
$(
impl Render for $ty {
fn render_to(&self, w: &mut String) {
w.push_str(itoa::Buffer::new().format(*self));
}
}
)*
};
}
impl_render_with_itoa! {
i8 i16 i32 i64 i128 isize
u8 u16 u32 u64 u128 usize
}
/// Renderiza un valor usando su implementación de [`Display`].
///
/// # Example
///
/// ```rust
/// use pagetop::prelude::*;
/// use std::net::Ipv4Addr;
///
/// let ip_address = Ipv4Addr::new(127, 0, 0, 1);
///
/// let markup = html! {
/// "My IP address is: "
/// (display(ip_address))
/// };
///
/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1");
/// ```
pub fn display(value: impl Display) -> impl Render {
struct DisplayWrapper(T);
impl Render for DisplayWrapper {
fn render_to(&self, w: &mut String) {
format_args!("{0}", self.0).render_to(w);
}
}
DisplayWrapper(value)
}
/// Contenedor que renderiza el valor interno sin escapar.
#[derive(Debug, Clone, Copy)]
pub struct PreEscaped(pub T);
impl> Render for PreEscaped {
fn render_to(&self, w: &mut String) {
w.push_str(self.0.as_ref());
}
}
/// Un bloque de marcado es una cadena que no necesita ser escapada.
///
/// The `html!` macro expands to an expression of this type.
pub type Markup = PreEscaped;
impl Markup {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl> PreEscaped {
/// Converts the inner value to a string.
pub fn into_string(self) -> String {
self.0.into()
}
}
impl> From> for String {
fn from(value: PreEscaped) -> String {
value.into_string()
}
}
impl Default for PreEscaped {
fn default() -> Self {
Self(Default::default())
}
}
/// La cadena literal ``.
///
/// # Example
///
/// A minimal web page:
///
/// ```rust
/// use pagetop::prelude::*;
///
/// let markup = html! {
/// (DOCTYPE)
/// html {
/// head {
/// meta charset="utf-8";
/// title { "Test page" }
/// }
/// body {
/// p { "Hello, world!" }
/// }
/// }
/// };
/// ```
pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("");
mod actix_support {
extern crate alloc;
use core::{
pin::Pin,
task::{Context, Poll},
};
use crate::html::PreEscaped;
use actix_web::{
body::{BodySize, MessageBody},
http::header,
web::Bytes,
HttpRequest, HttpResponse, Responder,
};
use alloc::string::String;
impl MessageBody for PreEscaped {
type Error = ::Error;
fn size(&self) -> BodySize {
self.0.size()
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll