akari/
template_manager.rs

1use 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
11/// Manages template loading, caching, and rendering
12pub struct TemplateManager {
13    /// Base directory for template files
14    template_dir: PathBuf,
15    /// Cache of parsed templates
16    template_cache: Arc<RwLock<HashMap<String, Vec<Token>>>>,
17    /// Maximum cache size (number of templates)
18    max_cache_size: usize,
19    /// Cache enabled flag
20    cache_enabled: bool,
21}
22
23impl TemplateManager {
24    /// Creates a new TemplateManager with the given template directory
25    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    /// Creates a new TemplateManager with default template directory "./template"
35    pub fn default() -> Self {
36        Self::new("./template")
37    }
38    
39    /// Sets the maximum cache size
40    pub fn with_max_cache_size(mut self, size: usize) -> Self {
41        self.max_cache_size = size;
42        self
43    }
44    
45    /// Enables or disables template caching
46    pub fn with_caching(mut self, enabled: bool) -> Self {
47        self.cache_enabled = enabled;
48        self
49    }
50    
51    /// Loads and renders a template by name
52    pub fn render(&self, template_name: &str, data: &HashMap<String, Obj>) -> Result<String, String> {
53        // Load the template content from file or cache
54        let template_content = self.load_template_content(template_name)?;
55        // Get all template tokens needed (with inheritance resolution)
56        let tokens = self.load_template_with_inheritance(template_content)?;
57        // Use your existing compile function to process these tokens
58        compile(tokens, data.clone())
59    }
60
61    /// Loads and renders a template by string
62    pub fn render_string(&self, template_content: String, data: &HashMap<String, Obj>) -> Result<String, String> {
63        // Get all template tokens needed (with inheritance resolution)
64        let tokens = self.load_template_with_inheritance(template_content)?;
65        
66        // Use your existing compile function to process these tokens
67        compile(tokens, data.clone())
68    }
69    
70    /// Loads a template and resolves inheritance
71    fn load_template_with_inheritance(&self, template_content: String) -> Result<Vec<Token>, String> {
72        let tokens = tokenize(&template_content);
73        
74        // Check if this template extends another one
75        if let Some(parent_name) = self.extract_parent_template_name(&tokens) {
76            // Load the parent template
77            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            // Extract blocks from both parent and child
83            let parent_blocks = self.extract_blocks(&parent_tokens)?;
84            let child_blocks = self.extract_blocks(&tokens)?;
85            
86            // Create merged blocks where child overrides parent
87            let mut merged_blocks = parent_blocks;
88            for (name, tokens) in child_blocks {
89                merged_blocks.insert(name, tokens);
90            }
91            
92            // Create the final template by replacing blocks in parent with merged blocks
93            self.create_template_with_blocks(&parent_tokens, merged_blocks)
94        } else {
95            // No inheritance, just process blocks within this template
96            let blocks = self.extract_blocks(&tokens)?;
97            self.create_template_with_blocks(&tokens, blocks)
98        }
99    }
100    
101    /// Creates a processed template with all blocks properly defined
102    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                // Skip template directive since we've already handled inheritance
113                Token::TemplateKeyword => {
114                    // Skip template keyword and the template name string
115                    i += 2;
116                },
117                // Add block definition with its identifier
118                Token::BlockKeyword => {
119                    if i + 1 < template_tokens.len() {
120                        if let Token::Identifier(name) = &template_tokens[i + 1] {
121                            // Add BlockKeyword and its name
122                            result.push(template_tokens[i].clone());
123                            result.push(template_tokens[i + 1].clone());
124                            
125                            // Skip to after block name
126                            i += 2;
127                            
128                            // Skip EndOfStatement if present
129                            if i < template_tokens.len() && matches!(template_tokens[i], Token::EndOfStatement) {
130                                result.push(template_tokens[i].clone());
131                                i += 1;
132                            }
133                            
134                            // Add the block content from our blocks map
135                            if let Some(block_tokens) = blocks.get(name) {
136                                result.extend_from_slice(block_tokens);
137                            }
138                            
139                            // Skip to after the endblock
140                            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                                            // Add EndBlockKeyword
148                                            result.push(template_tokens[i].clone());
149                                            break;
150                                        }
151                                    },
152                                    _ => {}
153                                }
154                                i += 1;
155                            }
156                            i += 1; // Move past EndBlockKeyword
157                        } 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                    // Copy all other tokens as-is
168                    result.push(template_tokens[i].clone());
169                    i += 1;
170                }
171            }
172        }
173        
174        Ok(result)
175    }
176    
177    /// Loads the raw content of a template file
178    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    /// Extracts the parent template name if this template extends another
185    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    /// Extracts all blocks from a token stream
200    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; // Skip block keyword and name
210                        
211                        // Skip EndOfStatement if present
212                        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                        // Find matching endblock
221                        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                        // Extract block content (excluding endblock token)
240                        let content = tokens[start..end_pos].to_vec();
241                        blocks.insert(block_name, content);
242                        
243                        i = end_pos + 1; // Skip past EndBlockKeyword
244                        continue;
245                    }
246                }
247            }
248            i += 1;
249        }
250        
251        Ok(blocks)
252    }
253    
254    /// Reloads a template from disk, bypassing the cache
255    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        // Try loading to verify it exists and is valid
260        self.load_template_content(template_name)?;
261        Ok(())
262    }
263    
264    /// Clears the entire template cache
265    pub fn clear_cache(&self) {
266        if self.cache_enabled {
267            self.template_cache.write().unwrap().clear();
268        }
269    }
270    
271    /// Lists all available templates in the template directory
272    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}