use std::env;
use std::env::split_paths;
use std::fs;
use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
const COMMON_DIRS: &[&str] = &[
"/usr/bin",
"/usr/local/bin",
"/bin",
"/sbin",
"/usr/sbin",
"/usr/local/sbin",
"/snap/bin",
];
const NIX_DIRS: &[&str] = &[
"/run/current-system/sw/bin",
"/nix/var/nix/profiles/default/bin",
];
#[must_use]
pub fn find_command(name: &str) -> Option<PathBuf> {
if name.is_empty() {
return None;
}
let path = Path::new(name);
if path.components().count() > 1 {
return if is_executable(path) {
Some(path.to_path_buf())
} else {
None
};
}
let env_path = env::var_os("PATH").unwrap_or_default();
let mut candidates: Vec<_> = split_paths(&env_path).collect();
for d in COMMON_DIRS {
candidates.push(PathBuf::from(d));
}
for d in NIX_DIRS {
candidates.push(PathBuf::from(d));
}
if let Some(home) = env::var_os("HOME") {
candidates.push(PathBuf::from(home).join(".nix-profile").join("bin"));
}
for dir in candidates {
if dir.as_os_str().is_empty() {
continue;
}
let candidate = dir.clone().join(name);
if is_executable(&candidate) {
return Some(candidate);
}
}
None
}
#[must_use]
pub fn command_exists(name: &str) -> bool {
find_command(name).is_some()
}
fn is_executable(path: &Path) -> bool {
if !path.exists() {
return false;
}
if !path.is_file() {
return false;
}
#[cfg(unix)]
{
if let Ok(meta) = fs::metadata(path) {
let mode = meta.permissions().mode();
return mode & 0o111 != 0;
}
false
}
#[cfg(not(unix))]
{
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_shell_exists() {
let candidates = ["sh", "bash", "zsh"];
let found = candidates.iter().any(|c| command_exists(c));
assert!(found, "expected at least one common shell to exist");
}
#[test]
fn nonexistent_command_does_not_exist() {
assert!(!command_exists(
"this-command-should-never-exist-12345-amogus"
));
}
}