ricecoder_commands/
template.rs1use crate::error::{CommandError, Result};
2use regex::Regex;
3use std::collections::HashMap;
4
5pub struct TemplateProcessor;
7
8impl TemplateProcessor {
9 pub fn process(template: &str, variables: &HashMap<String, String>) -> Result<String> {
15 let mut result = template.to_string();
16
17 let pattern = Regex::new(r"\{\{([a-zA-Z_][a-zA-Z0-9_]*(?::[^}]*)?)\}\}")?;
19
20 for cap in pattern.captures_iter(template) {
21 let full_match = cap.get(0).unwrap().as_str();
22 let var_spec = cap.get(1).unwrap().as_str();
23
24 let (var_name, default_value) = if let Some(colon_pos) = var_spec.find(':') {
26 let (name, default) = var_spec.split_at(colon_pos);
27 (name, Some(&default[1..]))
28 } else {
29 (var_spec, None)
30 };
31
32 let value = variables
34 .get(var_name)
35 .map(|s| s.as_str())
36 .or(default_value)
37 .ok_or_else(|| {
38 CommandError::TemplateError(format!("Missing variable: {}", var_name))
39 })?;
40
41 result = result.replace(full_match, value);
42 }
43
44 Ok(result)
45 }
46
47 pub fn extract_variables(template: &str) -> Result<Vec<String>> {
49 let pattern = Regex::new(r"\{\{([a-zA-Z_][a-zA-Z0-9_]*(?::[^}]*)?)\}\}")?;
50 let mut variables = Vec::new();
51
52 for cap in pattern.captures_iter(template) {
53 let var_spec = cap.get(1).unwrap().as_str();
54 let var_name = if let Some(colon_pos) = var_spec.find(':') {
55 &var_spec[..colon_pos]
56 } else {
57 var_spec
58 };
59 if !variables.contains(&var_name.to_string()) {
60 variables.push(var_name.to_string());
61 }
62 }
63
64 Ok(variables)
65 }
66
67 pub fn validate_variables(template: &str, provided: &HashMap<String, String>) -> Result<()> {
69 let required = Self::extract_variables(template)?;
70
71 for var in required {
72 if !provided.contains_key(&var) {
73 let pattern = Regex::new(&format!(r"\{{\{{{var}:([^}}]*)\}}\}}"))?;
75 if !pattern.is_match(template) {
76 return Err(CommandError::TemplateError(format!(
77 "Missing required variable: {}",
78 var
79 )));
80 }
81 }
82 }
83
84 Ok(())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_simple_substitution() {
94 let mut vars = HashMap::new();
95 vars.insert("name".to_string(), "Alice".to_string());
96 vars.insert("age".to_string(), "30".to_string());
97
98 let template = "Hello {{name}}, you are {{age}} years old";
99 let result = TemplateProcessor::process(template, &vars).unwrap();
100 assert_eq!(result, "Hello Alice, you are 30 years old");
101 }
102
103 #[test]
104 fn test_substitution_with_default() {
105 let mut vars = HashMap::new();
106 vars.insert("name".to_string(), "Bob".to_string());
107
108 let template = "Hello {{name}}, you are {{age:unknown}} years old";
109 let result = TemplateProcessor::process(template, &vars).unwrap();
110 assert_eq!(result, "Hello Bob, you are unknown years old");
111 }
112
113 #[test]
114 fn test_missing_variable_error() {
115 let vars = HashMap::new();
116 let template = "Hello {{name}}";
117 let result = TemplateProcessor::process(template, &vars);
118 assert!(result.is_err());
119 }
120
121 #[test]
122 fn test_extract_variables() {
123 let template = "Hello {{name}}, you are {{age:unknown}} years old";
124 let vars = TemplateProcessor::extract_variables(template).unwrap();
125 assert_eq!(vars.len(), 2);
126 assert!(vars.contains(&"name".to_string()));
127 assert!(vars.contains(&"age".to_string()));
128 }
129
130 #[test]
131 fn test_validate_variables_success() {
132 let mut vars = HashMap::new();
133 vars.insert("name".to_string(), "Charlie".to_string());
134
135 let template = "Hello {{name}}";
136 let result = TemplateProcessor::validate_variables(template, &vars);
137 assert!(result.is_ok());
138 }
139
140 #[test]
141 fn test_validate_variables_with_default() {
142 let vars = HashMap::new();
143 let template = "Hello {{name:Guest}}";
144 let result = TemplateProcessor::validate_variables(template, &vars);
145 assert!(result.is_ok());
146 }
147
148 #[test]
149 fn test_validate_variables_missing() {
150 let vars = HashMap::new();
151 let template = "Hello {{name}}";
152 let result = TemplateProcessor::validate_variables(template, &vars);
153 assert!(result.is_err());
154 }
155
156 #[test]
157 fn test_no_variables() {
158 let vars = HashMap::new();
159 let template = "Hello world";
160 let result = TemplateProcessor::process(template, &vars).unwrap();
161 assert_eq!(result, "Hello world");
162 }
163}