syncable_cli/analyzer/tool_management/
status.rs1use crate::analyzer::dependency_parser::Language;
2use crate::analyzer::tool_management::{ToolDetector, ToolStatus, InstallationSource};
3use std::collections::HashMap;
4
5pub 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 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 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}