akari/
template_manager.rs1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5use std::sync::RwLock;
6
7use crate::object::Object as Obj;
8use crate::parse::{tokenize, Token};
9use crate::compile::compile;
10
11pub struct TemplateManager {
13 template_dir: PathBuf,
15 template_cache: Arc<RwLock<HashMap<String, Vec<Token>>>>,
17 max_cache_size: usize,
19 cache_enabled: bool,
21}
22
23impl TemplateManager {
24 pub fn new<P: AsRef<Path>>(template_dir: P) -> Self {
26 TemplateManager {
27 template_dir: template_dir.as_ref().to_path_buf(),
28 template_cache: Arc::new(RwLock::new(HashMap::new())),
29 max_cache_size: 100,
30 cache_enabled: true,
31 }
32 }
33
34 pub fn default() -> Self {
36 Self::new("./template")
37 }
38
39 pub fn with_max_cache_size(mut self, size: usize) -> Self {
41 self.max_cache_size = size;
42 self
43 }
44
45 pub fn with_caching(mut self, enabled: bool) -> Self {
47 self.cache_enabled = enabled;
48 self
49 }
50
51 pub fn render(&self, template_name: &str, data: &HashMap<String, Obj>) -> Result<String, String> {
53 let template_content = self.load_template_content(template_name)?;
55 let tokens = self.load_template_with_inheritance(template_content)?;
57 compile(tokens, data.clone())
59 }
60
61 pub fn render_string(&self, template_content: String, data: &HashMap<String, Obj>) -> Result<String, String> {
63 let tokens = self.load_template_with_inheritance(template_content)?;
65
66 compile(tokens, data.clone())
68 }
69
70 fn load_template_with_inheritance(&self, template_content: String) -> Result<Vec<Token>, String> {
72 let tokens = tokenize(&template_content);
73
74 if let Some(parent_name) = self.extract_parent_template_name(&tokens) {
76 let parent_tokens = match self.load_template_content(&parent_name) {
78 Ok(content) => tokenize(&content),
79 Err(e) => return Err(format!("Failed to load parent template '{}': {}", parent_name, e)),
80 };
81
82 let parent_blocks = self.extract_blocks(&parent_tokens)?;
84 let child_blocks = self.extract_blocks(&tokens)?;
85
86 let mut merged_blocks = parent_blocks;
88 for (name, tokens) in child_blocks {
89 merged_blocks.insert(name, tokens);
90 }
91
92 self.create_template_with_blocks(&parent_tokens, merged_blocks)
94 } else {
95 let blocks = self.extract_blocks(&tokens)?;
97 self.create_template_with_blocks(&tokens, blocks)
98 }
99 }
100
101 fn create_template_with_blocks(
103 &self,
104 template_tokens: &[Token],
105 blocks: HashMap<String, Vec<Token>>
106 ) -> Result<Vec<Token>, String> {
107 let mut result = Vec::new();
108 let mut i = 0;
109
110 while i < template_tokens.len() {
111 match &template_tokens[i] {
112 Token::TemplateKeyword => {
114 i += 2;
116 },
117 Token::BlockKeyword => {
119 if i + 1 < template_tokens.len() {
120 if let Token::Identifier(name) = &template_tokens[i + 1] {
121 result.push(template_tokens[i].clone());
123 result.push(template_tokens[i + 1].clone());
124
125 i += 2;
127
128 if i < template_tokens.len() && matches!(template_tokens[i], Token::EndOfStatement) {
130 result.push(template_tokens[i].clone());
131 i += 1;
132 }
133
134 if let Some(block_tokens) = blocks.get(name) {
136 result.extend_from_slice(block_tokens);
137 }
138
139 let mut depth = 1;
141 while i < template_tokens.len() && depth > 0 {
142 match template_tokens[i] {
143 Token::BlockKeyword => depth += 1,
144 Token::EndBlockKeyword => {
145 depth -= 1;
146 if depth == 0 {
147 result.push(template_tokens[i].clone());
149 break;
150 }
151 },
152 _ => {}
153 }
154 i += 1;
155 }
156 i += 1; } else {
158 result.push(template_tokens[i].clone());
159 i += 1;
160 }
161 } else {
162 result.push(template_tokens[i].clone());
163 i += 1;
164 }
165 },
166 _ => {
167 result.push(template_tokens[i].clone());
169 i += 1;
170 }
171 }
172 }
173
174 Ok(result)
175 }
176
177 fn load_template_content(&self, template_name: &str) -> Result<String, String> {
179 let template_path = self.template_dir.join(template_name);
180 fs::read_to_string(&template_path)
181 .map_err(|e| format!("Failed to read template '{}': {}", template_name, e))
182 }
183
184 fn extract_parent_template_name(&self, tokens: &[Token]) -> Option<String> {
186 for i in 0..tokens.len() {
187 if let Token::TemplateKeyword = &tokens[i] {
188 if i + 1 < tokens.len() {
189 if let Token::Object(Obj::Str(name)) = &tokens[i + 1] {
190 return Some(name.clone());
191 }
192 }
193 break;
194 }
195 }
196 None
197 }
198
199 fn extract_blocks(&self, tokens: &[Token]) -> Result<HashMap<String, Vec<Token>>, String> {
201 let mut blocks = HashMap::new();
202 let mut i = 0;
203
204 while i < tokens.len() {
205 if let Token::BlockKeyword = tokens[i] {
206 if i + 1 < tokens.len() {
207 if let Token::Identifier(name) = &tokens[i + 1] {
208 let block_name = name.clone();
209 i += 2; if i < tokens.len() && matches!(tokens[i], Token::EndOfStatement) {
213 i += 1;
214 }
215
216 let start = i;
217 let mut depth = 1;
218 let mut end_pos = i;
219
220 while end_pos < tokens.len() && depth > 0 {
222 match &tokens[end_pos] {
223 Token::BlockKeyword => depth += 1,
224 Token::EndBlockKeyword => {
225 depth -= 1;
226 if depth == 0 {
227 break;
228 }
229 },
230 _ => {}
231 }
232 end_pos += 1;
233 }
234
235 if depth > 0 {
236 return Err(format!("Unterminated block: {}", block_name));
237 }
238
239 let content = tokens[start..end_pos].to_vec();
241 blocks.insert(block_name, content);
242
243 i = end_pos + 1; continue;
245 }
246 }
247 }
248 i += 1;
249 }
250
251 Ok(blocks)
252 }
253
254 pub fn reload_template(&self, template_name: &str) -> Result<(), String> {
256 if self.cache_enabled {
257 self.template_cache.write().unwrap().remove(template_name);
258 }
259 self.load_template_content(template_name)?;
261 Ok(())
262 }
263
264 pub fn clear_cache(&self) {
266 if self.cache_enabled {
267 self.template_cache.write().unwrap().clear();
268 }
269 }
270
271 pub fn list_templates(&self) -> Result<Vec<String>, String> {
273 let entries = fs::read_dir(&self.template_dir)
274 .map_err(|e| format!("Failed to read template directory: {}", e))?;
275
276 let mut templates = Vec::new();
277
278 for entry in entries {
279 if let Ok(entry) = entry {
280 if entry.path().is_file() {
281 if let Some(filename) = entry.path().file_name() {
282 if let Some(name) = filename.to_str() {
283 templates.push(name.to_string());
284 }
285 }
286 }
287 }
288 }
289
290 Ok(templates)
291 }
292}