libhanzzok 0.1.5

Hanzzok compiler library
Documentation
use std::{
    collections::HashMap,
    io::{self, Write},
    rc::Rc,
};

use super::Context;

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct HtmlNodeRef {
    id: usize,
}

impl HtmlNodeRef {
    pub fn load_from<'a>(&self, context: &'a Context) -> &'a HtmlNode {
        context.load_html_node(self.id).unwrap_or(&HtmlNode::Empty)
    }
}

#[derive(Clone)]
pub enum HtmlNode {
    Tag(HtmlTagNode),
    Text(String),
    Lazy(Rc<Box<dyn Fn(&Context) -> HtmlNode>>),
    Collection(Vec<HtmlNode>),
    Empty,
}

impl HtmlNode {
    pub fn create_tag_builder(tag: impl AsRef<str>) -> HtmlTagNodeBuilder {
        HtmlTagNodeBuilder {
            tag: tag.as_ref().to_owned(),
            attrs: HashMap::new(),
            children: Vec::new(),
        }
    }
    pub fn create_tag(tag: impl AsRef<str>, children: &[HtmlNode]) -> HtmlNode {
        HtmlNode::Tag(HtmlTagNode {
            tag: tag.as_ref().to_owned(),
            attrs: HashMap::new(),
            children: children.to_vec(),
        })
    }

    pub fn create_text(data: impl AsRef<str>) -> HtmlNode {
        HtmlNode::Text(data.as_ref().to_owned())
    }

    pub fn create_lazy(f: impl Fn(&Context) -> HtmlNode + 'static) -> HtmlNode {
        HtmlNode::Lazy(Rc::new(Box::new(f)))
    }

    pub fn into_ref(self, context: &mut Context) -> HtmlNodeRef {
        let id = context.save_html_node(self);
        HtmlNodeRef { id }
    }

    pub fn into_plain_text(self, context: &Context) -> String {
        match self {
            HtmlNode::Text(text) => text,
            HtmlNode::Tag(HtmlTagNode { children, .. }) => children
                .into_iter()
                .map(|node| node.into_plain_text(context))
                .collect(),
            HtmlNode::Collection(vec) => vec
                .into_iter()
                .map(|node| node.into_plain_text(context))
                .collect(),
            HtmlNode::Lazy(f) => f(context).into_plain_text(context),
            HtmlNode::Empty => "".to_owned(),
        }
    }

    pub fn evaluate_early(self, context: &Context) -> HtmlNode {
        match self {
            HtmlNode::Lazy(f) => f(context),
            otherwise => otherwise,
        }
    }

    pub fn write(&self, context: &Context, w: &mut impl Write) -> io::Result<()> {
        match self {
            HtmlNode::Tag(node) => node.write(context, w),
            HtmlNode::Text(data) => write!(w, "{}", data),
            HtmlNode::Collection(vec) => {
                for node in vec {
                    node.write(context, w)?;
                }
                Ok(())
            }
            HtmlNode::Lazy(f) => f(context).write(context, w),
            HtmlNode::Empty => Ok(()),
        }
    }
}

pub struct HtmlTagNodeBuilder {
    pub(crate) tag: String,
    pub(crate) attrs: HashMap<String, String>,
    pub(crate) children: Vec<HtmlNode>,
}

impl HtmlTagNodeBuilder {
    pub fn append(&mut self, child: HtmlNode) -> &mut Self {
        self.children.push(child);
        self
    }
    pub fn append_all(&mut self, children: impl IntoIterator<Item = HtmlNode>) -> &mut Self {
        self.children.extend(children);
        self
    }
    pub fn set_attr(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
        self.attrs
            .insert(key.as_ref().to_owned(), value.as_ref().to_owned());
        self
    }
    pub fn append_attr(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
        let entry = self
            .attrs
            .entry(key.as_ref().to_owned())
            .or_insert("".to_owned());
        *entry = format!("{}{}", entry, value.as_ref());
        self
    }

    pub fn build(&mut self) -> HtmlNode {
        HtmlNode::Tag(HtmlTagNode {
            tag: self.tag.clone(),
            attrs: self.attrs.clone(),
            children: self.children.clone(),
        })
    }
}

#[derive(Clone)]
pub struct HtmlTagNode {
    tag: String,
    attrs: HashMap<String, String>,
    children: Vec<HtmlNode>,
}

impl HtmlTagNode {
    pub fn write(&self, context: &Context, w: &mut impl Write) -> io::Result<()> {
        write!(w, "<{}", &self.tag)?;

        let mut attrs: Vec<_> = self.attrs.iter().collect();
        attrs.sort();

        for (key, value) in attrs {
            if value.is_empty() {
                write!(w, " {}", key)?;
            } else {
                write!(w, " {}=\"{}\"", key, value)?;
            }
        }
        write!(w, ">")?;
        for node in &self.children {
            node.write(context, w)?;
        }
        write!(w, "</{}>", &self.tag)
    }
}