Skip to main content

utils/
path.rs

1use std::path::{Path, PathBuf};
2
3/// Renders `path` relative to the user's home directory (`~/…`) when it lives
4/// inside it, mirroring the way shells display paths.
5pub fn home_relative_path(path: &Path) -> String {
6    home_dir().map_or_else(|| path.display().to_string(), |home| home_relative_path_with_home(path, &home))
7}
8
9pub fn aether_home() -> Option<PathBuf> {
10    app_home("AETHER_HOME", ".aether")
11}
12
13pub fn app_home(env_var: &str, dot_dir: &str) -> Option<PathBuf> {
14    resolve_home(std::env::var(env_var).ok().as_deref(), home_dir().as_deref(), dot_dir)
15}
16
17pub(crate) fn home_dir() -> Option<PathBuf> {
18    std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(PathBuf::from)
19}
20
21fn resolve_home(env_override: Option<&str>, home: Option<&Path>, dot_dir: &str) -> Option<PathBuf> {
22    if let Some(value) = env_override
23        && !value.trim().is_empty()
24    {
25        return Some(PathBuf::from(value));
26    }
27
28    home.map(|home| home.join(dot_dir))
29}
30
31fn home_relative_path_with_home(path: &Path, home: &Path) -> String {
32    if path == home {
33        return "~".to_string();
34    }
35    path.strip_prefix(home)
36        .ok()
37        .filter(|relative| !relative.as_os_str().is_empty())
38        .map_or_else(|| path.display().to_string(), |relative| format!("~/{}", relative.display()))
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn paths_inside_home_shorten_to_tilde() {
47        let home = Path::new("/home/user");
48        assert_eq!(home_relative_path_with_home(&home.join("project"), home), "~/project");
49        assert_eq!(home_relative_path_with_home(home, home), "~");
50    }
51
52    #[test]
53    fn resolve_home_prefers_a_non_empty_env_override() {
54        let home = Some(Path::new("/home/test"));
55        assert_eq!(resolve_home(Some("/tmp/custom"), home, ".app"), Some(PathBuf::from("/tmp/custom")));
56        assert_eq!(resolve_home(Some("  "), home, ".app"), Some(PathBuf::from("/home/test/.app")));
57        assert_eq!(resolve_home(None, home, ".app"), Some(PathBuf::from("/home/test/.app")));
58        assert_eq!(resolve_home(None, None, ".app"), None);
59    }
60
61    #[test]
62    fn paths_outside_home_stay_absolute() {
63        let home = Path::new("/home/user");
64        assert_eq!(home_relative_path_with_home(Path::new("/etc/config"), home), "/etc/config");
65        assert_eq!(home_relative_path_with_home(home.parent().unwrap(), home), "/home");
66    }
67}