1#[derive(Debug, Clone)]
10pub struct EmbedStats {
11 pub templates: CategoryStats,
13 pub packages: PackageStats,
15 pub fonts: CategoryStats,
17 pub dedup: DedupStats,
19}
20
21#[derive(Debug, Clone, Copy)]
23pub struct DedupStats {
24 pub total_files: usize,
26 pub unique_blobs: usize,
28 pub duplicate_count: usize,
30 pub saved_bytes: usize,
32}
33
34#[derive(Debug, Clone, Copy)]
36pub struct CategoryStats {
37 pub original_size: usize,
39 pub compressed_size: usize,
41 pub file_count: usize,
43}
44
45#[derive(Debug, Clone)]
47pub struct PackageStats {
48 pub packages: Vec<PackageInfo>,
50 pub original_size: usize,
52 pub compressed_size: usize,
54}
55
56#[derive(Debug, Clone)]
58pub struct PackageInfo {
59 pub name: String,
61 pub original_size: usize,
63 pub compressed_size: usize,
65 pub file_count: usize,
67}
68
69impl EmbedStats {
70 pub fn total_original(&self) -> usize {
72 self.templates.original_size + self.packages.original_size + self.fonts.original_size
73 }
74
75 pub fn total_compressed(&self) -> usize {
77 self.templates.compressed_size + self.packages.compressed_size + self.fonts.compressed_size
78 }
79
80 pub fn compression_ratio(&self) -> f64 {
82 compression_ratio(self.total_original(), self.total_compressed())
83 }
84
85 pub fn total_deduplicated(&self) -> usize {
87 self.total_compressed() - self.dedup.saved_bytes
88 }
89
90 pub fn overall_ratio(&self) -> f64 {
92 compression_ratio(self.total_original(), self.total_deduplicated())
93 }
94
95 fn total_file_count(&self) -> usize {
97 self.templates.file_count
98 + self.fonts.file_count
99 + self
100 .packages
101 .packages
102 .iter()
103 .map(|p| p.file_count)
104 .sum::<usize>()
105 }
106
107 pub fn display(&self) {
109 print!("{self}");
110 }
111}
112
113impl std::fmt::Display for EmbedStats {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 writeln!(f, "Embed Summary")?;
116 writeln!(f, "========================")?;
117
118 if self.templates.file_count > 0 {
120 writeln!(
121 f,
122 "Templates: {:>9} -> {:>9} ({:>5.1}% reduced, {} files)",
123 format_size(self.templates.original_size),
124 format_size(self.templates.compressed_size),
125 self.templates.compression_ratio() * 100.0,
126 self.templates.file_count
127 )?;
128 }
129
130 if self.fonts.file_count > 0 {
132 writeln!(
133 f,
134 "Fonts: {:>9} -> {:>9} ({:>5.1}% reduced, {} files)",
135 format_size(self.fonts.original_size),
136 format_size(self.fonts.compressed_size),
137 self.fonts.compression_ratio() * 100.0,
138 self.fonts.file_count
139 )?;
140 }
141
142 if !self.packages.packages.is_empty() {
144 writeln!(f, "Packages:")?;
145
146 let (name_width, orig_width, comp_width) =
148 self.packages
149 .packages
150 .iter()
151 .fold((0, 0, 0), |(nw, ow, cw), p| {
152 (
153 nw.max(p.name.len()),
154 ow.max(format_size(p.original_size).len()),
155 cw.max(format_size(p.compressed_size).len()),
156 )
157 });
158
159 for pkg in &self.packages.packages {
160 writeln!(
161 f,
162 " {:<name_w$} {:>orig_w$} -> {:>comp_w$} ({:>5.1}%)",
163 pkg.name,
164 format_size(pkg.original_size),
165 format_size(pkg.compressed_size),
166 pkg.compression_ratio() * 100.0,
167 name_w = name_width,
168 orig_w = orig_width,
169 comp_w = comp_width,
170 )?;
171 }
172 }
173
174 writeln!(f, "------------------------")?;
176 writeln!(
177 f,
178 "Compressed: {} -> {} ({:.1}% reduced, {} files)",
179 format_size(self.total_original()),
180 format_size(self.total_compressed()),
181 self.compression_ratio() * 100.0,
182 self.total_file_count()
183 )?;
184
185 if self.dedup.duplicate_count > 0 {
187 writeln!(
188 f,
189 "Deduplicated: {} unique blobs, {} duplicates removed (-{})",
190 self.dedup.unique_blobs,
191 self.dedup.duplicate_count,
192 format_size(self.dedup.saved_bytes)
193 )?;
194 }
195
196 writeln!(
198 f,
199 "Total: {} -> {} ({:.1}% reduced)",
200 format_size(self.total_original()),
201 format_size(self.total_deduplicated()),
202 self.overall_ratio() * 100.0
203 )
204 }
205}
206
207pub trait HasCompressionRatio {
209 fn original_size(&self) -> usize;
210 fn compressed_size(&self) -> usize;
211
212 fn compression_ratio(&self) -> f64 {
213 compression_ratio(self.original_size(), self.compressed_size())
214 }
215}
216
217macro_rules! impl_has_compression_ratio {
218 ($($ty:ty),*) => {
219 $(impl HasCompressionRatio for $ty {
220 fn original_size(&self) -> usize { self.original_size }
221 fn compressed_size(&self) -> usize { self.compressed_size }
222 })*
223 };
224}
225
226impl_has_compression_ratio!(CategoryStats, PackageInfo, PackageStats);
227
228fn compression_ratio(original: usize, compressed: usize) -> f64 {
231 if original == 0 {
232 return 0.0;
233 }
234 1.0 - (compressed as f64 / original as f64)
235}
236
237fn format_size(bytes: usize) -> String {
239 const KB: usize = 1024;
240 const MB: usize = KB * 1024;
241
242 if bytes >= MB {
243 format!("{:.2} MB", bytes as f64 / MB as f64)
244 } else if bytes >= KB {
245 format!("{:.1} KB", bytes as f64 / KB as f64)
246 } else {
247 format!("{bytes} B")
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_format_size_bytes() {
257 assert_eq!(format_size(0), "0 B");
258 assert_eq!(format_size(512), "512 B");
259 assert_eq!(format_size(1023), "1023 B");
260 }
261
262 #[test]
263 fn test_format_size_kilobytes() {
264 assert_eq!(format_size(1024), "1.0 KB");
265 assert_eq!(format_size(1536), "1.5 KB");
266 assert_eq!(format_size(10240), "10.0 KB");
267 }
268
269 #[test]
270 fn test_format_size_megabytes() {
271 assert_eq!(format_size(1048576), "1.00 MB");
272 assert_eq!(format_size(1572864), "1.50 MB");
273 }
274
275 #[test]
276 fn test_compression_ratio_zero_original() {
277 let stats = CategoryStats {
278 original_size: 0,
279 compressed_size: 0,
280 file_count: 0,
281 };
282 assert_eq!(stats.compression_ratio(), 0.0);
283 }
284
285 #[test]
286 fn test_compression_ratio_75_percent() {
287 let stats = CategoryStats {
291 original_size: 1000,
292 compressed_size: 250,
293 file_count: 1,
294 };
295 assert!((stats.compression_ratio() - 0.75).abs() < 0.001);
296 }
297
298 #[test]
299 fn test_embed_stats_totals() {
300 let stats = EmbedStats {
301 templates: CategoryStats {
302 original_size: 1000,
303 compressed_size: 200, file_count: 1,
305 },
306 fonts: CategoryStats {
307 original_size: 2000,
308 compressed_size: 600, file_count: 2,
310 },
311 packages: PackageStats {
312 packages: vec![],
313 original_size: 1000,
314 compressed_size: 200, },
316 dedup: DedupStats {
317 total_files: 4,
318 unique_blobs: 3,
319 duplicate_count: 1,
320 saved_bytes: 100,
321 },
322 };
323 assert_eq!(stats.total_original(), 4000);
325 assert_eq!(stats.total_compressed(), 1000);
326 assert!((stats.compression_ratio() - 0.75).abs() < 0.001);
327 assert_eq!(stats.total_deduplicated(), 900);
329 }
330}