htmlite 0.19.0

An HTML manipulation toolkit
Documentation
#[doc(hidden)]
#[macro_export]
macro_rules! attributes_impl {
    ([$($acc:tt)*] $name:expr => $value:expr, $($rest:tt)*) => {
        $crate::attributes_impl!(
            [$($acc)* ((&$name).to_string(), (&$value).to_string()),]
            $($rest)*
        )
    };

    ([$($acc:tt)*] $name:expr => $value:expr) => {
        $crate::attributes_impl!([$($acc)* ((&$name).to_string(), (&$value).to_string()),])
    };

    ([$($acc:tt)*]) => {
        [$($acc)*] as [(String, String); _]
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! html_impl {
    // (text "some text")
    ([$($acc:tt)*] (text $t:expr) $($rest:tt)*) => {{
        let text = $crate::text($t);
        $crate::html_impl!(
            [$($acc)* text,]
            $($rest)*
        )
    }};

    // (raw "some unescaped test")
    ([$($acc:tt)*] (raw $t:expr) $($rest:tt)*) => {{
        let text = $crate::raw_text($t);
        $crate::html_impl!(
            [$($acc)* text,]
            $($rest)*
        )
    }};

    // (>...)
    ([$($acc:tt)*] (> $children:expr) $($rest:tt)*) => {{
        let fragment = $crate::fragment($children);
        $crate::html_impl!(
            [$($acc)* fragment,]
            $($rest)*
        )
    }};


    // (["tag"] [<attributes>] ... )
    ([$($acc:tt)*] ([$tagname:expr] [$($attributes:tt)*] $($children:tt)*) $($rest:tt)*) => {{
        let attributes = $crate::attributes_impl!([] $($attributes)*);
        let element = $crate::tag($tagname, attributes, []);
        element.append($crate::html_impl!(
            []
            $($children)*
        ));
        $crate::html_impl!(
            [$($acc)* element,]
            $($rest)*
        )
    }};

    // (tag [<attribtes>] ... )
    ([$($acc:tt)*] ($tagname:ident [$($attributes:tt)*] $($children:tt)*) $($rest:tt)*) => {{
        let tagname = stringify!($tagname);
        $crate::html_impl!(
            [$($acc)*]
            ([tagname] [$($attributes)*] $($children)*)
            $($rest)*
        )
    }};


    // (tag (...))
    ([$($acc:tt)*] ($tagname:ident $($children:tt)*) $($rest:tt)*) => {{
        let tagname = stringify!($tagname);
        $crate::html_impl!(
            [$($acc)*]
            ([tagname] [] $($children)*)
            $($rest)*
        )
    }};

    // (["tag"] (...))
    ([$($acc:tt)*] ([$tagname:expr] $($children:tt)*) $($rest:tt)*) => {{
        $crate::html_impl!(
            [$($acc)*]
            ([$tagname] [] $($children)*)
            $($rest)*
        )
    }};

    ([$($top:tt)*]) => {
        $crate::fragment([$($top)*])
    }
}

/// This macro implements a syntax for creating HTML Nodes.
///
/// It accepts an [SXML-like dsl](https://en.wikipedia.org/wiki/SXML) that represents your HTML.
///
/// # Markup syntax
///
/// The markup consistes of one or more elements, where each element is wrapped in parentheses.
/// The first identifier after the opening parentheses is the element's tag name.
///
/// ```
/// use htmlite::html;
///
/// let out = html!(
///     (header)
///     (main)
///     (footer)
/// );
///
/// assert_eq!(out.html(), "<header></header><main></main><footer></footer>");
/// ```
///
/// Should you need to use a character that is not allowed as a rust identifier, you can wrap the name in brackets and use a string literal instead:
///
/// ```
/// use htmlite::html;
///
/// let out = html!(
///     (["a weird name"])
///     (["!@wow"])
/// );
/// assert_eq!(out.html(), "<a weird name></a weird name><!@wow></!@wow>");
/// ```
///
/// Elements can have attributes.
/// These are expressed as a key-value list seperated by `=>`.
/// Both key & value must implement [`std::fmt::Display`].
///
/// ```
/// use htmlite::html;
///
/// let out = html!(
///     (input ["readonly" => "", "placeholder" => "hello"])
/// );
///
/// assert_eq!(out.html(), r#"<input placeholder="hello" readonly="">"#)
/// ```
///
/// Elements can have children.
/// These come after the attributes (if any).
///
/// ```
/// use htmlite::html;
///
/// let out = html!(
///     (section
///         ["class" => "container"]
///         (div)
///         (p (span))
///     )
/// );
/// assert_eq!(out.html(), r#"<section class="container"><div></div><p><span></span></p></section>"#)
/// ```
///
/// Use `text` for HTML-escaped text, and `raw` for unescaped text:
///
/// ```
/// use htmlite::html;
///
/// let out = html!(
///     (p
///         (span (text "<a>"))
///         (span (raw "<a>"))
///     )
/// );
/// assert_eq!(out.html(), "<p><span>&lt;a&gt;</span><span><a></span></p>")
/// ```
///
/// Lastly, use `>` to interpolate any rust expression that returns a [`Node`](crate::Node) or iterator of `Node`s.
///
/// ```
/// use htmlite::html;
///
/// let snippet = "<div><p><li>One</li><li>Two</li></p></div>";
///
/// let parsed = htmlite::parse(snippet).unwrap();
///
/// let dst = html!(
///     (ul (> htmlite::select("li", parsed.descendants())))
/// );
///
/// assert_eq!(dst.html(), "<ul><li>One</li><li>Two</li></ul>");
/// ```
#[macro_export]
macro_rules! html {
    ($($dsl:tt)*) => {
        $crate::html_impl!([] $($dsl)*)
    };
}

#[cfg(test)]
mod macro_tests {
    #[test]
    fn empty() {
        let res = html!();
        assert_eq!(res.to_string(), "");
    }

    #[test]
    fn tag_names() {
        let res = html!((span)(header));
        assert_eq!(res.to_string(), "<span></span><header></header>");
    }

    #[test]
    fn non_rust_literal_tag_names() {
        let res = html!((["!@wow"])(["very odd"]));

        assert_eq!(res.to_string(), "<!@wow></!@wow><very odd></very odd>");
    }

    #[test]
    fn with_attributes() {
        let res = html!(
            (a ['a' => "b", String::from("wow") => 1, &3.2 => &'c'])
        );

        assert_eq!(res.html(), "<a 3.2=\"c\" a=\"b\" wow=\"1\"></a>");
    }

    #[test]
    fn with_children() {
        let without_attributes = html!((div(span)));
        assert_eq!(without_attributes.html(), "<div><span></span></div>");

        let with_attributes = html!(
            (div ["class" => "card"]
                (span)
            )
        );
        assert_eq!(
            with_attributes.html(),
            "<div class=\"card\"><span></span></div>"
        );
    }

    #[test]
    fn with_text() {
        let res = html!(
            (div (text "<>"))
            (span (raw "<>"))
        );
        assert_eq!(res.html(), "<div>&lt;&gt;</div><span><></span>");
    }

    #[test]
    fn with_node_iterators() {
        let opt = Some(html!((p)));
        let list = vec![crate::text("hello "), crate::text("world")];
        let single = crate::text("how goes it");

        let res = html!(
            (> opt)
            (> list)
            (> single)
        );

        assert_eq!(res.html(), "<p></p>hello worldhow goes it");
    }
}