use anyhow::Result;
use std::path::Path;
use walkdir::WalkDir;
#[derive(Debug, Clone, Default)]
pub struct ProjectComplexity {
pub file_count: usize,
pub total_lines: usize,
pub function_count: usize,
pub module_count: usize,
pub test_count: usize,
pub doc_lines: usize,
pub dependency_count: usize,
}
impl ProjectComplexity {
pub fn doc_coverage(&self) -> f64 {
if self.total_lines == 0 {
0.0
} else {
self.doc_lines as f64 / self.total_lines as f64
}
}
pub fn test_coverage(&self) -> f64 {
if self.function_count == 0 {
0.0
} else {
self.test_count as f64 / self.function_count as f64
}
}
pub fn complexity_factor(&self) -> f64 {
(self.total_lines as f64 / 1000.0).min(10.0)
}
}
pub async fn analyze_project_complexity(path: &Path) -> Result<ProjectComplexity> {
let mut complexity = ProjectComplexity::default();
let cargo_toml = path.join("Cargo.toml");
if cargo_toml.exists() {
if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
if let Ok(toml) = content.parse::<toml::Value>() {
if let Some(deps) = toml.get("dependencies").and_then(|d| d.as_table()) {
complexity.dependency_count = deps.len();
}
}
}
}
for entry in WalkDir::new(path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("rs"))
{
complexity.file_count += 1;
if let Ok(content) = std::fs::read_to_string(entry.path()) {
complexity.total_lines += content.lines().count();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("///") || trimmed.starts_with("//!") {
complexity.doc_lines += 1;
}
if trimmed.starts_with("fn ") || trimmed.contains("fn ") {
complexity.function_count += 1;
}
if trimmed.starts_with("mod ") {
complexity.module_count += 1;
}
if trimmed.contains("#[test]") || trimmed.contains("#[cfg(test)]") {
complexity.test_count += 1;
}
}
}
}
if complexity.file_count == 0
&& path.is_file()
&& path.extension().and_then(|s| s.to_str()) == Some("rs")
{
complexity.file_count = 1;
if let Ok(content) = std::fs::read_to_string(path) {
complexity.total_lines = content.lines().count();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("fn ") || trimmed.contains("fn ") {
complexity.function_count += 1;
}
if trimmed.starts_with("///") || trimmed.starts_with("//!") {
complexity.doc_lines += 1;
}
}
}
}
Ok(complexity)
}