use serde_json;
use std::fmt::Write;
use crate::tdg::{Comparison, Grade, ProjectScore, TdgScore};
#[must_use]
pub fn format_human(score: &TdgScore) -> String {
let mut output = String::new();
writeln!(
output,
"╭─────────────────────────────────────────────────╮"
)
.unwrap();
if let Some(path) = &score.file_path {
writeln!(
output,
"│ TDG Score Report: {:30} │",
path.display()
.to_string()
.chars()
.take(30)
.collect::<String>()
)
.unwrap();
} else {
writeln!(output, "│ TDG Score Report: Code Analysis │").unwrap();
}
writeln!(
output,
"├─────────────────────────────────────────────────┤"
)
.unwrap();
writeln!(
output,
"│ Overall Score: {:.1}/100 ({}) │",
score.total, score.grade
)
.unwrap();
writeln!(
output,
"│ Language: {} (confidence: {:.0}%) │",
score.language,
score.confidence * 100.0
)
.unwrap();
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(
output,
"│ 📊 Breakdown: │"
)
.unwrap();
writeln!(
output,
"│ ├─ Structural: {:4.1}/25 {} │",
score.structural_complexity,
progress_bar(score.structural_complexity, 25.0, 10)
)
.unwrap();
writeln!(
output,
"│ ├─ Semantic: {:4.1}/20 {} │",
score.semantic_complexity,
progress_bar(score.semantic_complexity, 20.0, 10)
)
.unwrap();
writeln!(
output,
"│ ├─ Duplication: {:4.1}/20 {} │",
score.duplication_ratio,
progress_bar(score.duplication_ratio, 20.0, 10)
)
.unwrap();
writeln!(
output,
"│ ├─ Coupling: {:4.1}/15 {} │",
score.coupling_score,
progress_bar(score.coupling_score, 15.0, 10)
)
.unwrap();
writeln!(
output,
"│ ├─ Documentation: {:4.1}/10 {} │",
score.doc_coverage,
progress_bar(score.doc_coverage, 10.0, 10)
)
.unwrap();
writeln!(
output,
"│ └─ Consistency: {:4.1}/10 {} │",
score.consistency_score,
progress_bar(score.consistency_score, 10.0, 10)
)
.unwrap();
if !score.penalties_applied.is_empty() {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(
output,
"│ 🔍 Issues Found: │"
)
.unwrap();
for penalty in &score.penalties_applied {
let issue_line = format!(" • {}", penalty.issue);
let truncated = if issue_line.len() > 45 {
format!("{}...", issue_line.chars().take(42).collect::<String>())
} else {
issue_line
};
writeln!(output, "│ {truncated:47} │").unwrap();
}
}
if score.total >= 90.0 {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ ✨ Excellent code quality! No major issues. │").unwrap();
} else if score.total >= 75.0 {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ 👍 Good code quality with minor improvements. │").unwrap();
} else if score.total >= 60.0 {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ ⚠️ Code needs improvement in several areas. │").unwrap();
} else {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ 🔴 Code requires significant refactoring. │").unwrap();
}
writeln!(
output,
"╰─────────────────────────────────────────────────╯"
)
.unwrap();
output
}
#[must_use]
pub fn format_json(score: &TdgScore) -> String {
serde_json::to_string_pretty(score).unwrap_or_else(|_| "{}".to_string())
}
#[must_use]
pub fn format_markdown(score: &TdgScore) -> String {
let mut output = String::new();
writeln!(output, "# TDG Score Report").unwrap();
writeln!(output).unwrap();
if let Some(path) = &score.file_path {
writeln!(output, "**File:** `{}`", path.display()).unwrap();
}
writeln!(
output,
"**Language:** {} ({}% confidence)",
score.language,
(score.confidence * 100.0) as u8
)
.unwrap();
writeln!(
output,
"**Overall Score:** {:.1}/100 ({})",
score.total, score.grade
)
.unwrap();
writeln!(output).unwrap();
writeln!(output, "## Score Breakdown").unwrap();
writeln!(output).unwrap();
writeln!(output, "| Metric | Score | Max | Percentage |").unwrap();
writeln!(output, "|--------|-------|-----|------------|").unwrap();
writeln!(
output,
"| Structural Complexity | {:.1} | 25.0 | {:.1}% |",
score.structural_complexity,
(score.structural_complexity / 25.0) * 100.0
)
.unwrap();
writeln!(
output,
"| Semantic Complexity | {:.1} | 20.0 | {:.1}% |",
score.semantic_complexity,
(score.semantic_complexity / 20.0) * 100.0
)
.unwrap();
writeln!(
output,
"| Code Duplication | {:.1} | 20.0 | {:.1}% |",
score.duplication_ratio,
(score.duplication_ratio / 20.0) * 100.0
)
.unwrap();
writeln!(
output,
"| Coupling | {:.1} | 15.0 | {:.1}% |",
score.coupling_score,
(score.coupling_score / 15.0) * 100.0
)
.unwrap();
writeln!(
output,
"| Documentation | {:.1} | 10.0 | {:.1}% |",
score.doc_coverage,
(score.doc_coverage / 10.0) * 100.0
)
.unwrap();
writeln!(
output,
"| Consistency | {:.1} | 10.0 | {:.1}% |",
score.consistency_score,
(score.consistency_score / 10.0) * 100.0
)
.unwrap();
writeln!(output).unwrap();
if !score.penalties_applied.is_empty() {
writeln!(output, "## Issues Found").unwrap();
writeln!(output).unwrap();
for penalty in &score.penalties_applied {
writeln!(
output,
"- **{}**: {} (-{:.1} points)",
format_metric_name(&penalty.source_metric),
penalty.issue,
penalty.amount
)
.unwrap();
}
writeln!(output).unwrap();
}
writeln!(output, "## Grade Description").unwrap();
writeln!(output).unwrap();
writeln!(output, "{}", grade_description(score.grade)).unwrap();
output
}
#[must_use]
pub fn format_comparison(comparison: &Comparison) -> String {
let mut output = String::new();
writeln!(
output,
"╭─────────────────────────────────────────────────╮"
)
.unwrap();
let name1 = comparison
.source1
.file_path
.as_ref().map_or_else(|| "source1".to_string(), |p| p.file_name().unwrap().to_string_lossy().to_string());
let name2 = comparison
.source2
.file_path
.as_ref().map_or_else(|| "source2".to_string(), |p| p.file_name().unwrap().to_string_lossy().to_string());
let header = format!("TDG Comparison: {name1} vs {name2}");
let truncated_header = if header.len() > 45 {
format!("{}...", header.chars().take(42).collect::<String>())
} else {
header
};
writeln!(output, "│ {truncated_header:47} │").unwrap();
writeln!(
output,
"├─────────────────────────────────────────────────┤"
)
.unwrap();
writeln!(
output,
"│ {:>8} {:>8} {:>4} │",
name1, name2, "Δ"
)
.unwrap();
writeln!(
output,
"│ Overall Score: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.total, comparison.source2.total, comparison.delta
)
.unwrap();
writeln!(
output,
"│ Grade: {:>8} {:>8} {:>4} │",
comparison.source1.grade,
comparison.source2.grade,
grade_delta(comparison.source1.grade, comparison.source2.grade)
)
.unwrap();
writeln!(
output,
"│ Confidence: {:>7.0}% {:>7.0}% │",
comparison.source1.confidence * 100.0,
comparison.source2.confidence * 100.0
)
.unwrap();
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(
output,
"│ Structural: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.structural_complexity,
comparison.source2.structural_complexity,
comparison.source2.structural_complexity - comparison.source1.structural_complexity
)
.unwrap();
writeln!(
output,
"│ Semantic: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.semantic_complexity,
comparison.source2.semantic_complexity,
comparison.source2.semantic_complexity - comparison.source1.semantic_complexity
)
.unwrap();
writeln!(
output,
"│ Duplication: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.duplication_ratio,
comparison.source2.duplication_ratio,
comparison.source2.duplication_ratio - comparison.source1.duplication_ratio
)
.unwrap();
writeln!(
output,
"│ Coupling: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.coupling_score,
comparison.source2.coupling_score,
comparison.source2.coupling_score - comparison.source1.coupling_score
)
.unwrap();
writeln!(
output,
"│ Documentation: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.doc_coverage,
comparison.source2.doc_coverage,
comparison.source2.doc_coverage - comparison.source1.doc_coverage
)
.unwrap();
writeln!(
output,
"│ Consistency: {:8.1} {:8.1} {:>+5.1} │",
comparison.source1.consistency_score,
comparison.source2.consistency_score,
comparison.source2.consistency_score - comparison.source1.consistency_score
)
.unwrap();
writeln!(
output,
"│ │"
)
.unwrap();
let winner_text = format!(
"Winner: {} ({:.1}% improvement)",
comparison.winner,
comparison.improvement_percentage.abs()
);
let truncated_winner = if winner_text.len() > 47 {
format!("{}...", winner_text.chars().take(44).collect::<String>())
} else {
winner_text
};
writeln!(output, "│ {truncated_winner:47} │").unwrap();
if !comparison.improvements.is_empty() {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ Key Improvements: │").unwrap();
for improvement in &comparison.improvements {
let improvement_line = format!(" • {improvement}");
let truncated = if improvement_line.len() > 45 {
format!(
"{}...",
improvement_line.chars().take(42).collect::<String>()
)
} else {
improvement_line
};
writeln!(output, "│ {truncated:47} │").unwrap();
}
}
if !comparison.regressions.is_empty() {
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ Minor Regressions: │").unwrap();
for regression in &comparison.regressions {
let regression_line = format!(" • {regression}");
let truncated = if regression_line.len() > 45 {
format!(
"{}...",
regression_line.chars().take(42).collect::<String>()
)
} else {
regression_line
};
writeln!(output, "│ {truncated:47} │").unwrap();
}
}
writeln!(
output,
"╰─────────────────────────────────────────────────╯"
)
.unwrap();
output
}
#[must_use]
pub fn format_project(project: &ProjectScore) -> String {
let mut output = String::new();
writeln!(
output,
"╭─────────────────────────────────────────────────╮"
)
.unwrap();
writeln!(output, "│ Project TDG Score Report │").unwrap();
writeln!(
output,
"├─────────────────────────────────────────────────┤"
)
.unwrap();
writeln!(
output,
"│ Average Score: {:.1}/100 ({}) │",
project.average_score, project.average_grade
)
.unwrap();
writeln!(
output,
"│ Total Files: {} │",
project.total_files
)
.unwrap();
writeln!(
output,
"│ │"
)
.unwrap();
writeln!(output, "│ Language Distribution: │").unwrap();
for (language, count) in &project.language_distribution {
let percentage = (*count as f32 / project.total_files as f32) * 100.0;
writeln!(
output,
"│ ├─ {:12}: {:3} files ({:4.1}%) │",
language.to_string(),
count,
percentage
)
.unwrap();
}
writeln!(
output,
"│ │"
)
.unwrap();
let mut files_by_grade: std::collections::BTreeMap<Grade, usize> =
std::collections::BTreeMap::new();
for score in &project.files {
*files_by_grade.entry(score.grade).or_insert(0) += 1;
}
writeln!(output, "│ Grade Distribution: │").unwrap();
for (grade, count) in files_by_grade {
let percentage = (count as f32 / project.total_files as f32) * 100.0;
writeln!(
output,
"│ ├─ {grade}: {count:3} files ({percentage:4.1}%) │"
)
.unwrap();
}
writeln!(
output,
"╰─────────────────────────────────────────────────╯"
)
.unwrap();
output
}
fn progress_bar(value: f32, max_value: f32, width: usize) -> String {
let ratio = (value / max_value).clamp(0.0, 1.0);
let filled = (ratio * width as f32) as usize;
let empty = width - filled;
format!("{}{}", "█".repeat(filled), "░".repeat(empty))
}
fn grade_delta(from: Grade, to: Grade) -> String {
let from_val = grade_to_number(from);
let to_val = grade_to_number(to);
let delta = to_val - from_val;
if delta > 0 {
format!("↑{delta}")
} else if delta < 0 {
format!("↓{}", delta.abs())
} else {
"=".to_string()
}
}
fn grade_to_number(grade: Grade) -> i32 {
match grade {
Grade::APLus => 11,
Grade::A => 10,
Grade::AMinus => 9,
Grade::BPlus => 8,
Grade::B => 7,
Grade::BMinus => 6,
Grade::CPlus => 5,
Grade::C => 4,
Grade::CMinus => 3,
Grade::D => 2,
Grade::F => 1,
}
}
fn format_metric_name(metric: &crate::tdg::MetricCategory) -> &'static str {
match metric {
crate::tdg::MetricCategory::StructuralComplexity => "Structural Complexity",
crate::tdg::MetricCategory::SemanticComplexity => "Semantic Complexity",
crate::tdg::MetricCategory::Duplication => "Code Duplication",
crate::tdg::MetricCategory::Coupling => "Coupling",
crate::tdg::MetricCategory::Documentation => "Documentation",
crate::tdg::MetricCategory::Consistency => "Consistency",
}
}
fn grade_description(grade: Grade) -> &'static str {
match grade {
Grade::APLus => {
"**A+** (95-100): Exceptional code quality. Production-ready with excellent practices."
}
Grade::A => "**A** (90-94): High quality code with minor room for improvement.",
Grade::AMinus => "**A-** (85-89): Good code quality with some areas for enhancement.",
Grade::BPlus => "**B+** (80-84): Above average code that meets most quality standards.",
Grade::B => "**B** (75-79): Average code quality with moderate technical debt.",
Grade::BMinus => "**B-** (70-74): Below average with noticeable issues to address.",
Grade::CPlus => "**C+** (65-69): Poor code quality requiring significant attention.",
Grade::C => "**C** (60-64): Very poor quality with major issues throughout.",
Grade::CMinus => "**C-** (55-59): Problematic code that needs extensive refactoring.",
Grade::D => "**D** (50-54): Severely problematic code with critical issues.",
Grade::F => "**F** (0-49): Failing grade indicating code that is not production-ready.",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tdg::*;
use std::path::PathBuf;
#[test]
fn test_format_human() {
let mut score = TdgScore::default();
score.total = 85.5;
score.grade = Grade::AMinus;
score.language = Language::Rust;
score.confidence = 1.0;
score.file_path = Some(PathBuf::from("src/test.rs"));
let output = format_human(&score);
assert!(output.contains("85.5/100"));
assert!(output.contains("A-"));
assert!(output.contains("Rust"));
assert!(output.contains("src/test.rs"));
assert!(output.contains("📊 Breakdown"));
}
#[test]
fn test_format_json() {
let score = TdgScore::default();
let output = format_json(&score);
assert!(output.contains("structural_complexity"));
assert!(output.contains("semantic_complexity"));
assert!(output.contains("total"));
assert!(output.contains("grade"));
}
#[test]
fn test_format_markdown() {
let mut score = TdgScore::default();
score.total = 75.0;
score.grade = Grade::B;
let output = format_markdown(&score);
assert!(output.contains("# TDG Score Report"));
assert!(output.contains("## Score Breakdown"));
assert!(output.contains("| Metric | Score | Max"));
assert!(output.contains("**B** (75-79)"));
}
#[test]
fn test_progress_bar() {
assert_eq!(progress_bar(5.0, 10.0, 10), "█████░░░░░");
assert_eq!(progress_bar(0.0, 10.0, 10), "░░░░░░░░░░");
assert_eq!(progress_bar(10.0, 10.0, 10), "██████████");
assert_eq!(progress_bar(15.0, 10.0, 10), "██████████"); }
#[test]
fn test_grade_delta() {
assert_eq!(grade_delta(Grade::B, Grade::A), "↑3");
assert_eq!(grade_delta(Grade::A, Grade::B), "↓3");
assert_eq!(grade_delta(Grade::B, Grade::B), "=");
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}