syncable_cli/analyzer/tool_management/
status.rs

1use crate::analyzer::dependency_parser::Language;
2use crate::analyzer::tool_management::{ToolDetector, ToolStatus, InstallationSource};
3use std::collections::HashMap;
4
5/// Handles reporting and display of tool status information
6pub struct ToolStatusReporter {
7    tool_detector: ToolDetector,
8}
9
10impl ToolStatusReporter {
11    pub fn new() -> Self {
12        Self {
13            tool_detector: ToolDetector::new(),
14        }
15    }
16    
17    /// Generate a comprehensive tool status report
18    pub fn generate_report(&mut self, languages: &[Language]) -> ToolStatusReport {
19        let tool_statuses = self.tool_detector.detect_all_vulnerability_tools(languages);
20        
21        let mut available_tools = Vec::new();
22        let mut missing_tools = Vec::new();
23        
24        for (tool_name, status) in &tool_statuses {
25            if status.available {
26                available_tools.push(ToolInfo {
27                    name: tool_name.clone(),
28                    version: status.version.clone(),
29                    path: status.path.clone(),
30                    source: status.installation_source.clone(),
31                });
32            } else {
33                missing_tools.push(MissingToolInfo {
34                    name: tool_name.clone(),
35                    language: self.get_language_for_tool(tool_name, languages),
36                    install_command: self.get_install_command(tool_name),
37                });
38            }
39        }
40        
41        let available_count = available_tools.len();
42        
43        ToolStatusReport {
44            available_tools,
45            missing_tools,
46            total_tools: tool_statuses.len(),
47            availability_percentage: (available_count as f32 / tool_statuses.len() as f32) * 100.0,
48        }
49    }
50    
51    fn get_language_for_tool(&self, tool_name: &str, languages: &[Language]) -> Option<Language> {
52        for language in languages {
53            let tools = match language {
54                Language::Rust => vec!["cargo-audit"],
55                Language::JavaScript | Language::TypeScript => vec!["bun", "npm", "yarn", "pnpm"],
56                Language::Python => vec!["pip-audit"],
57                Language::Go => vec!["govulncheck"],
58                Language::Java | Language::Kotlin => vec!["grype"],
59                _ => vec![],
60            };
61            
62            if tools.contains(&tool_name) {
63                return Some(language.clone());
64            }
65        }
66        None
67    }
68    
69    fn get_install_command(&self, tool_name: &str) -> String {
70        match tool_name {
71            "cargo-audit" => "cargo install cargo-audit".to_string(),
72            "bun" => "curl -fsSL https://bun.sh/install | bash".to_string(),
73            "npm" => "Install Node.js from https://nodejs.org/".to_string(),
74            "yarn" => "npm install -g yarn".to_string(),
75            "pnpm" => "npm install -g pnpm".to_string(),
76            "pip-audit" => "pip install pip-audit or pipx install pip-audit".to_string(),
77            "govulncheck" => "go install golang.org/x/vuln/cmd/govulncheck@latest".to_string(),
78            "grype" => "brew install grype or download from GitHub".to_string(),
79            _ => "check documentation".to_string(),
80        }
81    }
82}
83
84#[derive(Debug, Clone)]
85pub struct ToolStatusReport {
86    pub available_tools: Vec<ToolInfo>,
87    pub missing_tools: Vec<MissingToolInfo>,
88    pub total_tools: usize,
89    pub availability_percentage: f32,
90}
91
92#[derive(Debug, Clone)]
93pub struct ToolInfo {
94    pub name: String,
95    pub version: Option<String>,
96    pub path: Option<std::path::PathBuf>,
97    pub source: InstallationSource,
98}
99
100#[derive(Debug, Clone)]
101pub struct MissingToolInfo {
102    pub name: String,
103    pub language: Option<Language>,
104    pub install_command: String,
105}
106
107impl ToolStatusReport {
108    /// Print a formatted report to the console
109    pub fn print_console_report(&self) {
110        println!("\n🔧 Tool Status Report");
111        println!("{}", "=".repeat(50));
112        println!("Overall availability: {:.1}% ({}/{})", 
113                 self.availability_percentage, 
114                 self.available_tools.len(), 
115                 self.total_tools);
116        
117        if !self.available_tools.is_empty() {
118            println!("\n✅ Available Tools:");
119            for tool in &self.available_tools {
120                print!("  • {}", tool.name);
121                if let Some(ref version) = tool.version {
122                    print!(" (v{})", version);
123                }
124                if let Some(ref path) = tool.path {
125                    print!(" at {}", path.display());
126                }
127                match &tool.source {
128                    InstallationSource::SystemPath => print!(" [system]"),
129                    InstallationSource::UserLocal => print!(" [user]"),
130                    InstallationSource::CargoHome => print!(" [cargo]"),
131                    InstallationSource::GoHome => print!(" [go]"),
132                    InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
133                    InstallationSource::Manual => print!(" [manual]"),
134                    InstallationSource::NotFound => {},
135                }
136                println!();
137            }
138        }
139        
140        if !self.missing_tools.is_empty() {
141            println!("\n❌ Missing Tools:");
142            for tool in &self.missing_tools {
143                print!("  • {}", tool.name);
144                if let Some(ref lang) = tool.language {
145                    print!(" ({:?})", lang);
146                }
147                println!(" - Install: {}", tool.install_command);
148            }
149        }
150        
151        println!();
152    }
153}