use std::path::PathBuf;
use crate::report_helpers;
use super::analyzer::{DepEntry, DepResult, JsonDepResult, UnsupportedLanguage};
const COL_LANG: usize = 10;
const COL_FAN_IN: usize = 6;
const COL_FAN_OUT: usize = 7;
const COL_CYCLE: usize = 5;
const FIXED_WIDTH: usize = 7 + COL_LANG + COL_FAN_IN + COL_FAN_OUT + COL_CYCLE;
pub fn print_report(entries: &[DepEntry], result: &DepResult) {
print!("{}", render_report(entries, result));
}
fn render_report(entries: &[DepEntry], result: &DepResult) -> String {
let mut lines = if entries.is_empty() {
vec!["No source files found for dependency analysis.".to_string()]
} else {
let mut lines = table_lines(entries);
lines.extend(cycle_lines(&result.cycles));
lines
};
lines.extend(unsupported_note(&result.unsupported));
lines.join("\n") + "\n"
}
fn row(path_width: usize, cells: [&dyn std::fmt::Display; 5]) -> String {
let [path, language, fan_in, fan_out, cycle] = cells;
format!(
" {path:<path_width$} {language:>COL_LANG$} {fan_in:>COL_FAN_IN$} {fan_out:>COL_FAN_OUT$} {cycle:>COL_CYCLE$}"
)
}
fn table_lines(entries: &[DepEntry]) -> Vec<String> {
let max_path = report_helpers::max_path_width(entries.iter().map(|e| e.path.as_path()), 4);
let sep = report_helpers::separator((max_path + FIXED_WIDTH).max(72));
let mut lines = vec![
"Dependency Graph".to_string(),
sep.clone(),
row(
max_path,
[&"File", &"Language", &"Fan-In", &"Fan-Out", &"Cycle"],
),
sep.clone(),
];
lines.extend(entries.iter().map(|e| {
let cycle = if e.in_cycle { "yes" } else { "no" };
row(
max_path,
[
&e.path.display(),
&e.language,
&e.fan_in,
&e.fan_out,
&cycle,
],
)
}));
lines.push(sep);
lines
}
fn cycle_lines(cycles: &[Vec<PathBuf>]) -> Vec<String> {
if cycles.is_empty() {
return vec!["No dependency cycles detected.".to_string()];
}
let mut lines = vec![
String::new(),
format!("Dependency cycles: {}", cycles.len()),
];
for (i, cycle) in cycles.iter().enumerate() {
lines.push(format!(" Cycle {} ({} files):", i + 1, cycle.len()));
lines.extend(cycle.iter().map(|p| format!(" {}", p.display())));
}
lines
}
fn unsupported_note(unsupported: &[UnsupportedLanguage]) -> Option<String> {
if unsupported.is_empty() {
return None;
}
let languages: Vec<String> = unsupported
.iter()
.map(|u| format!("{} {}", u.language, u.files))
.collect();
Some(format!(
"Not analysed (unsupported language): {}",
languages.join(", ")
))
}
pub fn print_json(result: &DepResult) -> Result<(), Box<dyn std::error::Error>> {
let out = JsonDepResult::from(result);
report_helpers::print_json_stdout(&out)
}
pub fn print_short(result: &DepResult) {
println!(
"deps files:{} cycles:{} max_fan_out:{} max_fan_in:{} unsupported:{}",
result.entries.len(),
result.cycles.len(),
result.entries.iter().map(|e| e.fan_out).max().unwrap_or(0),
result.entries.iter().map(|e| e.fan_in).max().unwrap_or(0),
result.unsupported.iter().map(|u| u.files).sum::<usize>(),
);
}
pub fn print_terse(result: &DepResult) {
println!("{}", result.cycles.len());
}
#[cfg(test)]
#[path = "report_test.rs"]
mod tests;