planner / src/templates.rs

raw file 3003 bytes
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)
}