rempl 0.3.1

A simple library for creating html components directly in your source
Documentation
//! Rempl is a simple toolkit for creating html components
//! directly within your rust code. The intended purpose
//! is to use this library for HTML templating.
//!
//! The main things to look at are the [`macro@html`] and
//! [`macro@component`] macros.
//!
//! All of the HTML structs ([`HtmlTerm`], [`HtmlElement`],
//! [`HtmlNode`]) can be turned into a html string simply
//! be using the [`std::fmt::Display`] trait.
use std::collections::HashMap;

pub use rempl_macros::component;

/// Allows you to write html directly within your source
/// file.
///
/// The html macro expands to the html element types from
/// the main rempl crate, but let's you create them using
/// easy to write HTML. It also allows you to substitute
/// any attributes or even html content with rust expressions.
/// Due to limitations of the rust tokenizer, unfortunately
/// strings in the html will have to wrapped in quotes.
///
/// You can also call [`macro@component`]s by using the `@` syntax,
/// in this case, attributes are passed as parameters, and the child
/// content is passed as the special require children parameter.
///
///
/// No-children Example:
///
/// ```
/// use rempl::{html, component, HtmlNode, HtmlTerm};
///
/// #[component]
/// fn Figure(caption: String, src: String) -> HtmlTerm {
///     html! {
///         <figure>
///             <img src={ src } alt={ caption } />
///             <figcaption>{ caption }</figcaption>
///         </figure>
///     }
/// }
///
///
/// let image_filename = "/image.png".to_string();
/// let test_result = html! {
///     <@Figure caption="A generic caption" src={ image_filename }/>
/// };
/// let expected_result = "<figure><img alt=\"A generic caption\" src=\"/image.png\"/><figcaption>A generic caption</figcaption></figure>";
/// assert_eq!(test_result.to_string(), expected_result);
/// ```
///
/// Example with children:
///
/// ```
/// use rempl::{html, component, HtmlNode, HtmlTerm};
///
/// #[component]
/// fn Echo(children: HtmlNode) -> HtmlTerm {
///     html! {
///         <p>{ children }</p>
///     }
/// }
///
/// let msg = "Hello, world!";
/// let test_result = html! {
///     <@Echo>{ msg }</Echo>
/// };
/// let expected_result = "<p>Hello, world!</p>";
/// assert_eq!(test_result.to_string(), expected_result);
/// ```
pub use rempl_macros::html;

/// A rust native description of a html term.
pub enum HtmlTerm {
    /// Represents a html element, go look a [`HtmlElement`].
    Element(HtmlElement),
    /// Represents a raw text string in the HTML.
    Text(String),
}

/// A html element
pub struct HtmlElement {
    pub name: String,
    pub attributes: HashMap<String, String>,
    pub content: Option<HtmlNode>,
}

impl HtmlElement {
    pub fn new(
        name: String,
        attributes: HashMap<String, String>,
        content: Option<HtmlNode>,
    ) -> Self {
        Self {
            name,
            attributes,
            content,
        }
    }
}

/// Represents the children of a HTML element.
#[derive(Default)]
pub struct HtmlNode(pub Vec<HtmlTerm>);

impl std::fmt::Display for HtmlNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for e in &self.0 {
            write!(f, "{}", e)?;
        }

        Ok(())
    }
}

impl std::fmt::Display for HtmlElement {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<{}", self.name)?;
        let mut v: Vec<_> = self.attributes.iter().collect();
        v.sort_by_key(|&(name, _)| name);
        for (name, val) in v {
            write!(f, " {}=\"{}\"", name, val)?;
        }
        match &self.content {
            Some(content) => {
                write!(f, ">")?;
                for c in &content.0 {
                    write!(f, "{}", c)?;
                }
                write!(f, "</{}>", self.name)?;
            }
            None => {
                write!(f, "/>")?;
            }
        }

        Ok(())
    }
}

impl std::fmt::Display for HtmlTerm {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            HtmlTerm::Element(e) => e.fmt(f),
            HtmlTerm::Text(t) => t.fmt(f),
        }
    }
}