1use crate::{
2 CompactScanReport, ScanCacheStats, ScanReport, ScanTermination, SkipKind, SkippedEntry,
3};
4use std::collections::BTreeMap;
5use std::fmt;
6
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ScanSummary {
15 pub selected_files: usize,
16 pub selected_bytes: u64,
17 pub hashed_files: usize,
18 pub binary_checked_files: usize,
19 pub recorded_skips: usize,
20 pub skipped_by_kind: BTreeMap<SkipKind, usize>,
21 pub warnings: usize,
22 pub ignore_sources: usize,
23 pub complete: bool,
24 pub termination: Option<ScanTermination>,
25 pub portable: bool,
26 pub cache: ScanCacheStats,
27}
28
29impl ScanSummary {
30 #[allow(clippy::too_many_arguments)]
31 fn from_parts(
32 selected_files: usize,
33 selected_bytes: impl Iterator<Item = u64>,
34 hashed_files: usize,
35 binary_checked_files: usize,
36 skipped: &[SkippedEntry],
37 warnings: usize,
38 ignore_sources: usize,
39 complete: bool,
40 termination: Option<ScanTermination>,
41 portable: bool,
42 cache: ScanCacheStats,
43 ) -> Self {
44 let mut skipped_by_kind = BTreeMap::new();
45 for entry in skipped {
46 *skipped_by_kind.entry(entry.kind).or_default() += 1;
47 }
48 Self {
49 selected_files,
50 selected_bytes: selected_bytes.fold(0_u64, u64::saturating_add),
51 hashed_files,
52 binary_checked_files,
53 recorded_skips: skipped.len(),
54 skipped_by_kind,
55 warnings,
56 ignore_sources,
57 complete,
58 termination,
59 portable,
60 cache,
61 }
62 }
63}
64
65impl ScanReport {
66 #[must_use]
68 pub fn summary(&self) -> ScanSummary {
69 ScanSummary::from_parts(
70 self.files.len(),
71 self.files.iter().map(|file| file.bytes),
72 self.files
73 .iter()
74 .filter(|file| file.content_hash.is_some())
75 .count(),
76 self.files.iter().filter(|file| file.binary_checked).count(),
77 &self.skipped,
78 self.warnings.len(),
79 self.ignore_sources.len(),
80 self.complete,
81 self.termination,
82 self.portable,
83 self.cache,
84 )
85 }
86}
87
88impl CompactScanReport {
89 #[must_use]
91 pub fn summary(&self) -> ScanSummary {
92 ScanSummary::from_parts(
93 self.files.len(),
94 self.files.iter().map(|file| file.bytes),
95 self.files
96 .iter()
97 .filter(|file| file.content_hash().is_some())
98 .count(),
99 self.files
100 .iter()
101 .filter(|file| {
102 file.content
103 .as_deref()
104 .is_some_and(|content| content.binary_checked)
105 })
106 .count(),
107 &self.skipped,
108 self.warnings.len(),
109 self.ignore_sources.len(),
110 self.complete,
111 self.termination,
112 self.portable,
113 self.cache,
114 )
115 }
116}
117
118impl fmt::Display for ScanSummary {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(
121 formatter,
122 "files={} bytes={} hashed={} binary_checked={} skipped={} warnings={} \
123 ignore_sources={} complete={} portable={}",
124 self.selected_files,
125 self.selected_bytes,
126 self.hashed_files,
127 self.binary_checked_files,
128 self.recorded_skips,
129 self.warnings,
130 self.ignore_sources,
131 self.complete,
132 self.portable,
133 )?;
134 if let Some(termination) = self.termination {
135 write!(formatter, " termination={termination:?}")?;
136 }
137 if self.cache != ScanCacheStats::default() {
138 write!(
139 formatter,
140 " cache_reused_hashes={} cache_content_reads={} cache_fingerprint_reads={}",
141 self.cache.reused_hashes, self.cache.content_reads, self.cache.fingerprint_reads,
142 )?;
143 }
144 Ok(())
145 }
146}