nu_path/
helpers.rs

1#[cfg(windows)]
2use std::path::{Component, Prefix};
3use std::path::{Path, PathBuf};
4
5use crate::AbsolutePathBuf;
6
7pub fn home_dir() -> Option<AbsolutePathBuf> {
8    dirs::home_dir().and_then(|home| AbsolutePathBuf::try_from(home).ok())
9}
10
11/// Return the data directory for the current platform or XDG_DATA_HOME if specified.
12pub fn data_dir() -> Option<AbsolutePathBuf> {
13    configurable_dir_path("XDG_DATA_HOME", dirs::data_dir)
14}
15
16/// Return the cache directory for the current platform or XDG_CACHE_HOME if specified.
17pub fn cache_dir() -> Option<AbsolutePathBuf> {
18    configurable_dir_path("XDG_CACHE_HOME", dirs::cache_dir)
19}
20
21/// Return the nushell config directory.
22pub fn nu_config_dir() -> Option<AbsolutePathBuf> {
23    configurable_dir_path("XDG_CONFIG_HOME", dirs::config_dir).map(|mut p| {
24        p.push("nushell");
25        p
26    })
27}
28
29fn configurable_dir_path(
30    name: &'static str,
31    dir: impl FnOnce() -> Option<PathBuf>,
32) -> Option<AbsolutePathBuf> {
33    std::env::var(name)
34        .ok()
35        .and_then(|path| AbsolutePathBuf::try_from(path).ok())
36        .or_else(|| dir().and_then(|path| AbsolutePathBuf::try_from(path).ok()))
37        .map(|path| path.canonicalize().map(Into::into).unwrap_or(path))
38}
39
40// List of special paths that can be written to and/or read from, even though they
41// don't appear as directory entries.
42// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
43// In rare circumstances, reserved paths _can_ exist as regular files in a
44// directory which shadow their special counterpart, so the safe way of referring
45// to these paths is by prefixing them with '\\.\' (this instructs the Windows APIs
46// to access the Win32 device namespace instead of the Win32 file namespace)
47// https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
48#[cfg(windows)]
49pub fn is_windows_device_path(path: &Path) -> bool {
50    match path.components().next() {
51        Some(Component::Prefix(prefix)) if matches!(prefix.kind(), Prefix::DeviceNS(_)) => {
52            return true;
53        }
54        _ => {}
55    }
56    let special_paths: [&Path; 28] = [
57        Path::new("CON"),
58        Path::new("PRN"),
59        Path::new("AUX"),
60        Path::new("NUL"),
61        Path::new("COM1"),
62        Path::new("COM2"),
63        Path::new("COM3"),
64        Path::new("COM4"),
65        Path::new("COM5"),
66        Path::new("COM6"),
67        Path::new("COM7"),
68        Path::new("COM8"),
69        Path::new("COM9"),
70        Path::new("COM¹"),
71        Path::new("COM²"),
72        Path::new("COM³"),
73        Path::new("LPT1"),
74        Path::new("LPT2"),
75        Path::new("LPT3"),
76        Path::new("LPT4"),
77        Path::new("LPT5"),
78        Path::new("LPT6"),
79        Path::new("LPT7"),
80        Path::new("LPT8"),
81        Path::new("LPT9"),
82        Path::new("LPT¹"),
83        Path::new("LPT²"),
84        Path::new("LPT³"),
85    ];
86    if special_paths.contains(&path) {
87        return true;
88    }
89    false
90}
91
92#[cfg(not(windows))]
93pub fn is_windows_device_path(_path: &Path) -> bool {
94    false
95}
96
97#[cfg(test)]
98mod test_is_windows_device_path {
99    use crate::is_windows_device_path;
100    use std::path::Path;
101
102    #[cfg_attr(not(windows), ignore = "only for Windows")]
103    #[test]
104    fn device_namespace() {
105        assert!(is_windows_device_path(Path::new(r"\\.\CON")))
106    }
107
108    #[cfg_attr(not(windows), ignore = "only for Windows")]
109    #[test]
110    fn reserved_device_name() {
111        assert!(is_windows_device_path(Path::new(r"NUL")))
112    }
113
114    #[cfg_attr(not(windows), ignore = "only for Windows")]
115    #[test]
116    fn normal_path() {
117        assert!(!is_windows_device_path(Path::new(r"dir\file")))
118    }
119
120    #[cfg_attr(not(windows), ignore = "only for Windows")]
121    #[test]
122    fn absolute_path() {
123        assert!(!is_windows_device_path(Path::new(r"\dir\file")))
124    }
125
126    #[cfg_attr(not(windows), ignore = "only for Windows")]
127    #[test]
128    fn unc_path() {
129        assert!(!is_windows_device_path(Path::new(r"\\server\share")))
130    }
131
132    #[cfg_attr(not(windows), ignore = "only for Windows")]
133    #[test]
134    fn verbatim_path() {
135        assert!(!is_windows_device_path(Path::new(r"\\?\dir\file")))
136    }
137}