Skip to main content

falcon_library/
paths.rs

1//! Where the user's folders are, and paths written the way a reader expects.
2
3use std::path::{Path, PathBuf};
4
5/// The user's home folder: `HOME`, which a normal Windows launch doesn't set.
6#[cfg(not(windows))]
7pub fn home_dir() -> Option<PathBuf> {
8    std::env::var_os("HOME").map(PathBuf::from)
9}
10
11/// The user's home folder: `USERPROFILE`, or the system's answer without it.
12#[cfg(windows)]
13pub fn home_dir() -> Option<PathBuf> {
14    std::env::home_dir()
15}
16
17/// `%LOCALAPPDATA%`, for what belongs to this machine and user.
18#[cfg(windows)]
19pub fn local_data_dir() -> Option<PathBuf> {
20    std::env::var_os("LOCALAPPDATA").filter(|d| !d.is_empty()).map(PathBuf::from).or_else(|| Some(home_dir()?.join("AppData").join("Local")))
21}
22
23/// `path` without the `\\?\` prefix `canonicalize` gives it on Windows, which
24/// means nothing to a reader or to most programs. Other systems have none.
25pub fn shown(path: &Path) -> PathBuf {
26    let text = path.as_os_str().to_string_lossy();
27    if let Some(unc) = text.strip_prefix(r"\\?\UNC\") {
28        PathBuf::from(format!(r"\\{unc}"))
29    } else if let Some(disk) = text.strip_prefix(r"\\?\").filter(|p| p.as_bytes().get(1) == Some(&b':')) {
30        PathBuf::from(disk)
31    } else {
32        path.to_path_buf()
33    }
34}
35
36#[cfg(test)]
37pub(crate) mod tests {
38    #[test]
39    fn verbatim_prefixes_are_dropped_for_showing() {
40        use super::shown;
41        use std::path::Path;
42        assert_eq!(shown(Path::new("/home/me/a b")), Path::new("/home/me/a b"));
43        assert_eq!(shown(Path::new(r"C:\me")), Path::new(r"C:\me"));
44        assert_eq!(shown(Path::new(r"\\?\C:\me\a")), Path::new(r"C:\me\a"));
45        assert_eq!(shown(Path::new(r"\\?\UNC\host\share\a")), Path::new(r"\\host\share\a"));
46        assert_eq!(shown(Path::new(r"\\?\Volume{1}\a")), Path::new(r"\\?\Volume{1}\a"));
47    }
48
49    /// `HOME` is what Git Bash sets and a normal launch doesn't, so it must
50    /// not be what decides where the user's folders are.
51    #[cfg(windows)]
52    #[test]
53    fn the_windows_home_folder_is_the_user_profile() {
54        assert_eq!(super::home_dir().map(std::path::PathBuf::into_os_string), std::env::var_os("USERPROFILE"));
55    }
56
57    /// A junction, the link to a folder Windows lets anyone make.
58    #[cfg(windows)]
59    pub(crate) fn junction(target: &std::path::Path, link: &std::path::Path) {
60        let made = std::process::Command::new("cmd").arg("/C").arg("mklink").arg("/J").arg(super::shown(link)).arg(super::shown(target)).output().unwrap();
61        assert!(made.status.success(), "{made:?}");
62    }
63}