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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
extern crate handlebars;
extern crate rustc_serialize;
extern crate notify;
#[macro_use] extern crate log;
use std::sync::{Arc, RwLock};
use std::sync::mpsc::channel;
use std::{io, thread};
use std::fs::{self};
use std::path::Path;
use rustc_serialize::json::ToJson;
use notify::{RecommendedWatcher, Error, Watcher};
use handlebars::{RenderError, Handlebars};
pub struct TemplateEngine {
handlebars: RwLock<Box<Handlebars>>,
template_dir: String,
extension: String,
}
impl TemplateEngine {
pub fn new(template_dir: &str, extension: &str) -> Arc<Self> {
let engine = Arc::new(TemplateEngine {
handlebars: RwLock::new(Box::new(Handlebars::new())),
template_dir: String::from(template_dir),
extension: String::from(extension),
});
engine.reload();
engine.watch();
engine
}
pub fn render<T: ToJson + Sized>(&self, name: &str, data: &T) -> Result<String, RenderError> {
let handlebars = self.handlebars.read().unwrap();
handlebars.render(name, data)
}
fn reload(&self) {
let mut handlebars = self.handlebars.write().unwrap();
handlebars.clear_templates();
let files = self.collect_files();
debug!("reload templates start");
for file in files {
let name = file.trim_left_matches(&self.template_dir).trim_right_matches(&self.extension);
match handlebars.register_template_file(&name, &Path::new(&file)) {
Err(e) => {
panic!("error:{}",e);
},
Ok(_) => {
debug!("{}->{}", name, file);
}
}
}
debug!("reload templates end");
}
fn collect_files(&self) -> Vec<String> {
let mut vec: Vec<String> = Vec::new();
fn _collect(dir: &Path, vec: &mut Vec<String>) -> io::Result<()> {
if dir.exists() && try!(fs::metadata(dir)).is_dir() {
for entry in try!(fs::read_dir(dir)) {
let entry = try!(entry);
if try!(fs::metadata(entry.path())).is_dir() {
try!(_collect(&entry.path(), vec));
} else {
let path = entry.path();
if let Some(extension) = path.extension().map(|s| s.to_str().unwrap()) {
if extension == "hbs" {
vec.push(format!("{}", path.to_string_lossy()));
}
}
}
}
}
Ok(())
}
_collect(Path::new(&self.template_dir), &mut vec).unwrap();
vec
}
}
trait Watchable {
fn watch(&self);
}
impl Watchable for Arc<TemplateEngine> {
fn watch(&self) {
fn _watch(engine: &Arc<TemplateEngine>) -> Result<(), Error> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = try!(Watcher::new(tx));
let p = Path::new(&engine.template_dir);
try!(watcher.watch(p));
loop {
let _ = rx.recv();
info!("Template directory changed");
engine.reload();
}
}
let engine = self.clone();
thread::spawn(move || {
match _watch(&engine) {
Ok(_) => (),
Err(e) => {
warn!("Failed to watch directory: {:?}", e);
panic!("error watch:{}",e);
}
}
});
}
}