1use crate::ast::{ASTNode, Program};
11use crate::lexer::Lexer;
12use crate::parser::Parser;
13use crate::resolver::{ImportType, Resolver};
14use std::collections::{HashMap, HashSet};
15use std::fs;
16use std::path::Path;
17
18#[derive(Debug, Clone)]
20struct CacheEntry {
21 program: Program,
23
24 #[allow(dead_code)]
26 modified: Option<std::time::SystemTime>,
27}
28
29pub struct ImportProcessor {
31 resolver: Resolver,
33
34 cache: HashMap<String, CacheEntry>,
36
37 processing: HashSet<String>,
39
40 cache_enabled: bool,
42}
43
44impl ImportProcessor {
45 pub fn new<P: AsRef<Path>>(base_dir: P) -> Self {
47 Self {
48 resolver: Resolver::new(base_dir),
49 cache: HashMap::new(),
50 processing: HashSet::new(),
51 cache_enabled: true,
52 }
53 }
54
55 pub fn set_cache_enabled(&mut self, enabled: bool) {
57 self.cache_enabled = enabled;
58 }
59
60 pub fn clear_cache(&mut self) {
62 self.cache.clear();
63 }
64
65 pub fn process_imports(&mut self, program: &mut Program) -> Result<(), String> {
67 let mut imports_to_process = Vec::new();
69
70 for (index, node) in program.nodes.iter().enumerate() {
71 if let ASTNode::Import { path } = node {
72 imports_to_process.push((index, path.clone()));
73 }
74 }
75
76 for (index, path) in imports_to_process.iter().rev() {
78 let imported_program = self.load_import(path)?;
79
80 program.nodes.remove(*index);
82
83 for (offset, node) in imported_program.nodes.iter().enumerate() {
85 program.nodes.insert(index + offset, node.clone());
86 }
87 }
88
89 Ok(())
90 }
91
92 fn load_import(&mut self, path: &str) -> Result<Program, String> {
94 let resolved = self.resolver.resolve(path)?;
96
97 if self.processing.contains(&resolved.resolved_path) {
99 return Err(format!("Circular import detected: {}", path));
100 }
101
102 if self.cache_enabled {
104 if let Some(entry) = self.cache.get(&resolved.resolved_path) {
105 return Ok(entry.program.clone());
106 }
107 }
108
109 self.processing.insert(resolved.resolved_path.clone());
111
112 let content = match resolved.import_type {
114 ImportType::Url => self.load_url(&resolved.resolved_path)?,
115 _ => self.load_file(&resolved.resolved_path)?,
116 };
117
118 let mut lexer = Lexer::new(&content);
120 let tokens = lexer.tokenize()
121 .map_err(|e| format!("Error tokenizing {}: {}", path, e))?;
122
123 let mut parser = Parser::new(tokens);
124 let mut program = parser.parse()
125 .map_err(|e| format!("Error parsing {}: {}", path, e))?;
126
127 let old_base = self.resolver.base_dir().to_path_buf();
129 if let ImportType::Relative | ImportType::Absolute = resolved.import_type {
130 if let Some(parent) = Path::new(&resolved.resolved_path).parent() {
131 self.resolver.set_base_dir(parent);
132 }
133 }
134
135 self.process_imports(&mut program)?;
137
138 self.resolver.set_base_dir(old_base);
140
141 self.processing.remove(&resolved.resolved_path);
143
144 if self.cache_enabled {
146 self.cache.insert(
147 resolved.resolved_path.clone(),
148 CacheEntry {
149 program: program.clone(),
150 modified: None,
151 },
152 );
153 }
154
155 Ok(program)
156 }
157
158 fn load_file(&self, path: &str) -> Result<String, String> {
160 fs::read_to_string(path)
161 .map_err(|e| format!("Failed to read file {}: {}", path, e))
162 }
163
164 fn load_url(&self, url: &str) -> Result<String, String> {
166 Err(format!(
169 "URL imports not yet supported in this environment: {}. \
170 To enable URL imports, add an HTTP client dependency.",
171 url
172 ))
173 }
174
175 pub fn resolver(&self) -> &Resolver {
177 &self.resolver
178 }
179
180 pub fn resolver_mut(&mut self) -> &mut Resolver {
182 &mut self.resolver
183 }
184}
185
186impl Default for ImportProcessor {
187 fn default() -> Self {
188 Self::new(".")
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use std::fs;
196 use std::io::Write;
197 use tempfile::TempDir;
198
199 #[test]
200 fn test_process_simple_import() {
201 let temp_dir = TempDir::new().unwrap();
202 let base_path = temp_dir.path();
203
204 let import_file = base_path.join("colors.tcss");
206 let mut file = fs::File::create(&import_file).unwrap();
207 writeln!(file, "@var primary: #3498db").unwrap();
208 writeln!(file, "@var secondary: #2ecc71").unwrap();
209
210 let main_content = "@import './colors.tcss'\n\n.button:\n background: primary";
212
213 let mut lexer = Lexer::new(main_content);
214 let tokens = lexer.tokenize().unwrap();
215 let mut parser = Parser::new(tokens);
216 let mut program = parser.parse().unwrap();
217
218 let mut processor = ImportProcessor::new(base_path);
220 processor.process_imports(&mut program).unwrap();
221
222 assert_eq!(program.nodes.len(), 3);
224 }
225
226 #[test]
227 fn test_circular_import_detection() {
228 let temp_dir = TempDir::new().unwrap();
229 let base_path = temp_dir.path();
230
231 let file_a = base_path.join("a.tcss");
233 let mut f = fs::File::create(&file_a).unwrap();
234 writeln!(f, "@import './b.tcss'").unwrap();
235
236 let file_b = base_path.join("b.tcss");
238 let mut f = fs::File::create(&file_b).unwrap();
239 writeln!(f, "@import './a.tcss'").unwrap();
240
241 let mut lexer = Lexer::new("@import './a.tcss'");
243 let tokens = lexer.tokenize().unwrap();
244 let mut parser = Parser::new(tokens);
245 let mut program = parser.parse().unwrap();
246
247 let mut processor = ImportProcessor::new(base_path);
248 let result = processor.process_imports(&mut program);
249
250 assert!(result.is_err());
251 assert!(result.unwrap_err().contains("Circular import"));
252 }
253
254 #[test]
255 fn test_cache() {
256 let temp_dir = TempDir::new().unwrap();
257 let base_path = temp_dir.path();
258
259 let import_file = base_path.join("vars.tcss");
261 let mut file = fs::File::create(&import_file).unwrap();
262 writeln!(file, "@var x: 10").unwrap();
263
264 let mut processor = ImportProcessor::new(base_path);
265
266 let result1 = processor.load_import("./vars.tcss").unwrap();
268 assert_eq!(processor.cache.len(), 1);
269
270 let result2 = processor.load_import("./vars.tcss").unwrap();
272 assert_eq!(result1.nodes.len(), result2.nodes.len());
273
274 processor.clear_cache();
276 assert_eq!(processor.cache.len(), 0);
277 }
278}
279