gen-html-proc 0.4.0

Generate HTML using Rust
Documentation
use std::collections::HashMap;

use crate::error::Error;
use proc_macro2::Span;
use syn::{Expr, Ident, Pat, spanned::Spanned};

pub struct Template {
    pub nodes: Vec<Node>,
}

pub enum Node {
    Element(Element),
    Block(Block),
    Expr(Expr),
    If(If),
    Match(Match),
    ForLoop(ForLoop),
    Let(Let),
}

impl Node {
    pub fn ends_with_eager_brace(&self) -> bool {
        matches!(
            self,
            Node::Block(_) | Node::Element(_) | Node::ForLoop(_) | Node::If(_) | Node::Match(_)
        )
    }
}

pub struct Element {
    pub name: Ident,
    pub attr_list: Vec<Attribute>,
    pub body: Option<Block>,
}

impl Element {
    pub fn validate(&self) -> Result<(), Error> {
        match crate::tags::is_self_closing(&self.name.to_string()) {
            Some(true) if self.body.is_some() => Err(Error::SelfClosing(self.name.clone())),
            Some(false) if self.body.is_none() => Err(Error::NotSelfClosing(self.name.clone())),
            Some(_) => Ok(()),
            None => Err(Error::InvalidTag(self.name.clone())),
        }
    }

    pub fn attributes(&self) -> Result<Vec<(String, Option<Expr>)>, Error> {
        let mut error = Error::empty();

        let mut key_to_spans = HashMap::new();
        for (span, key) in self.attr_list.iter().map(Attribute::key) {
            key_to_spans
                .entry(key.clone())
                .or_insert(Vec::new())
                .push(span);
        }

        for (key, spans) in key_to_spans
            .into_iter()
            .filter(|(_, spans)| spans.len() > 1)
        {
            error.push(Error::AttributeSpecifiedMoreThenOnce { spans, key });
        }

        if !error.is_empty() {
            return Err(error);
        }

        Ok(self
            .attr_list
            .iter()
            .map(|attr| (attr.key().1, attr.value().cloned()))
            .collect())
    }
}

pub enum Attribute {
    Id(Expr),
    Class(Expr),
    KeyValue { key: Ident, value: Option<Expr> },
}

impl Attribute {
    pub fn key(&self) -> (Span, String) {
        match self {
            Self::Id(id) => (id.span(), "id".to_owned()),
            Self::Class(class) => (class.span(), "class".to_owned()),
            Self::KeyValue { key, .. } => (
                key.span(),
                key.to_string().trim_start_matches("r#").replace("_", "-"),
            ),
        }
    }

    pub fn value(&self) -> Option<&Expr> {
        match self {
            Self::Id(id) => Some(id),
            Self::Class(class) => Some(class),
            Self::KeyValue { value, .. } => value.as_ref(),
        }
    }
}

pub struct Block {
    pub nodes: Vec<Node>,
}

pub struct If {
    pub cond: Expr,
    pub then_branch: Block,
    pub else_branch: Option<Box<Node>>,
}

pub struct Match {
    pub expr: Expr,
    pub arms: Vec<Arm>,
}

pub struct Arm {
    pub pat: Pat,
    pub guard: Option<Expr>,
    pub body: Node,
}

pub struct ForLoop {
    pub pat: Pat,
    pub expr: Expr,
    pub body: Block,
}

pub struct Let {
    pub pat: Pat,
    pub expr: Expr,
}