Skip to main content

dejavu/exec/
path.rs

1//! `PATH` manipulation, always over `OsString` (paths may be non-UTF8).
2
3use std::ffi::{OsStr, OsString};
4use std::path::{Path, PathBuf};
5
6/// Split `PATH` into entries, dropping empty entries (POSIX "current dir").
7pub fn split_path(path: &OsStr) -> Vec<PathBuf> {
8    std::env::split_paths(path)
9        .filter(|p| !p.as_os_str().is_empty())
10        .collect()
11}
12
13/// `dir` prepended to `current`, with any pre-existing occurrence of `dir`
14/// removed (dedupe, so nested `dejavu start` doesn't stack shim dirs).
15pub fn prepend_dedup(dir: &Path, current: &OsStr) -> OsString {
16    let mut entries: Vec<PathBuf> = vec![dir.to_path_buf()];
17    for entry in split_path(current) {
18        if entry != dir {
19            entries.push(entry);
20        }
21    }
22    std::env::join_paths(entries).unwrap_or_else(|_| current.to_os_string())
23}
24
25/// `current` with every occurrence of `dir` removed — the sanitized PATH the
26/// real command runs against, so nested tool calls don't re-enter a shim.
27pub fn without_dir(dir: &Path, current: &OsStr) -> OsString {
28    let entries: Vec<PathBuf> = split_path(current)
29        .into_iter()
30        .filter(|entry| entry != dir)
31        .collect();
32    std::env::join_paths(entries).unwrap_or_else(|_| current.to_os_string())
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn prepend_puts_dir_first_and_dedups() {
41        let current = OsString::from("/usr/bin:/shim:/bin");
42        let out = prepend_dedup(Path::new("/shim"), &current);
43        let entries = split_path(&out);
44        assert_eq!(entries[0], PathBuf::from("/shim"));
45        assert_eq!(
46            entries.iter().filter(|p| *p == Path::new("/shim")).count(),
47            1
48        );
49    }
50
51    #[test]
52    fn without_dir_removes_all_occurrences() {
53        let current = OsString::from("/shim:/usr/bin:/shim:/bin");
54        let out = without_dir(Path::new("/shim"), &current);
55        let entries = split_path(&out);
56        assert!(!entries.iter().any(|p| p == Path::new("/shim")));
57        assert_eq!(
58            entries,
59            vec![PathBuf::from("/usr/bin"), PathBuf::from("/bin")]
60        );
61    }
62}