1use std::path::{Path, PathBuf};
4
5pub fn expand_tilde(path: &str, home: &Path) -> PathBuf {
8 if path == "~" {
9 home.to_path_buf()
10 } else if let Some(rest) = path.strip_prefix("~/") {
11 home.join(rest)
12 } else {
13 PathBuf::from(path)
14 }
15}
16
17pub fn expand_tilde_env(path: &str) -> PathBuf {
21 match std::env::var("HOME") {
22 Ok(home) => expand_tilde(path, Path::new(&home)),
23 Err(_) => PathBuf::from(path),
24 }
25}
26
27pub fn is_executable(path: &Path) -> bool {
30 #[cfg(unix)]
31 {
32 use std::os::unix::fs::PermissionsExt;
33 std::fs::metadata(path)
34 .map(|meta| meta.is_file() && meta.permissions().mode() & 0o111 != 0)
35 .unwrap_or(false)
36 }
37 #[cfg(not(unix))]
38 {
39 path.is_file()
40 }
41}
42
43pub fn find_on_path(binary: &str) -> Option<PathBuf> {
45 let path = std::env::var_os("PATH")?;
46 std::env::split_paths(&path)
47 .map(|dir| dir.join(binary))
48 .find(|candidate| is_executable(candidate))
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn a_tilde_expands_against_home() {
57 let home = Path::new("/home/koala");
58 assert_eq!(expand_tilde("~", home), PathBuf::from("/home/koala"));
59 assert_eq!(
60 expand_tilde("~/models/a.gguf", home),
61 PathBuf::from("/home/koala/models/a.gguf")
62 );
63 }
64
65 #[test]
66 fn a_trailing_slash_on_home_is_normalized() {
67 assert_eq!(
68 expand_tilde("~/x", Path::new("/home/koala/")),
69 PathBuf::from("/home/koala/x")
70 );
71 }
72
73 #[test]
74 fn a_non_tilde_path_is_unchanged() {
75 let home = Path::new("/home/koala");
76 assert_eq!(expand_tilde("/abs/path", home), PathBuf::from("/abs/path"));
77 assert_eq!(expand_tilde("relative", home), PathBuf::from("relative"));
78 assert_eq!(expand_tilde("a/~/b", home), PathBuf::from("a/~/b"));
80 }
81}