femstache 0.1.4

Embrace the feminine mustache to unlock your templating dreams! Or something, I don't know. :3
Documentation
use std::{collections::BTreeMap, path::PathBuf, sync::OnceLock};

use crate::{
    env::TemplateEnv,
    error::EvalResult,
    input::TemplateInput,
    loader::{TemplateLoader, TemplateSource},
    output::{EscapeMode, Output},
    renderer::TemplateRenderer,
    template::Template,
    value::Value,
};

const TEMPLATES: &[(&str, &str)] = &[("meow.fem.html", "{{meow}} :3")];

static TEMPLATE_LOADER: OnceLock<TemplateLoader> = OnceLock::new();

fn get_loader() -> TemplateLoader {
    TEMPLATE_LOADER
        .get_or_init(|| {
            let mut templates = BTreeMap::new();
            for &(name, source) in TEMPLATES {
                templates.insert(PathBuf::from(name), source.to_owned());
            }
            TemplateLoader::new(TemplateSource::map(templates))
        })
        .clone()
}

fn render(input: &impl TemplateInput, template: &Template) -> EvalResult<String> {
    let loader = get_loader();
    let env = TemplateEnv::new(input);
    let blocks = BTreeMap::new();
    let mut renderer = TemplateRenderer::new(env, blocks, Some(loader));
    let mut output = Output::string(EscapeMode::None);
    renderer.render_document(&template.doc, &mut output)?;
    Ok(output.into_inner())
}

macro_rules! template_test {
    ($name:ident, $input:expr, $tpl:literal, $expected:literal) => {
        #[test]
        fn $name() {
            let tpl = Template::parse($tpl).unwrap();
            let output = render(&$input, &tpl).unwrap();
            assert_eq!(output, $expected);
        }
    };
}

struct Input;
impl TemplateInput for Input {
    fn get(&self, key: &str) -> Option<Value> {
        if key == "meow" {
            Some(Value::String("gay".to_owned()))
        } else if key == "meows" {
            Some(Value::List(vec![
                Value::String("meow".to_owned()),
                Value::String("nyan".to_owned()),
                Value::String("miauw".to_owned()),
                Value::String("nyaa".to_owned()),
            ]))
        } else {
            None
        }
    }
}

fn make_input() -> Input {
    Input
}

template_test!(empty, make_input(), "", "");

template_test!(literal_meow, make_input(), "meow", "meow");

template_test!(stache_meow, make_input(), "{{meow}}", "gay");

template_test!(
    if_statement_var,
    make_input(),
    "{% if meow %}woa{% end %}{% if meows %} x3{% end %}",
    "woa x3"
);

template_test!(
    if_statement_true,
    make_input(),
    "{% if true %}woa{% end %}",
    "woa"
);

template_test!(
    if_statement_false,
    make_input(),
    "{% if false %}woa{% end %}",
    ""
);

template_test!(
    if_statement_null,
    make_input(),
    "{% if null %}woa{% end %}",
    ""
);

template_test!(
    if_statement_string_empty,
    make_input(),
    r#"{% if "" %}woa{% end %}"#,
    ""
);

template_test!(
    if_statement_string_flirty,
    make_input(),
    r#"{% if "hi ur cute ;3" %}woa{% end %}"#,
    "woa"
);

template_test!(
    if_statement_list_empty,
    make_input(),
    r#"{% if [] %}woa{% end %}"#,
    ""
);

template_test!(
    if_statement_list_aggressive,
    make_input(),
    r#"{% if ["woa gowai >:("] %}woa{% end %}"#,
    "woa"
);

template_test!(
    for_statement_var_1,
    make_input(),
    r#"{% for item in meows %}woa{% end %}"#,
    "woawoawoawoa"
);

template_test!(
    for_statement_var_2,
    make_input(),
    r#"{% for item in meows %}woa{{item}}{% end %}"#,
    "woameowwoanyanwoamiauwwoanyaa"
);

template_test!(
    for_statement_literal,
    make_input(),
    r#"hi{% for item in ["uwu", "owo", "awa"] %} {{item}}{% end %}"#,
    "hi uwu owo awa"
);

template_test!(
    let_1,
    make_input(),
    r#"{% let x3 = "x3" %}*nuzzles u* {{ x3 }}"#,
    "*nuzzles u* x3"
);

template_test!(
    let_2,
    make_input(),
    r#"{% let nyan = meow %}*nuzzles u* {{ nyan }}"#,
    "*nuzzles u* gay"
);

template_test!(
    stuff,
    make_input(),
    r#"hewo! {% let nyan = meow %}{% let nya = null %}{% if nyan %}you are {{meow}}{% end %}{% if nya %}oh noes!{% end %} {% for meow in meows %}{{meow}} {% end %}{{meow}}{% if false %}nope{% end %}{% if null %}nope{% end %}{% if true %} yuh{% end %}"#,
    "hewo! you are gay meow nyan miauw nyaa gay yuh"
);

template_test!(
    include,
    make_input(),
    r#"{% include "meow.fem.html" %}"#,
    "gay :3"
);