use std::process::Command;
use which::which;
use crate::config::BinaryCheck;
use super::CheckResult;
pub fn check_binary(binary: &BinaryCheck) -> CheckResult {
let name = binary.name();
match which(name) {
Ok(path) => {
let version = get_version(name);
let version_str = version.as_deref().unwrap_or("unknown version");
let path_str = path.display();
CheckResult::pass("Binary", name, format!("{version_str} ({path_str})"))
}
Err(_) => CheckResult::fail("Binary", name, "not found in PATH"),
}
}
fn get_version(name: &str) -> Option<String> {
for flag in ["--version", "-version", "-V", "version"] {
if let Some(version) = try_version_flag(name, flag) {
return Some(version);
}
}
None
}
fn try_version_flag(name: &str, flag: &str) -> Option<String> {
let output = Command::new(name).arg(flag).output().ok()?;
if !output.status.success() && flag != "-V" {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let version_line = stdout
.lines()
.chain(stderr.lines())
.find(|line| !line.trim().is_empty())?;
Some(extract_version(version_line))
}
fn extract_version(line: &str) -> String {
let words: Vec<&str> = line.split_whitespace().collect();
for word in &words {
let clean = word.trim_matches(|c: char| !c.is_ascii_digit() && c != '.');
if clean.chars().next().is_some_and(|c| c.is_ascii_digit()) && clean.contains('.') {
return format!("v{clean}");
}
}
if line.len() > 50 {
format!("{}...", &line[..50])
} else {
line.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_version() {
assert_eq!(extract_version("docker version 20.10.8"), "v20.10.8");
assert_eq!(extract_version("node v18.17.0"), "v18.17.0");
assert_eq!(extract_version("rustc 1.72.0"), "v1.72.0");
}
#[test]
fn test_check_common_binary() {
let check = BinaryCheck::Simple("ls".to_string());
let result = check_binary(&check);
assert!(result.passed);
}
#[test]
fn test_check_missing_binary() {
let check = BinaryCheck::Simple("definitely-not-a-real-binary-12345".to_string());
let result = check_binary(&check);
assert!(!result.passed);
}
}