syncable_cli/analyzer/tool_management/
status.rs

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