use crate::context::*;
use crate::default_functions::{val_length, val_range, val_stringify};
use crate::exec::*;
use crate::expr::*;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::sync::Mutex;
pub struct Dojang {
templates: HashMap<String, (Executer, String)>,
functions: HashMap<String, FunctionContainer>,
includes: Mutex<HashMap<String, String>>,
}
impl Dojang {
pub fn new() -> Self {
let mut functions = HashMap::<String, FunctionContainer>::new();
functions.insert(
"length".to_string(),
FunctionContainer::F1(Box::new(val_length)),
);
functions.insert(
"range".to_string(),
FunctionContainer::F1(Box::new(val_range)),
);
functions.insert(
"json_stringify".to_string(),
FunctionContainer::F1(Box::new(val_stringify)),
);
Dojang {
templates: HashMap::new(),
functions,
includes: Mutex::new(HashMap::new()),
}
}
pub fn add(&mut self, file_name: String, template: String) -> Result<&Self, String> {
if self.templates.contains_key(&file_name) {
return Err(format!("{} is already added as a template", file_name));
}
self.templates.insert(
file_name,
(Executer::new(Parser::parse(&template)?)?, template),
);
Ok(self)
}
pub fn add_function_1<T: 'static, V: 'static>(
&mut self,
function_name: String,
function: fn(T) -> V,
) -> Result<&Self, String>
where
T: From<Operand>,
V: Into<Operand>,
{
if self.functions.contains_key(&function_name) {
return Err(format!("{} is already added as a function", function_name));
}
self.functions
.insert(function_name, to_function_container1(function));
Ok(self)
}
pub fn add_function_2<T1: 'static, T2: 'static, V: 'static>(
&mut self,
function_name: String,
function: fn(T1, T2) -> V,
) -> Result<&Self, String>
where
T1: From<Operand>,
T2: From<Operand>,
V: Into<Operand>,
{
if self.functions.contains_key(&function_name) {
return Err(format!("{} is already added as a function", function_name));
}
self.functions
.insert(function_name, to_function_container2(function));
Ok(self)
}
pub fn add_function_3<T1: 'static, T2: 'static, T3: 'static, V: 'static>(
&mut self,
function_name: String,
function: fn(T1, T2, T3) -> V,
) -> Result<&Self, String>
where
T1: From<Operand>,
T2: From<Operand>,
T3: From<Operand>,
V: Into<Operand>,
{
if self.functions.contains_key(&function_name) {
return Err(format!("{} is already added as a function", function_name));
}
self.functions
.insert(function_name, to_function_container3(function));
Ok(self)
}
pub fn add_function_4<T1: 'static, T2: 'static, T3: 'static, T4: 'static, V: 'static>(
&mut self,
function_name: String,
function: fn(T1, T2, T3, T4) -> V,
) -> Result<&Self, String>
where
T1: From<Operand>,
T2: From<Operand>,
T3: From<Operand>,
T4: From<Operand>,
V: Into<Operand>,
{
if self.functions.contains_key(&function_name) {
return Err(format!("{} is already added as a function", function_name));
}
self.functions
.insert(function_name, to_function_container4(function));
Ok(self)
}
pub fn load(&mut self, dir_name: &str) -> Result<&mut Self, String> {
match get_all_file_path_under_dir(dir_name) {
Ok(files) => {
for file in files {
let file_name = file
.file_name()
.unwrap()
.to_os_string()
.into_string()
.unwrap();
if self.templates.contains_key(&file_name) {
println!("Template {} is already added", file_name);
continue;
}
let file_content = std::fs::read_to_string(&file);
if file_content.is_err() {
println!(
"Unable to read file '{:?}', error : {:?}",
file, file_content
);
continue;
}
let file_content = file_content.unwrap();
self.templates.insert(
file_name,
(Executer::new(Parser::parse(&file_content)?)?, file_content),
);
}
}
Err(e) => return Err(e.to_string()),
}
Ok(self)
}
pub fn render(&mut self, file_name: &str, value: Value) -> Result<String, String> {
if let Some((executer, file_content)) = self.templates.get(&file_name.to_string()) {
executer.render(
&mut Context::new(value),
&self.templates,
&self.functions,
file_content,
&mut self.includes,
)
} else {
Err(format!("Template {} is not found", file_name))
}
}
}
fn get_all_file_path_under_dir(dir_name: &str) -> io::Result<Vec<PathBuf>> {
fs::read_dir(dir_name)?
.into_iter()
.map(|x| x.map(|entry| entry.path()))
.collect()
}
pub fn to_function_container1<T: 'static + From<Operand>, V: 'static + Into<Operand>>(
func: fn(T) -> V,
) -> FunctionContainer {
FunctionContainer::F1(Box::new(move |v: Operand| -> Operand {
func(v.into()).into()
}))
}
pub fn to_function_container2<
'a,
T1: 'static + From<Operand>,
T2: 'static + From<Operand>,
V: 'static + Into<Operand>,
>(
func: fn(T1, T2) -> V,
) -> FunctionContainer {
FunctionContainer::F2(Box::new(move |v1: Operand, v2: Operand| -> Operand {
func(v1.into(), v2.into()).into()
}))
}
pub fn to_function_container3<
'a,
T1: 'static + From<Operand>,
T2: 'static + From<Operand>,
T3: 'static + From<Operand>,
V: 'static + Into<Operand>,
>(
func: fn(T1, T2, T3) -> V,
) -> FunctionContainer {
FunctionContainer::F3(Box::new(
move |v1: Operand, v2: Operand, v3: Operand| -> Operand {
func(v1.into(), v2.into(), v3.into()).into()
},
))
}
pub fn to_function_container4<
'a,
T1: 'static + From<Operand>,
T2: 'static + From<Operand>,
T3: 'static + From<Operand>,
T4: 'static + From<Operand>,
V: 'static + Into<Operand>,
>(
func: fn(T1, T2, T3, T4) -> V,
) -> FunctionContainer {
FunctionContainer::F4(Box::new(
move |v1: Operand, v2: Operand, v3: Operand, v4: Operand| -> Operand {
func(v1.into(), v2.into(), v3.into(), v4.into()).into()
},
))
}
#[test]
fn render() {
let template = "<% if a == 1 { %> Hi <% } else { %><%= a %><% } %>".to_string();
let mut dojang = Dojang::new();
assert!(dojang.add("some_template".to_string(), template).is_ok());
assert_eq!(
dojang
.render(
"some_template",
serde_json::from_str(r#"{ "a" : 1 }"#).unwrap()
)
.unwrap(),
" Hi "
);
assert_eq!(
dojang
.render(
"some_template",
serde_json::from_str(r#"{ "a" : 2 }"#).unwrap()
)
.unwrap(),
"2"
);
}
#[test]
fn render_from_dir() {
let mut dojang = Dojang::new();
assert!(dojang.load("./tests").is_ok());
println!(
"{}",
dojang
.render(
"test.html",
serde_json::from_str(r#"{ "test" : { "title" : "Welcome to Dojang"} }"#).unwrap()
)
.unwrap()
);
assert_eq!(
dojang
.render(
"test.html",
serde_json::from_str(r#"{ "test" : { "title" : "Welcome to Dojang"} }"#).unwrap()
)
.unwrap(),
r#"<html>
Welcome to Dojang
<body>
<p>some content</p><p>x is0</p><p>x is1</p><p>x is2</p><p>x is3</p><p>x is4</p><p>x is5</p><p>x is6</p><p>x is7</p><p>x is8</p><p>x is9</p>
</body>
</html>
"#
);
}
#[cfg(test)]
fn func(a: i64, b: i64) -> i64 {
a + b
}
#[test]
fn render_function() {
let template = r#"func(a,b) = <%= func(a, b) %>"#.to_string();
let mut dojang = Dojang::new();
assert!(dojang.add("some_template".to_string(), template).is_ok());
assert!(dojang.add_function_2("func".to_string(), func).is_ok());
assert_eq!(
dojang
.render("some_template", serde_json::json!({"a" : 1, "b" : 2 }))
.unwrap(),
"func(a,b) = 3"
);
}
#[test]
fn for_use_range_function() {
let template = r#"<% for i in range(v) { %><%= i %><% } %>"#.to_string();
let mut dojang = Dojang::new();
assert!(dojang.add("some_template".to_string(), template).is_ok());
assert_eq!(
dojang
.render("some_template", serde_json::json!({"v" : 10}))
.unwrap(),
"0123456789"
);
}
#[test]
fn use_length_function() {
let template = r#"<%= length(s) %>"#.to_string();
let mut dojang = Dojang::new();
assert!(dojang.add("some_template".to_string(), template).is_ok());
assert_eq!(
dojang
.render("some_template", serde_json::json!({"s" : "abc"}))
.unwrap(),
"3"
);
}
#[test]
fn use_json_stringify_function() {
let template = r#"<%- json_stringify(s) %>"#.to_string();
let mut dojang = Dojang::new();
assert!(dojang.add("some_template".to_string(), template).is_ok());
assert_eq!(
dojang
.render("some_template", serde_json::json!({"s" : [1,2,3]}))
.unwrap(),
"[1,2,3]"
);
}