1use crate::format;
7use crate::report::ReportData;
8
9pub fn print_overview(data: &ReportData, color: bool) {
19 eprintln!(
20 "{}",
21 format::format_section_header("Project Overview", color)
22 );
23
24 if !data.language_breakdown.is_empty() {
26 let total = data.total_files.max(1) as f64;
27 for lc in &data.language_breakdown {
28 let fraction = lc.count as f64 / total;
29 eprintln!(
30 "{}",
31 format::format_bar_chart(
32 &lc.language.to_string(),
33 lc.count,
34 fraction,
35 "files",
36 color,
37 ),
38 );
39 }
40 eprintln!();
41 }
42
43 let dep_detail = format_dependency_detail(data);
45 eprintln!(
46 " {} files, {}",
47 format::format_number(data.total_files as u64),
48 dep_detail,
49 );
50 eprintln!();
51}
52
53fn format_dependency_detail(data: &ReportData) -> String {
57 let total = data.total_dependencies;
58 if data.dependency_breakdown.is_empty() {
59 return format!("{} packages", format::format_number(total as u64));
60 }
61
62 let parts: Vec<String> = data
63 .dependency_breakdown
64 .iter()
65 .map(|ec| format!("{} {}", ec.count, ec.label))
66 .collect();
67
68 format!(
69 "{} packages ({})",
70 format::format_number(total as u64),
71 parts.join(", "),
72 )
73}
74
75#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::report::{EcosystemCount, LanguageCount};
83 use seshat_core::Language;
84
85 fn make_report_data(
86 language_breakdown: Vec<LanguageCount>,
87 total_files: usize,
88 total_dependencies: usize,
89 dependency_breakdown: Vec<EcosystemCount>,
90 ) -> ReportData {
91 ReportData {
92 language_breakdown,
93 total_files,
94 total_dependencies,
95 dependency_breakdown,
96 conventions: vec![],
97 files_discovered: total_files,
98 files_parsed: total_files,
99 nodes_persisted: 0,
100 edges_persisted: 0,
101 manifests_analyzed: 0,
102 docs_ingested: 0,
103 db_path: std::path::PathBuf::from("/tmp/test.db"),
104 db_size: 0,
105 elapsed: std::time::Duration::from_secs(1),
106 excluded_submodules: vec![],
107 submodules_excluded_by_flag: false,
108 }
109 }
110
111 #[test]
112 fn test_format_dependency_detail_empty() {
113 let data = make_report_data(vec![], 0, 0, vec![]);
114 assert_eq!(format_dependency_detail(&data), "0 packages");
115 }
116
117 #[test]
118 fn test_format_dependency_detail_single_ecosystem() {
119 let data = make_report_data(
120 vec![],
121 10,
122 42,
123 vec![EcosystemCount {
124 label: "cargo".to_owned(),
125 count: 42,
126 }],
127 );
128 assert_eq!(format_dependency_detail(&data), "42 packages (42 cargo)");
129 }
130
131 #[test]
132 fn test_format_dependency_detail_multiple_ecosystems() {
133 let data = make_report_data(
134 vec![],
135 10,
136 127,
137 vec![
138 EcosystemCount {
139 label: "npm".to_owned(),
140 count: 98,
141 },
142 EcosystemCount {
143 label: "pip".to_owned(),
144 count: 29,
145 },
146 ],
147 );
148 assert_eq!(
149 format_dependency_detail(&data),
150 "127 packages (98 npm, 29 pip)",
151 );
152 }
153
154 #[test]
155 fn test_format_dependency_detail_large_number() {
156 let data = make_report_data(
157 vec![],
158 10,
159 1500,
160 vec![EcosystemCount {
161 label: "npm".to_owned(),
162 count: 1500,
163 }],
164 );
165 assert_eq!(format_dependency_detail(&data), "1,500 packages (1500 npm)",);
166 }
167
168 #[test]
169 fn test_print_overview_does_not_panic() {
170 let data = make_report_data(
171 vec![
172 LanguageCount {
173 language: Language::Rust,
174 count: 40,
175 },
176 LanguageCount {
177 language: Language::Python,
178 count: 20,
179 },
180 ],
181 60,
182 127,
183 vec![
184 EcosystemCount {
185 label: "cargo".to_owned(),
186 count: 98,
187 },
188 EcosystemCount {
189 label: "pip".to_owned(),
190 count: 29,
191 },
192 ],
193 );
194 print_overview(&data, false);
196 print_overview(&data, true);
197 }
198
199 #[test]
200 fn test_print_overview_empty_data() {
201 let data = make_report_data(vec![], 0, 0, vec![]);
202 print_overview(&data, false);
204 }
205}