atom-engine 5.0.2

A component-oriented template engine built on Tera with props, slots, and provide/inject context
Documentation
use serde_json::{json, Value};
use std::collections::HashMap;
use tera::Function;

use super::FilterResult;

pub fn slot_filter(name: &Value, _: &HashMap<String, Value>) -> FilterResult {
    let slot_name = name.as_str().unwrap_or("default").trim_start_matches('$');
    let slot_key = format!("__slot_{}", slot_name);
    if let Some(slot_content) = name.get(&slot_key) {
        return Ok(slot_content.clone());
    }
    Ok(Value::String(String::new()))
}

pub fn has_slot_filter(name: &Value, _: &HashMap<String, Value>) -> FilterResult {
    let slot_name = name.as_str().unwrap_or("default").trim_start_matches('$');
    let slot_key = format!("__slot_{}", slot_name);
    Ok(Value::Bool(name.get(&slot_key).is_some()))
}

pub fn stack_filter(name: &Value, _: &HashMap<String, Value>) -> FilterResult {
    let stack_name = name.as_str().unwrap_or("");
    let stack_key = format!("__stack_{}", stack_name);
    Ok(name
        .get(&stack_key)
        .cloned()
        .unwrap_or(Value::String(String::new())))
}

pub fn scoped_slot_filter(args: &Value, _: &HashMap<String, Value>) -> FilterResult {
    let slot_name = args
        .get("slot")
        .and_then(|v| v.as_str())
        .unwrap_or("default");
    let data_key = args.get("key").and_then(|v| v.as_str()).unwrap_or("item");
    let default_value = args.get("default");

    let _slot_key = format!("__scoped_slot_{}", slot_name);
    let data_key_full = format!("__scoped_data_{}_{}", slot_name, data_key);

    if let Some(data) = args.get(&data_key_full) {
        return Ok(data.clone());
    }

    Ok(default_value.cloned().unwrap_or(Value::Null))
}

pub fn with_scoped_data_filter(args: &Value, _: &HashMap<String, Value>) -> FilterResult {
    let slot_name = args
        .get("slot")
        .and_then(|v| v.as_str())
        .unwrap_or("default");
    let data = args.get("data").cloned().unwrap_or(Value::Null);

    let mut result = json!({
        "slot": slot_name,
        "data": data,
    });

    if let Some(obj) = result.as_object_mut() {
        if let Some(data_obj) = data.as_object() {
            for (key, value) in data_obj {
                obj.insert(
                    format!("__scoped_data_{}_{}", slot_name, key),
                    value.clone(),
                );
            }
        }
    }

    Ok(result)
}

pub struct PushFn;

impl Function for PushFn {
    fn call(&self, args: &HashMap<String, Value>) -> FilterResult {
        let stack_name = args
            .get("name")
            .and_then(|v| v.as_str())
            .unwrap_or("default");
        let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
        let stack_key = format!("__stack_{}", stack_name);
        Ok(Value::String(format!("{}__push__ {}", stack_key, content)))
    }
}

pub struct PrependFn;

impl Function for PrependFn {
    fn call(&self, args: &HashMap<String, Value>) -> FilterResult {
        let stack_name = args
            .get("name")
            .and_then(|v| v.as_str())
            .unwrap_or("default");
        let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
        let stack_key = format!("__stack_{}", stack_name);
        Ok(Value::String(format!(
            "{}__prepend__ {}",
            stack_key, content
        )))
    }
}

pub struct SetSlotFn;

impl Function for SetSlotFn {
    fn call(&self, args: &HashMap<String, Value>) -> FilterResult {
        let slot_name = args
            .get("name")
            .and_then(|v| v.as_str())
            .unwrap_or("default")
            .trim_start_matches('$');
        let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
        let slot_key = format!("__slot_{}", slot_name);
        Ok(Value::String(format!("{} {}", slot_key, content)))
    }
}

pub struct OnceFn;

impl Function for OnceFn {
    fn call(&self, args: &HashMap<String, Value>) -> FilterResult {
        let key = args
            .get("key")
            .and_then(|v| v.as_str())
            .unwrap_or("default");
        let hash = simple_hash(key);
        let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
        Ok(json!({ "key": hash, "content": content }))
    }
}

fn simple_hash(s: &str) -> u64 {
    let mut hash: u64 = 0;
    for (i, c) in s.bytes().enumerate() {
        hash = hash.wrapping_add((c as u64).wrapping_mul(31_u64.wrapping_pow(i as u32)));
    }
    hash
}