gestura_core_hooks/
template.rs

1//! Tiny, dependency-free template rendering for hooks.
2//!
3//! The template syntax is `{{key}}`. Keys are trimmed and matched exactly.
4//! Unknown keys are replaced with an empty string.
5
6use std::collections::HashMap;
7
8/// Template variables map used for hook command rendering.
9pub type TemplateVars = HashMap<String, String>;
10
11/// Render `template` by replacing `{{key}}` placeholders with values in `vars`.
12///
13/// This is intentionally simple and avoids complex templating logic.
14pub fn render_template(template: &str, vars: &TemplateVars) -> String {
15    let mut out = String::with_capacity(template.len());
16    let bytes = template.as_bytes();
17    let mut i = 0;
18
19    while i < bytes.len() {
20        if i + 1 < bytes.len() && bytes[i] == b'{' && bytes[i + 1] == b'{' {
21            // Find closing braces.
22            let mut j = i + 2;
23            while j + 1 < bytes.len() {
24                if bytes[j] == b'}' && bytes[j + 1] == b'}' {
25                    break;
26                }
27                j += 1;
28            }
29
30            if j + 1 < bytes.len() && bytes[j] == b'}' && bytes[j + 1] == b'}' {
31                let key = template[i + 2..j].trim();
32                if let Some(v) = vars.get(key) {
33                    out.push_str(v);
34                }
35                i = j + 2;
36                continue;
37            }
38        }
39
40        out.push(bytes[i] as char);
41        i += 1;
42    }
43
44    out
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn render_template_replaces_known_and_blanks_unknown() {
53        let mut vars = TemplateVars::new();
54        vars.insert("tool".to_string(), "git".to_string());
55
56        let rendered = render_template("run {{tool}} {{missing}}", &vars);
57        assert_eq!(rendered, "run git ");
58    }
59}