Skip to main content

inline_html/
lib.rs

1use std::fmt::{self, Display, Formatter};
2
3#[derive(Debug, Clone)]
4pub struct Tag {
5    pub name: &'static str,
6    pub args: Vec<(&'static str, String)>,
7    pub children: Vec<Element>,
8    pub self_closing: bool,
9}
10
11#[derive(Debug, Clone)]
12pub enum Element {
13    Tag(Tag),
14    String(String),
15}
16
17#[derive(Clone, Copy)]
18pub struct Indent(u32);
19
20impl Tag {
21    pub fn new(name: &'static str) -> Self {
22        Self {
23            name,
24            args: Vec::new(),
25            children: Vec::new(),
26            self_closing: false,
27        }
28    }
29}
30
31impl Element {
32    pub fn render(&self, f: &mut Formatter<'_>, ind: Indent) -> fmt::Result {
33        match self {
34            Self::Tag(Tag {
35                name,
36                args,
37                children,
38                self_closing,
39            }) => {
40                write!(f, "{ind}<{name}")?;
41                for (name, value) in args {
42                    write!(f, " {name}={value:?}")?;
43                }
44                write!(f, ">")?;
45                match &children[..] {
46                    [] if *self_closing => writeln!(f),
47                    [] => writeln!(f, "</{name}>"),
48                    [Element::String(s)] => writeln!(f, "{s}</{name}>"),
49                    _ => {
50                        writeln!(f)?;
51
52                        for child in children {
53                            child.render(f, ind.next())?;
54                        }
55
56                        writeln!(f, "{ind}</{name}>")?;
57                        Ok(())
58                    }
59                }
60            }
61            Self::String(s) if s.is_empty() => Ok(()),
62            Self::String(s) => writeln!(f, "{ind}{s}"),
63        }
64    }
65}
66
67impl From<&str> for Element {
68    fn from(value: &str) -> Self {
69        Self::String(value.into())
70    }
71}
72
73impl From<String> for Element {
74    fn from(value: String) -> Self {
75        Self::String(value)
76    }
77}
78
79impl Display for Element {
80    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81        self.render(f, Indent(0))
82    }
83}
84
85impl Display for Indent {
86    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
87        for _ in 0..self.0 {
88            write!(f, "\t")?;
89        }
90        Ok(())
91    }
92}
93
94impl Indent {
95    fn next(self) -> Self {
96        Self(self.0 + 1)
97    }
98}
99
100#[macro_export]
101macro_rules! html {
102    ($name:ident $([ $($an:tt = $av:expr),* $(,)? ])? { $($tk:tt)* }) => {
103	{
104	    #[allow(unused_mut)]
105	    let mut tag = $crate::Tag {
106		name: stringify!($name),
107		args: vec! [
108		    $(
109			$(
110			    (html!(@ $an), $av.to_string())
111			),*
112		    )?
113		],
114		children: Vec::new(),
115		self_closing: false,
116	    };
117
118	    html! {
119		! tag $($tk)*
120	    }
121
122	    $crate::Element::Tag(tag)
123	}
124    };
125    (! $parent:ident $name:ident $([$($args:tt)*])? { $($tk:tt)* } $($rest:tt)*) => {
126	$parent.children.push(html! { $name $([$($args)*])? { $($tk)* } });
127
128	html! {
129	    ! $parent $($rest)*
130	}
131    };
132    (! $parent:ident $name:ident $([$($an:ident = $av:expr),*])? ; $($rest:tt)*) => {
133	$parent.children.push(
134	    $crate::Tag($crate::Tag {
135		name: stringify!($name),
136		args: vec! [
137		    $(
138			$(
139			    (html! { @$an}, $av.to_string())
140			),*
141		    )?
142		],
143		children: Vec::new(),
144		self_closing: true,
145	    })
146	);
147
148	html! {
149	    ! $parent $($rest)*
150	}
151    };
152    (! $parent:ident $name:ident = $val:expr ; $($rest:tt)*) => {
153	$parent.children.push($crate::Element::Tag($crate::Tag {
154	    name: stringify!($name),
155	    args: Vec::new(),
156	    children: vec![
157            $crate::Element::String($val.into())
158	    ],
159	    self_closing: false,
160	}));
161
162	html! {
163	    ! $parent $($rest)*
164	}
165    };
166    (! $parent:ident $s:literal ; $($rest:tt)*) => {
167	$parent.children.push($crate::Element::String(format!($s)));
168
169	html! {
170	    ! $parent $($rest)*
171	}
172    };
173    (! $parent:ident { $e:expr } $($rest:tt)*) => {
174	$parent.children.push($e.into());
175
176	html! {
177	    ! $parent $($rest)*
178	}
179    };
180    (! $parent:ident ? { $e:expr } $($rest:tt)*) => {
181	if let Some(e) = $e {
182	    $parent.children.push(e.into());
183	}
184
185	html! {
186	    ! $parent $($rest)*
187	}
188    };
189    (! $parent:ident [ $e:expr ] $($rest:tt)*) => {
190	for e in $e {
191	    $parent.children.push(e.into());
192	}
193
194	html! {
195	    ! $parent $($rest)*
196	}
197    };
198    (! $parent:ident) => {};
199    (@ $i:ident) => { stringify!($i) };
200    (@ $s:literal) => { $s };
201}