tailwind_rs_scanner/
cache.rs1use crate::class_extractor::ExtractedClass;
7use crate::error::{Result, ScannerError};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11use std::time::{Duration, SystemTime};
12
13#[derive(Debug)]
15pub struct ScanCache {
16 entries: HashMap<PathBuf, CacheEntry>,
18 stats: CacheStats,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct CacheEntry {
25 pub path: PathBuf,
27 pub classes: Vec<ExtractedClass>,
29 pub modified: SystemTime,
31 pub created: SystemTime,
33 pub ttl: Duration,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct CacheStats {
40 pub total_entries: usize,
42 pub hits: usize,
44 pub misses: usize,
46 pub size_bytes: usize,
48 pub average_entry_size: usize,
50}
51
52impl Default for CacheStats {
53 fn default() -> Self {
54 Self {
55 total_entries: 0,
56 hits: 0,
57 misses: 0,
58 size_bytes: 0,
59 average_entry_size: 0,
60 }
61 }
62}
63
64impl ScanCache {
65 pub fn new() -> Self {
67 Self {
68 entries: HashMap::new(),
69 stats: CacheStats::default(),
70 }
71 }
72
73 pub fn get_file_classes(&mut self, path: &PathBuf) -> Option<&Vec<ExtractedClass>> {
75 if let Some(entry) = self.entries.get(path) {
76 if entry.is_valid() {
77 self.stats.hits += 1;
78 Some(&entry.classes)
79 } else {
80 self.stats.misses += 1;
81 None
82 }
83 } else {
84 self.stats.misses += 1;
85 None
86 }
87 }
88
89 pub fn update_file(&mut self, path: PathBuf, classes: Vec<ExtractedClass>) {
91 let entry = CacheEntry {
92 path: path.clone(),
93 classes,
94 modified: SystemTime::now(),
95 created: SystemTime::now(),
96 ttl: Duration::from_secs(3600), };
98
99 self.entries.insert(path, entry);
100 self.stats.total_entries = self.entries.len();
101 }
102
103 pub fn is_cached(&self, path: &PathBuf) -> bool {
105 if let Some(entry) = self.entries.get(path) {
106 entry.is_valid()
107 } else {
108 false
109 }
110 }
111
112 pub fn remove_file(&mut self, path: &PathBuf) {
114 self.entries.remove(path);
115 self.stats.total_entries = self.entries.len();
116 }
117
118 pub fn clear(&mut self) {
120 self.entries.clear();
121 self.stats = CacheStats::default();
122 }
123
124 pub fn get_stats(&self) -> CacheStats {
126 self.stats.clone()
127 }
128
129 pub fn clean_expired(&mut self) {
131 let now = SystemTime::now();
132 self.entries.retain(|_, entry| entry.is_valid_at(now));
133 self.stats.total_entries = self.entries.len();
134 }
135}
136
137impl CacheEntry {
138 pub fn is_valid(&self) -> bool {
140 self.is_valid_at(SystemTime::now())
141 }
142
143 pub fn is_valid_at(&self, now: SystemTime) -> bool {
145 if let Ok(elapsed) = now.duration_since(self.created) {
146 elapsed < self.ttl
147 } else {
148 false
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::class_extractor::ClassContext;
157
158 #[test]
159 fn test_scan_cache_creation() {
160 let cache = ScanCache::new();
161 assert_eq!(cache.entries.len(), 0);
162 }
163
164 #[test]
165 fn test_cache_entry_creation() {
166 let path = PathBuf::from("test.rs");
167 let classes = vec![ExtractedClass {
168 class_name: "p-4".to_string(),
169 context: ClassContext::new(),
170 line: 1,
171 column: 1,
172 }];
173
174 let entry = CacheEntry {
175 path: path.clone(),
176 classes,
177 modified: SystemTime::now(),
178 created: SystemTime::now(),
179 ttl: Duration::from_secs(3600),
180 };
181
182 assert_eq!(entry.path, path);
183 assert!(entry.is_valid());
184 }
185
186 #[test]
187 fn test_cache_operations() {
188 let mut cache = ScanCache::new();
189 let path = PathBuf::from("test.rs");
190 let classes = vec![ExtractedClass {
191 class_name: "p-4".to_string(),
192 context: ClassContext::new(),
193 line: 1,
194 column: 1,
195 }];
196
197 cache.update_file(path.clone(), classes);
199 assert!(cache.is_cached(&path));
200
201 let cached_classes = cache.get_file_classes(&path);
203 assert!(cached_classes.is_some());
204
205 cache.remove_file(&path);
207 assert!(!cache.is_cached(&path));
208 }
209
210 #[test]
211 fn test_cache_stats() {
212 let cache = ScanCache::new();
213 let stats = cache.get_stats();
214 assert_eq!(stats.total_entries, 0);
215 assert_eq!(stats.hits, 0);
216 assert_eq!(stats.misses, 0);
217 }
218}