hyprshell_core_lib/util/
exists.rs1use std::env;
2use std::env::split_paths;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8
9const COMMON_DIRS: &[&str] = &[
11 "/usr/bin",
12 "/usr/local/bin",
13 "/bin",
14 "/sbin",
15 "/usr/sbin",
16 "/usr/local/sbin",
17 "/snap/bin",
18];
19const NIX_DIRS: &[&str] = &[
21 "/run/current-system/sw/bin",
22 "/nix/var/nix/profiles/default/bin",
23];
24
25#[must_use]
35pub fn find_command(name: &str) -> Option<PathBuf> {
36 if name.is_empty() {
37 return None;
38 }
39
40 let path = Path::new(name);
41
42 if path.components().count() > 1 {
44 return if is_executable(path) {
45 Some(path.to_path_buf())
46 } else {
47 None
48 };
49 }
50
51 let env_path = env::var_os("PATH").unwrap_or_default();
53 let mut candidates: Vec<_> = split_paths(&env_path).collect();
54
55 for d in COMMON_DIRS {
56 candidates.push(PathBuf::from(d));
57 }
58
59 for d in NIX_DIRS {
60 candidates.push(PathBuf::from(d));
61 }
62
63 if let Some(home) = env::var_os("HOME") {
65 candidates.push(PathBuf::from(home).join(".nix-profile").join("bin"));
66 }
67
68 for dir in candidates {
70 if dir.as_os_str().is_empty() {
71 continue;
72 }
73 let candidate = dir.clone().join(name);
74 if is_executable(&candidate) {
75 return Some(candidate);
76 }
77 }
78
79 None
80}
81
82#[must_use]
84pub fn command_exists(name: &str) -> bool {
85 find_command(name).is_some()
86}
87
88fn is_executable(path: &Path) -> bool {
89 if !path.exists() {
90 return false;
91 }
92 if !path.is_file() {
93 return false;
94 }
95
96 #[cfg(unix)]
97 {
98 if let Ok(meta) = fs::metadata(path) {
99 let mode = meta.permissions().mode();
100 return mode & 0o111 != 0;
102 }
103 false
104 }
105
106 #[cfg(not(unix))]
107 {
108 true
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn find_shell_exists() {
119 let candidates = ["sh", "bash", "zsh"];
121 let found = candidates.iter().any(|c| command_exists(c));
122 assert!(found, "expected at least one common shell to exist");
123 }
124
125 #[test]
126 fn nonexistent_command_does_not_exist() {
127 assert!(!command_exists(
128 "this-command-should-never-exist-12345-amogus"
129 ));
130 }
131}