sato 0.1.10

some sort of s-expr based html templating system
Documentation
use std::io::Read;

#[derive(thiserror::Error, Debug)]
pub enum ParseExprError {
    #[error("expr is not an atom: {0:?}")]
    NotAnAtom(sexp::Atom),
    #[error("expr is not a list: {0:?}")]
    NotAList(Vec<sexp::Sexp>),
    #[error("@ attribute is not a list {0:?} {1:?}")]
    NotAnAttribute(sexp::Sexp, Vec<sexp::Sexp>),
    #[error("html attribute is missing an element {0:?}")]
    AttributeMissingElement(Vec<sexp::Sexp>),
}

#[derive(Debug, Clone)]
pub struct TemplateAttribute(pub TemplateExprNode, pub Vec<TemplateExprNode>);

#[derive(Debug, Clone)]
pub struct TemplateTag {
    pub tag: String,
    pub attrs: Vec<TemplateAttribute>,
    pub children: Vec<TemplateExprNode>,
}

#[derive(Debug, Clone)]
pub enum TemplateExprNode {
    Identifier(String),
    Integer(i64),
    Tag(TemplateTag)
}

impl TryFrom<String> for TemplateExprNode {
    type Error = TemplateError;
    
    fn try_from(other: String) -> Result<TemplateExprNode, Self::Error> {
        Ok(parse_expr(&sexp::parse(&other).map_err(|err| TemplateError::ParseError(err, other.into()))?)?)
    }
}

impl TemplateExprNode {
    pub fn as_identifier(&self) -> Option<&String> {
        match self {
            TemplateExprNode::Identifier(s) => Some(s),
            _ => None
        }
    }

    pub fn as_integer(&self) -> Option<i64> {
        match self {
            TemplateExprNode::Integer(i) => Some(*i),
            _ => None
        }
    }
}

pub fn get_children_by_tag(expr: &[TemplateExprNode], tag_name: &str) -> Vec<TemplateTag> {
    expr
        .iter()
        .map(|e| {
            if let TemplateExprNode::Tag(tag) = e {
                if tag.tag == tag_name {
                    std::iter::once(tag)
                        .chain(get_children_by_tag(&tag.children, tag_name).iter())
                        .cloned()
                        .collect()
                }
                else {
                    get_children_by_tag(&tag.children, tag_name)
                }
            }
            else {
                Vec::new()
            }
        })
        .flatten()
        .collect()
}

fn parse_attrs(attrs: &Vec<sexp::Sexp>) -> Result<Vec<TemplateAttribute>, ParseExprError> {
    attrs.iter().skip(1)
        .map(|attr| {
            match attr {
                sexp::Sexp::List(list) => {
                    let name = parse_expr(list
                        .get(0)
                        .ok_or_else(|| ParseExprError::AttributeMissingElement(attrs.clone()))?)?;
                    let value = list
                        .get(1..)
                        .ok_or_else(|| ParseExprError::AttributeMissingElement(attrs.clone()))?
                        .iter()
                        .map(|v| {
                            parse_expr(v)
                        })
                        .collect::<Result<Vec<_>, _>>()?;
                    Ok(TemplateAttribute(name, value))
                }
                _ => Err(ParseExprError::NotAnAttribute(attr.clone(), attrs.clone()))
            }
        })
        .collect::<Result<Vec<_>, ParseExprError>>()
}

fn parse_expr(expr: &sexp::Sexp) -> Result<TemplateExprNode, ParseExprError> {
    Ok(match expr {
        sexp::Sexp::Atom(atom) => {
            match atom {
                sexp::Atom::S(s) => TemplateExprNode::Identifier(s.to_string()),
                sexp::Atom::I(i) => TemplateExprNode::Integer(*i),
                //sexp::Atom::I(i) => TemplateExprNode::Integer(i),
                _ => return Err(ParseExprError::NotAnAtom(atom.clone()))
            }
        },
        sexp::Sexp::List(list) => {
            let tag = match list[0] {
                sexp::Sexp::Atom(sexp::Atom::S(ref s)) => s.clone(),
                _ => return Err(ParseExprError::NotAList(list.clone()))
            };
            let (attrs, attr_index) = match &list.get(1) {
                Some(sexp::Sexp::List(list)) if list.get(0) == Some(&sexp::Sexp::Atom(sexp::Atom::S("@".into()))) => (parse_attrs(&list)?, 2),
                _ => (Vec::new(), 1)
            };

            let children = list.iter().skip(attr_index).map(|l| {
                parse_expr(l)
            }).collect::<Result<Vec<_>, ParseExprError>>()?;

            TemplateExprNode::Tag(TemplateTag {
                tag,
                attrs,
                children,
            })
        }
    })
}

#[derive(thiserror::Error, Debug)]
pub enum TemplateError {
    #[error("could not find template file")]
    NoFile,
    #[error("invalid file")]
    InvalidFile,
    #[error("error parsing template")]
    ParseError(Box<sexp::Error>, String),
    #[error("error parsing template expression")]
    ParseExprError(#[from] ParseExprError),
}

#[derive(Clone, Debug)]
pub struct Template {
    pub expr: TemplateExprNode,
}

impl Template {
    pub fn from_str(template: &str) -> Result<Template, TemplateError> {
        Ok(Template {
            expr: parse_expr(&sexp::parse(template).map_err(|err| TemplateError::ParseError(err, template.into()))?)?
        })
    }

    pub fn from_path<P: AsRef<std::path::Path>>(template: P) -> Result<Template, TemplateError> {
        let mut f = std::fs::File::open(template).map_err(|_| TemplateError::NoFile)?;
        let mut s = String::new();
        f.read_to_string(&mut s).map_err(|_| TemplateError::InvalidFile)?;
        Template::from_str(&s)
    }
}