use handlebars as hb;
use include_dir::{include_dir, Dir, DirEntry};
use serde::Serialize;
use std::collections::BTreeMap;
use std::fmt;
use std::fs;
use std::io;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
use crate::errors::*;
use crate::util::ConductorPathExt;
static DATA: Dir = include_dir!("data");
fn escape_double_quotes(data: &str) -> String {
data.replace(r#"\"#, r#"\\"#).replace(r#"""#, r#"\""#)
}
pub struct Template {
name: String,
files: BTreeMap<PathBuf, String>,
}
impl Template {
pub fn new(name: &str) -> Result<Template> {
let s = MAIN_SEPARATOR;
let name = name.replace('/', &s.to_string());
let prefix = format!("templates{}{}{}", s, &name, s);
let glob = format!("{}**{}*", prefix, s);
let sep_underscore = format!("{}_", s);
let mut files = BTreeMap::new();
for entry in DATA.find(&glob)? {
if let DirEntry::File(file) = entry {
trace!("checking template path {} in prefix {}", file.path, prefix);
assert!(file.path.starts_with(&prefix));
let rel: &str = &file.path[prefix.len()..];
if !rel.starts_with('_') && !rel.contains(&sep_underscore) {
let raw_data = file.contents().to_owned();
let data = String::from_utf8(raw_data)?;
files.insert(Path::new(rel).to_owned(), data);
}
}
}
Ok(Template { name, files })
}
pub fn generate<T>(
&mut self,
target_dir: &Path,
data: &T,
out: &mut dyn io::Write,
) -> Result<()>
where
T: Serialize + fmt::Debug,
{
debug!("Generating {} with {:?}", &self.name, data);
for (rel_path, tmpl) in &self.files {
let path = target_dir.join(rel_path);
debug!("Output {}", path.display());
writeln!(out, "Generating: {}", rel_path.display())?;
let mkerr = || ErrorKind::CouldNotWriteFile(path.clone());
path.with_guaranteed_parent()?;
let out = fs::File::create(&path).chain_err(&mkerr)?;
let mut writer = io::BufWriter::new(out);
let mut hb = hb::Handlebars::new();
hb.register_escape_fn(escape_double_quotes);
hb.render_template_to_write(tmpl, &data, &mut writer)
.chain_err(&mkerr)?;
}
Ok(())
}
}
#[test]
fn loads_correct_files_for_template() {
let tmpl = Template::new("test_tmpl").unwrap();
let keys: Vec<_> = tmpl.files.keys().cloned().collect();
assert!(keys.contains(&Path::new("test.txt").to_owned()));
assert!(keys.contains(&Path::new("nested").join("nested.txt")));
assert!(!keys.contains(&Path::new("_child_tmpl").join("child.txt")));
}