devist 0.4.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
use anyhow::{anyhow, Result};
use std::collections::HashMap;

/// Replace `{{ key }}` (with optional whitespace) with values from `vars`.
/// Unknown placeholders cause an error — fail-loud beats silent corruption.
pub fn render(template: &str, vars: &HashMap<String, String>) -> Result<String> {
    let mut out = String::with_capacity(template.len());
    let mut rest = template;

    while let Some(start) = rest.find("{{") {
        out.push_str(&rest[..start]);
        let after_open = &rest[start + 2..];

        let end = after_open
            .find("}}")
            .ok_or_else(|| anyhow!("Unclosed placeholder in template"))?;

        let key = after_open[..end].trim();
        let value = vars
            .get(key)
            .ok_or_else(|| anyhow!("Unknown template variable: {}", key))?;

        out.push_str(value);
        rest = &after_open[end + 2..];
    }

    out.push_str(rest);
    Ok(out)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn vars(pairs: &[(&str, &str)]) -> HashMap<String, String> {
        pairs
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect()
    }

    #[test]
    fn renders_simple() {
        let v = vars(&[("name", "alice")]);
        assert_eq!(render("hello {{ name }}", &v).unwrap(), "hello alice");
    }

    #[test]
    fn renders_no_whitespace() {
        let v = vars(&[("name", "bob")]);
        assert_eq!(render("hi {{name}}!", &v).unwrap(), "hi bob!");
    }

    #[test]
    fn fails_on_unknown_var() {
        let v = vars(&[]);
        assert!(render("{{ missing }}", &v).is_err());
    }
}