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