use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone)]
pub struct Tag {
pub name: &'static str,
pub args: Vec<(&'static str, String)>,
pub children: Vec<Element>,
pub self_closing: bool,
}
#[derive(Debug, Clone)]
pub enum Element {
Tag(Tag),
String(String),
}
#[derive(Clone, Copy)]
pub struct Indent(u32);
impl Tag {
pub fn new(name: &'static str) -> Self {
Self {
name,
args: Vec::new(),
children: Vec::new(),
self_closing: false,
}
}
}
impl Element {
pub fn render(&self, f: &mut Formatter<'_>, ind: Indent) -> fmt::Result {
match self {
Self::Tag(Tag {
name,
args,
children,
self_closing,
}) => {
write!(f, "{ind}<{name}")?;
for (name, value) in args {
write!(f, " {name}={value:?}")?;
}
write!(f, ">")?;
match &children[..] {
[] if *self_closing => writeln!(f),
[] => writeln!(f, "</{name}>"),
[Element::String(s)] => writeln!(f, "{s}</{name}>"),
_ => {
writeln!(f)?;
for child in children {
child.render(f, ind.next())?;
}
writeln!(f, "{ind}</{name}>")?;
Ok(())
}
}
}
Self::String(s) if s.is_empty() => Ok(()),
Self::String(s) => writeln!(f, "{ind}{s}"),
}
}
}
impl From<&str> for Element {
fn from(value: &str) -> Self {
Self::String(value.into())
}
}
impl From<String> for Element {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl Display for Element {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.render(f, Indent(0))
}
}
impl Display for Indent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for _ in 0..self.0 {
write!(f, "\t")?;
}
Ok(())
}
}
impl Indent {
fn next(self) -> Self {
Self(self.0 + 1)
}
}
#[macro_export]
macro_rules! html {
($name:ident $([ $($an:tt = $av:expr),* $(,)? ])? { $($tk:tt)* }) => {
{
#[allow(unused_mut)]
let mut tag = $crate::Tag {
name: stringify!($name),
args: vec! [
$(
$(
(html!(@ $an), $av.to_string())
),*
)?
],
children: Vec::new(),
self_closing: false,
};
html! {
! tag $($tk)*
}
$crate::Element::Tag(tag)
}
};
(! $parent:ident $name:ident $([$($args:tt)*])? { $($tk:tt)* } $($rest:tt)*) => {
$parent.children.push(html! { $name $([$($args)*])? { $($tk)* } });
html! {
! $parent $($rest)*
}
};
(! $parent:ident $name:ident $([$($an:ident = $av:expr),*])? ; $($rest:tt)*) => {
$parent.children.push(
$crate::Tag($crate::Tag {
name: stringify!($name),
args: vec! [
$(
$(
(html! { @$an}, $av.to_string())
),*
)?
],
children: Vec::new(),
self_closing: true,
})
);
html! {
! $parent $($rest)*
}
};
(! $parent:ident $name:ident = $val:expr ; $($rest:tt)*) => {
$parent.children.push($crate::Element::Tag($crate::Tag {
name: stringify!($name),
args: Vec::new(),
children: vec![
$crate::Element::String($val.into())
],
self_closing: false,
}));
html! {
! $parent $($rest)*
}
};
(! $parent:ident $s:literal ; $($rest:tt)*) => {
$parent.children.push($crate::Element::String(format!($s)));
html! {
! $parent $($rest)*
}
};
(! $parent:ident { $e:expr } $($rest:tt)*) => {
$parent.children.push($e.into());
html! {
! $parent $($rest)*
}
};
(! $parent:ident ? { $e:expr } $($rest:tt)*) => {
if let Some(e) = $e {
$parent.children.push(e.into());
}
html! {
! $parent $($rest)*
}
};
(! $parent:ident [ $e:expr ] $($rest:tt)*) => {
for e in $e {
$parent.children.push(e.into());
}
html! {
! $parent $($rest)*
}
};
(! $parent:ident) => {};
(@ $i:ident) => { stringify!($i) };
(@ $s:literal) => { $s };
}