xrml 0.1.0

eXtensible Rust Markup Language — recursive acronym: HRML (HRML Markup Language) and TRML (TOML-like Markup Language)
Documentation
use xrml::config::Config;
use xrml::project::Project;
use xrml::template::Engine;
use serde_json::{json, Value};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

pub(crate) struct TestEnv {
    dir: PathBuf,
}

impl TestEnv {
    pub(crate) fn new(name: &str) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let dir = std::env::temp_dir().join(format!("hrml_unit_{}_{}", name, now));
        let _ = fs::create_dir_all(&dir);
        Self { dir }
    }

    pub(crate) fn write(&self, path: &str, content: &str) {
        let full = self.dir.join(path);
        if let Some(parent) = Path::new(&full).parent() {
            let _ = fs::create_dir_all(parent);
        }
        fs::write(&full, content).unwrap();
    }

    pub(crate) fn engine(&self) -> Engine {
        Engine::new(self.dir.to_str().unwrap())
    }

    pub(crate) fn dir(&self) -> &Path {
        &self.dir
    }

    pub(crate) fn render(&self, path: &str) -> Result<String, String> {
        self.engine()
            .render(path, &json!({}))
            .map_err(|e| e.to_string())
    }

    pub(crate) fn render_fragment(&self, path: &str) -> Result<String, String> {
        self.engine()
            .render_fragment(path, &json!({}))
            .map_err(|e| e.to_string())
    }

    pub(crate) fn render_with_data(&self, path: &str, data: &Value) -> Result<String, String> {
        self.engine().render(path, data).map_err(|e| e.to_string())
    }

    pub(crate) fn render_project(&self, files: &[&str], target: &str) -> Result<String, String> {
        let config = Config::default();
        let mut project = Project::new(config).with_base_path(&self.dir);
        for &path in files {
            let source = fs::read_to_string(self.dir.join(path))
                .map_err(|e| format!("no such test file {path}: {e}"))?;
            project.add_file(path.to_string(), source);
        }
        project.parse_all().map_err(|e| e.to_string())?;
        project
            .render(target, &json!({}))
            .map_err(|e| e.to_string())
    }

    pub(crate) fn assert_trml_equivalent(&self, name: &str, hrml: &str, trml: &str) {
        let hrml_path = format!("{}.hrml", name);
        let trml_path = format!("{}.trml", name);
        self.write(&hrml_path, hrml);
        self.write(&trml_path, trml);
        let hrml_out = self.render(&hrml_path).unwrap();
        let trml_out = self.render(&trml_path).unwrap();
        assert_eq!(
            hrml_out, trml_out,
            "TRML ≠ HRML for {name}\n=== HRML ===\n{hrml_out}\n=== TRML ===\n{trml_out}"
        );
    }

}

impl Drop for TestEnv {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.dir);
    }
}