use anyhow::Result;
use colored::*;
use serde_json;
use crate::cli::OutputFormat;
use crate::detector::Analysis;
pub struct Reporter;
impl Reporter {
pub fn new() -> Self {
Self
}
pub fn generate_report(&self, analysis: &Analysis, format: OutputFormat) -> Result<()> {
match format {
OutputFormat::Table => self.print_table_report(analysis),
OutputFormat::Json => self.print_json_report(analysis),
OutputFormat::Csv => self.print_csv_report(analysis),
}
}
fn print_table_report(&self, analysis: &Analysis) -> Result<()> {
println!("{}", "Language Detection Report".bold().underline());
println!();
println!("📊 {}: {}", "Total Files".bold(), analysis.total_files.to_string().cyan());
println!("💾 {}: {}", "Total Size".bold(), self.format_bytes(analysis.total_bytes).cyan());
println!();
if analysis.stats.is_empty() {
println!("{}", "No languages detected!".yellow());
return Ok(());
}
println!("{:<20} {:<8} {:<12} {:<8} {:<10}",
"Language".bold(),
"Files".bold(),
"Percentage".bold(),
"Size".bold(),
"Bar".bold()
);
println!("{}", "─".repeat(70).dimmed());
for stat in &analysis.stats {
let bar = self.create_progress_bar(stat.percentage, 20);
let color = self.parse_hex_color(&stat.color);
println!("{:<20} {:<8} {:<12} {:<8} {}",
stat.language.color(color),
stat.file_count.to_string().cyan(),
format!("{:.1}%", stat.percentage).green(),
self.format_bytes(stat.total_bytes).magenta(),
bar
);
}
if !analysis.unknown_files.is_empty() {
println!();
println!("{} {} unknown files",
"⚠️".yellow(),
analysis.unknown_files.len().to_string().yellow()
);
}
Ok(())
}
fn print_json_report(&self, analysis: &Analysis) -> Result<()> {
let json_output = serde_json::json!({
"summary": {
"total_files": analysis.total_files,
"total_bytes": analysis.total_bytes,
"languages_detected": analysis.stats.len()
},
"languages": analysis.stats.iter().map(|stat| {
serde_json::json!({
"name": stat.language,
"file_count": stat.file_count,
"percentage": stat.percentage,
"total_bytes": stat.total_bytes,
"color": stat.color,
"files": stat.files
})
}).collect::<Vec<_>>(),
"unknown_files": analysis.unknown_files
});
println!("{}", serde_json::to_string_pretty(&json_output)?);
Ok(())
}
fn print_csv_report(&self, analysis: &Analysis) -> Result<()> {
println!("Language,Files,Percentage,Bytes");
for stat in &analysis.stats {
println!("{},{},{:.2},{}",
stat.language,
stat.file_count,
stat.percentage,
stat.total_bytes
);
}
if !analysis.unknown_files.is_empty() {
println!("Unknown,{},0,0", analysis.unknown_files.len());
}
Ok(())
}
fn create_progress_bar(&self, percentage: f64, width: usize) -> String {
let filled = ((percentage / 100.0) * width as f64) as usize;
let filled = filled.min(width);
let bar = "█".repeat(filled) + &"░".repeat(width - filled);
format!("{} {:.1}%", bar.green(), percentage)
}
fn format_bytes(&self, bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
fn parse_hex_color(&self, hex: &str) -> colored::Color {
match hex {
"#CE422B" => colored::Color::Red,
"#3776AB" => colored::Color::Blue,
"#F7DF1E" => colored::Color::Yellow,
"#3178C6" => colored::Color::Cyan,
"#00ADD8" => colored::Color::Cyan,
"#ED8B00" => colored::Color::Yellow,
"#A8B9CC" => colored::Color::White,
"#00599C" => colored::Color::Blue,
"#239120" => colored::Color::Green,
"#777BB4" => colored::Color::Magenta,
"#CC342D" => colored::Color::Red,
"#FA7343" => colored::Color::Yellow,
"#7F52FF" => colored::Color::Magenta,
"#DC322F" => colored::Color::Red,
"#89E051" => colored::Color::Green,
"#E34F26" => colored::Color::Red,
"#1572B6" => colored::Color::Blue,
"#CF649A" => colored::Color::Magenta,
"#4FC08D" => colored::Color::Green,
"#61DAFB" => colored::Color::Cyan,
_ => colored::Color::White,
}
}
}