pub mod rust_analyzer; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectInfo { pub name: String, pub description: Option, pub version: String, pub authors: Vec, pub license: Option, pub dependencies: HashMap, pub modules: Vec, pub structure: ProjectStructure, pub metrics: ProjectMetrics, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModuleInfo { pub name: String, pub path: PathBuf, pub functions: Vec, pub structs: Vec, pub enums: Vec, pub traits: Vec, pub docs: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FunctionInfo { pub name: String, pub visibility: String, pub is_async: bool, pub parameters: Vec, pub return_type: Option, pub docs: Option, pub line_number: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Parameter { pub name: String, pub param_type: String, pub is_mutable: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StructInfo { pub name: String, pub visibility: String, pub fields: Vec, pub docs: Option, pub line_number: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldInfo { pub name: String, pub field_type: String, pub visibility: String, pub docs: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EnumInfo { pub name: String, pub visibility: String, pub variants: Vec, pub docs: Option, pub line_number: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VariantInfo { pub name: String, pub fields: Vec, pub docs: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TraitInfo { pub name: String, pub visibility: String, pub methods: Vec, pub docs: Option, pub line_number: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectStructure { pub directories: Vec, pub files: Vec, pub dependency_graph: HashMap>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DirectoryInfo { pub name: String, pub path: PathBuf, pub file_count: usize, pub subdirectories: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FileInfo { pub name: String, pub path: PathBuf, pub language: String, pub lines_of_code: usize, pub is_test: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectMetrics { pub total_lines: usize, pub total_files: usize, pub test_files: usize, pub dependency_count: usize, pub complexity_score: f32, pub test_coverage: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApiInfo { pub modules: Vec, pub public_functions: Vec, pub public_structs: Vec, pub public_enums: Vec, pub public_traits: Vec, } pub struct CodeAnalyzer { rust_analyzer: rust_analyzer::RustAnalyzer, } impl CodeAnalyzer { pub fn new() -> Self { Self { rust_analyzer: rust_analyzer::RustAnalyzer::new(), } } pub fn analyze_project(&self, path: &Path) -> Result { println!(" 🔍 Analyzing project at: {}", path.display()); // Check if this is a Rust project let cargo_toml = path.join("Cargo.toml"); if cargo_toml.exists() { return self.rust_analyzer.analyze_project(path); } // For now, only support Rust projects anyhow::bail!("Only Rust projects are currently supported"); } pub fn analyze_api(&self, path: &Path) -> Result { println!(" 📚 Analyzing API at: {}", path.display()); let project_info = self.analyze_project(path.parent().unwrap_or(path))?; // Extract only public items let mut public_functions = Vec::new(); let mut public_structs = Vec::new(); let mut public_enums = Vec::new(); let mut public_traits = Vec::new(); for module in &project_info.modules { for func in &module.functions { if func.visibility == "pub" { public_functions.push(func.clone()); } } for struct_info in &module.structs { if struct_info.visibility == "pub" { public_structs.push(struct_info.clone()); } } for enum_info in &module.enums { if enum_info.visibility == "pub" { public_enums.push(enum_info.clone()); } } for trait_info in &module.traits { if trait_info.visibility == "pub" { public_traits.push(trait_info.clone()); } } } Ok(ApiInfo { modules: project_info.modules, public_functions, public_structs, public_enums, public_traits, }) } pub fn analyze_structure(&self, path: &Path, include_deps: bool) -> Result { println!(" 🏗️ Analyzing structure at: {}", path.display()); let mut directories = Vec::new(); let mut files = Vec::new(); let mut dependency_graph = HashMap::new(); self.walk_directory(path, &mut directories, &mut files)?; if include_deps { dependency_graph = self.analyze_dependencies(path)?; } Ok(ProjectStructure { directories, files, dependency_graph, }) } fn walk_directory( &self, path: &Path, directories: &mut Vec, files: &mut Vec, ) -> Result<()> { use walkdir::WalkDir; let walker = WalkDir::new(path) .into_iter() .filter_entry(|e| { let name = e.file_name().to_string_lossy(); // Skip hidden files and common build/cache directories !name.starts_with('.') && name != "target" && name != "node_modules" && name != "dist" }); for entry in walker { let entry = entry?; let path = entry.path(); let relative_path = path.strip_prefix(path.ancestors().last().unwrap())?; if entry.file_type().is_dir() { let file_count = std::fs::read_dir(path)? .filter_map(|e| e.ok()) .filter(|e| e.file_type().map(|ft| ft.is_file()).unwrap_or(false)) .count(); let subdirectories = std::fs::read_dir(path)? .filter_map(|e| e.ok()) .filter(|e| e.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) .map(|e| e.file_name().to_string_lossy().to_string()) .collect(); directories.push(DirectoryInfo { name: path.file_name().unwrap().to_string_lossy().to_string(), path: relative_path.to_path_buf(), file_count, subdirectories, }); } else if entry.file_type().is_file() { let language = self.detect_language(path); let lines_of_code = self.count_lines(path)?; let is_test = self.is_test_file(path); files.push(FileInfo { name: path.file_name().unwrap().to_string_lossy().to_string(), path: relative_path.to_path_buf(), language, lines_of_code, is_test, }); } } Ok(()) } fn detect_language(&self, path: &Path) -> String { match path.extension().and_then(|s| s.to_str()) { Some("rs") => "rust".to_string(), Some("py") => "python".to_string(), Some("js") => "javascript".to_string(), Some("ts") => "typescript".to_string(), Some("md") => "markdown".to_string(), Some("toml") => "toml".to_string(), Some("json") => "json".to_string(), Some("yaml") | Some("yml") => "yaml".to_string(), _ => "unknown".to_string(), } } fn count_lines(&self, path: &Path) -> Result { let content = std::fs::read_to_string(path)?; Ok(content.lines().count()) } fn is_test_file(&self, path: &Path) -> bool { let filename = path.file_name().unwrap().to_string_lossy(); filename.contains("test") || filename.starts_with("test_") || path.to_string_lossy().contains("/tests/") } fn analyze_dependencies(&self, _path: &Path) -> Result>> { // For now, just return empty dependencies // TODO: Implement actual dependency analysis Ok(HashMap::new()) } }