//! Finding binaries on `PATH`. use std::collections::BTreeMap; use std::path::PathBuf; /// The prefix every verb binary carries. const PREFIX: &str = "didbot-"; /// The first executable named `name` on `PATH`, if there is one. pub fn find(name: &str) -> Option { let path = std::env::var_os("PATH")?; std::env::split_paths(&path) .map(|dir| dir.join(name)) .find(|candidate| executable(candidate)) } /// Every `didbot-*` on `PATH`, by name, the first on `PATH` winning — the /// same one [`find`] would run. pub fn installed() -> BTreeMap { let mut found = BTreeMap::new(); let Some(path) = std::env::var_os("PATH") else { return found; }; for dir in std::env::split_paths(&path) { let Ok(entries) = std::fs::read_dir(&dir) else { continue; }; for entry in entries.flatten() { let name = entry.file_name().to_string_lossy().into_owned(); if name.len() > PREFIX.len() && name.starts_with(PREFIX) && executable(&entry.path()) { found.entry(name).or_insert_with(|| entry.path()); } } } found } /// A regular file this process may run. fn executable(candidate: &std::path::Path) -> bool { let Ok(metadata) = std::fs::metadata(candidate) else { return false; }; if !metadata.is_file() { return false; } #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; metadata.permissions().mode() & 0o111 != 0 } #[cfg(not(unix))] { true } }