Skip to content

Instantly share code, notes, and snippets.

@shritesh
Created September 21, 2024 15:05
Show Gist options
  • Save shritesh/346a87643280d1f7cf8397dff89fe8e6 to your computer and use it in GitHub Desktop.
Save shritesh/346a87643280d1f7cf8397dff89fe8e6 to your computer and use it in GitHub Desktop.
HTML in Rust
// TODO: escaping
enum Attribute {
Boolean(String),
Normal(String, String),
}
pub struct Element {
name: &'static str,
attributes: Vec<Attribute>,
child: Node,
}
impl Element {
pub fn on(mut self, event: &'static str, value: impl Into<String>) -> Self {
self.attributes
.push(Attribute::Normal(format!("x-on:{event}"), value.into()));
self
}
pub fn bind(mut self, attr: &'static str, value: impl Into<String>) -> Self {
self.attributes
.push(Attribute::Normal(format!("x-bind:{attr}"), value.into()));
self
}
}
macro_rules! impl_attrs {
($attr:ident) => {
impl Element {
pub fn $attr(mut self, value: impl Into<String>) -> Self {
self.attributes.push(Attribute::Normal(stringify!($attr).to_string(),value.into()));
self
}
}
};
($attr:ident, $($rest:ident),+) => {
impl_attrs!($attr);
impl_attrs!($($rest),+);
};
}
macro_rules! impl_bool_attrs {
($attr:ident) => {
impl Element {
pub fn $attr(mut self) -> Self {
self.attributes.push(Attribute::Boolean(stringify!($attr).to_string()));
self
}
}
};
($attr:ident, $($rest:ident),+) => {
impl_bool_attrs!($attr);
impl_bool_attrs!($($rest),+);
};
}
macro_rules! impl_alpine_attrs {
($attr:ident) => {
impl Element {
pub fn $attr(mut self, value: impl Into<String>) -> Self {
self.attributes.push(Attribute::Normal(format!("x-{}", stringify!($attr)),value.into()));
self
}
}
};
($attr:ident, $($rest:ident),+) => {
impl_alpine_attrs!($attr);
impl_alpine_attrs!($($rest),+);
};
}
impl Element {
pub fn render(&self, output: &mut String) {
if self.name == "html" {
output.push_str("<!DOCTYPE html>");
}
output.push('<');
output.push_str(&self.name);
for attribute in &self.attributes {
output.push(' ');
match attribute {
Attribute::Boolean(key) => output.push_str(key),
Attribute::Normal(key, value) => {
output.push_str(key);
output.push('=');
output.push('"');
output.push_str(value);
output.push('"');
}
}
}
if let Node::Void = self.child {
output.push_str("/>");
return;
}
output.push('>');
self.child.render(output);
output.push('<');
output.push('/');
output.push_str(&self.name);
output.push('>');
}
}
pub enum Node {
Void,
Text(String),
Element(Box<Element>),
Fragment(Vec<Node>),
}
impl Node {
fn render(&self, output: &mut String) {
match self {
Node::Void => (),
Node::Text(t) => output.push_str(t),
Node::Element(el) => el.render(output),
Node::Fragment(fragments) => {
for f in fragments {
f.render(output);
}
}
}
}
}
impl From<()> for Node {
fn from(_: ()) -> Self {
Self::Text(String::new())
}
}
impl From<String> for Node {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for Node {
fn from(value: &str) -> Self {
Self::Text(value.to_string())
}
}
impl From<Element> for Node {
fn from(value: Element) -> Self {
Self::Element(Box::new(value))
}
}
macro_rules! impl_from_tuple {
($($t:ident: $idx:tt),+) => {
impl<$($t: Into<Node>),+> From<($($t),+)> for Node {
fn from(value: ($($t),+)) -> Self {
let mut fragments = Vec::new();
$(
fragments.push(value.$idx.into());
)+
Node::Fragment(fragments)
}
}
};
}
macro_rules! impl_tags {
($tag:ident) => {
pub fn $tag(child: impl Into<Node>) -> Element {
Element {
name: stringify!($tag),
attributes: Vec::new(),
child: child.into(),
}
}
};
($tag:ident, $($rest:ident),+) => {
impl_tags!($tag);
impl_tags!($($rest),+);
};
}
macro_rules! impl_void_tags {
($tag:ident) => {
pub fn $tag() -> Element {
Element {
name: stringify!($tag),
attributes: Vec::new(),
child: Node::Void,
}
}
};
($tag:ident, $($rest:ident),+) => {
impl_void_tags!($tag);
impl_void_tags!($($rest),+);
};
}
impl_from_tuple!(T0: 0, T1: 1);
impl_from_tuple!(T0: 0, T1: 1, T2: 2);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8);
impl_from_tuple!(T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9);
impl_void_tags!(img, link, meta);
impl_tags!(a, body, div, h1, h2, h3, h4, h5, h6, head, html, p, span, script, template, title);
impl_attrs!(charset, class, content, href, name, rel, src);
impl_bool_attrs!(defer);
impl_alpine_attrs!(data, show, model, text, r#if, r#for);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment