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