use crate::model::TemplateManifest;
use include_dir::{include_dir, Dir};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
static TMPL_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
#[derive(Debug, Clone)]
pub struct Template {
pub manifest: TemplateManifest,
pub files: Vec<(String, String)>,
}
fn collect_dir_files(dir: &Dir, out: &mut Vec<(String, String)>) {
for f in dir.files() {
let p = f.path().to_string_lossy().to_string();
let name = f.path().file_name().unwrap().to_string_lossy().to_string();
if name == "template.toml" {
continue;
}
if let Some(s) = f.contents_utf8() {
out.push((p, s.to_string()));
}
}
for d in dir.dirs() {
collect_dir_files(d, out);
}
}
pub fn list_templates() -> Vec<Template> {
let mut out = Vec::new();
for sub in TMPL_DIR.dirs() {
let dirname = sub.path().file_name().unwrap().to_string_lossy().to_string();
let manifest_path = format!("{}/template.toml", dirname);
let mf = match TMPL_DIR.get_file(&manifest_path) {
Some(f) => f,
None => continue,
};
let manifest: TemplateManifest = match toml::from_str(mf.contents_utf8().unwrap()) {
Ok(m) => m,
Err(_) => continue,
};
let mut raw = Vec::new();
collect_dir_files(sub, &mut raw);
let prefix = format!("{}/", dirname);
let mut files = Vec::new();
for (p, c) in raw {
let rel = p.strip_prefix(&prefix).unwrap_or(&p).to_string();
let out_name = rel.strip_suffix(".tmpl").unwrap_or(&rel).to_string();
if out_name == "template.toml" {
continue;
}
files.push((out_name, c));
}
out.push(Template { manifest, files });
}
out.sort_by(|a, b| a.manifest.slug.cmp(&b.manifest.slug));
out
}
pub fn find_template(slug: &str) -> Option<Template> {
list_templates().into_iter().find(|t| t.manifest.slug == slug)
}
pub fn render_template(t: &Template, vars: &BTreeMap<String, String>) -> Vec<(String, String)> {
t.files
.iter()
.map(|(path, content)| {
let mut p = path.clone();
let mut c = content.clone();
for (k, v) in vars {
let token = format!("{{{{{}}}}}", k);
p = p.replace(&token, v);
c = c.replace(&token, v);
}
(p, c)
})
.collect()
}
pub fn scaffold_template(t: &Template, vars: &BTreeMap<String, String>, out: &Path) -> anyhow::Result<Vec<String>> {
let rendered = render_template(t, vars);
let mut written = Vec::new();
for (rel, content) in rendered {
let dest = out.join(&rel);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&dest, content)?;
written.push(rel);
}
Ok(written)
}