1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use rlua::prelude::*;
use rlua_serde;
use tera::{Tera, Value as JsonValue, Context as TeraContext};
use crate::error::Error;

struct LuaTera (Arc<Mutex<Tera>>);

fn get_tera_context_from_table(table: &HashMap<String, LuaValue>) -> Result<TeraContext, LuaError> {
    let mut context = TeraContext::new();

    for (key, value) in table.iter() {
        match value {
            LuaValue::Integer(num) => context.insert(key, num),
            LuaValue::Number(num) => context.insert(key, num),
            LuaValue::String(string) => context.insert(key, string.to_str()?),
            LuaValue::Boolean(boolean) => context.insert(key, boolean),
            value @ LuaValue::Table(_) => {
                let value: JsonValue = rlua_serde::from_value(value.clone())
                    .map_err(|err| LuaError::external(err))?;
                context.insert(key, &value);
            },
            LuaValue::Nil => context.insert(key, &()),
            value @ _ => unimplemented!("Value {:?} is not implemented as a template parameter", value),
        }
    }

    Ok(context)
}

impl LuaUserData for LuaTera {
    fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {

        methods.add_method("extend", |_, this, dir: String| {
            let mut tera = this.0.try_lock().unwrap();
            let new_tera = Tera::parse(&dir).map_err(|err| {
                LuaError::external(format_err!("{}", err.to_string()))
            })?;
            tera.extend(&new_tera).map_err(|err| {
                LuaError::external(format_err!("{}", err.to_string()))
            })
        });

        methods.add_method("reload", |_, this, _: ()| {
            let mut tera = this.0.try_lock().unwrap();
            tera.full_reload().map_err(|err| {
                LuaError::external(format_err!("{}", err.to_string()))
            })
        });

        methods.add_method("add_raw_templates", |_, this, hashmap: HashMap<String, String>| {
            let mut templates = Vec::new();
            for (key, val) in &hashmap {
                templates.push( (key.as_str(), val.as_str()) );
            }
            let mut tera = this.0.try_lock().unwrap();
            tera.add_raw_templates(templates).map_err(|err| {
                LuaError::external(format_err!("{}", err.to_string()))
            })
        });

        methods.add_method("render", |_, this, (path, params): (String, Option<HashMap<String, LuaValue>>)| {
            let tera = this.0.try_lock().unwrap();
            let text = match params {
                Some(params) => {
                    let context = get_tera_context_from_table(&params)?;
                    tera.render(&path, &context)
                },
                None => {
                    tera.render(&path, &())
                },
            }.map_err(|err| {
                // can't convert error_chain to failure directly
                LuaError::external(format_err!("{}", err.to_string()))
            })?;

            Ok(text)
        });
    }
}

pub fn init(lua: &Lua) -> crate::Result<()> {

    let new_tera = lua.create_function(move |_, dir: String| {
        let tera = Tera::new(&dir).unwrap();
        let arc_mutex = Arc::new(Mutex::new(tera));
        Ok(LuaTera(arc_mutex))
    })?;

    let module = lua.create_table()?;
    module.set("new", new_tera)?;
    lua.globals().set("tera", module).map_err(Error::from)?;

    Ok(())
}