mecha10_cli_core/framework/
template_engine.rs1use anyhow::{Context, Result};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9
10pub struct TemplateEngine {
12 template_dir: PathBuf,
14}
15
16impl TemplateEngine {
17 pub fn new(template_dir: impl Into<PathBuf>) -> Self {
22 Self {
23 template_dir: template_dir.into(),
24 }
25 }
26
27 pub fn render(&self, template_path: &str, variables: &HashMap<&str, &str>) -> Result<String> {
46 let full_path = self.template_dir.join(template_path);
47
48 let template_content =
50 std::fs::read_to_string(&full_path).context(format!("Failed to read template: {}", full_path.display()))?;
51
52 let mut result = template_content;
54 for (key, value) in variables {
55 let placeholder = format!("{{{{{}}}}}", key);
56 result = result.replace(&placeholder, value);
57 }
58
59 Ok(result)
60 }
61
62 pub async fn render_to_file(
69 &self,
70 template_path: &str,
71 output_path: impl AsRef<Path>,
72 variables: &HashMap<&str, &str>,
73 ) -> Result<()> {
74 let rendered = self.render(template_path, variables)?;
75
76 tokio::fs::write(output_path.as_ref(), rendered)
77 .await
78 .context(format!("Failed to write file: {}", output_path.as_ref().display()))?;
79
80 Ok(())
81 }
82
83 pub fn list_templates(&self, category: &str) -> Result<Vec<String>> {
91 let category_path = self.template_dir.join(category);
92
93 if !category_path.exists() {
94 return Ok(Vec::new());
95 }
96
97 let mut templates = Vec::new();
98 let entries = std::fs::read_dir(&category_path).context(format!(
99 "Failed to read template directory: {}",
100 category_path.display()
101 ))?;
102
103 for entry in entries {
104 let entry = entry?;
105 let path = entry.path();
106
107 if path.is_file() {
108 if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
109 if file_name.ends_with(".template") {
110 let name = file_name.trim_end_matches(".template").to_string();
112 templates.push(name);
113 }
114 }
115 }
116 }
117
118 templates.sort();
119 Ok(templates)
120 }
121
122 pub fn template_exists(&self, template_path: &str) -> bool {
124 self.template_dir.join(template_path).exists()
125 }
126}
127
128impl Default for TemplateEngine {
129 fn default() -> Self {
131 let template_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates");
133 Self::new(template_dir)
134 }
135}
136
137pub struct TemplateVars {
139 vars: HashMap<String, String>,
140}
141
142impl TemplateVars {
143 pub fn new() -> Self {
145 Self { vars: HashMap::new() }
146 }
147
148 #[allow(dead_code)]
150 pub fn add(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
151 self.vars.insert(key.into(), value.into());
152 self
153 }
154
155 pub fn add_name(&mut self, name: &str) -> &mut Self {
162 use crate::utils::{to_pascal_case, to_snake_case};
163
164 self.vars.insert("name".to_string(), name.to_string());
165 self.vars.insert("PascalName".to_string(), to_pascal_case(name));
166 self.vars.insert("snake_name".to_string(), to_snake_case(name));
167 self.vars
168 .insert("UPPER_NAME".to_string(), to_snake_case(name).to_uppercase());
169 self
170 }
171
172 pub fn to_hashmap(&self) -> HashMap<&str, &str> {
174 self.vars.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect()
175 }
176}
177
178impl Default for TemplateVars {
179 fn default() -> Self {
180 Self::new()
181 }
182}