use std::path::Path;
use serde::Serialize;
use unicode_width::UnicodeWidthStr;
pub trait PerFunctionRow {
fn name(&self) -> &str;
fn complexity(&self) -> usize;
fn level_str(&self) -> &str;
}
pub trait PerFunctionFile {
type Row: PerFunctionRow;
fn path_str(&self) -> String;
fn rows(&self) -> &[Self::Row];
}
pub fn print_per_function_breakdown<F: PerFunctionFile>(title: &str, files: &[F]) {
if files.is_empty() {
println!("No recognized source files found.");
return;
}
let sep = separator(78);
println!("{title}");
println!("{sep}");
for f in files {
println!();
println!("{}:", f.path_str());
let rows = f.rows();
let max_name_len = rows
.iter()
.map(|r| display_width(r.name()))
.max()
.unwrap_or(10)
.max(10);
for r in rows {
println!(
" {} {:>5} {}",
pad_to(r.name(), max_name_len),
r.complexity(),
r.level_str(),
);
}
}
println!("{sep}");
}
pub fn display_width(s: &str) -> usize {
UnicodeWidthStr::width(s)
}
pub fn pad_to(s: &str, width: usize) -> String {
let padding = width.saturating_sub(UnicodeWidthStr::width(s));
format!("{s}{}", " ".repeat(padding))
}
pub fn github_annotation(level: &str, file: &str, line: usize, title: &str, message: &str) {
println!("::{level} file={file},line={line},title={title}::{message}");
}
pub fn max_path_width<'a>(paths: impl Iterator<Item = &'a Path>, min: usize) -> usize {
paths
.map(|p| display_width(&p.display().to_string()))
.max()
.unwrap_or(min)
.max(min)
}
pub fn separator(width: usize) -> String {
"\u{2500}".repeat(width)
}
pub fn print_json_stdout(value: &impl Serialize) -> Result<(), Box<dyn std::error::Error>> {
println!("{}", serde_json::to_string_pretty(value)?);
Ok(())
}
pub fn output_results<T>(
results: &mut Vec<T>,
top: usize,
json: bool,
print_json_fn: impl FnOnce(&[T]) -> Result<(), Box<dyn std::error::Error>>,
print_report_fn: impl FnOnce(&[T]),
) -> Result<(), Box<dyn std::error::Error>> {
results.truncate(top);
if json {
print_json_fn(results)
} else {
print_report_fn(results);
Ok(())
}
}
#[cfg(test)]
#[path = "report_helpers_test.rs"]
mod tests;