Skip to main content

nu_path/
helpers.rs

1#[cfg(windows)]
2use std::path::{Component, Prefix};
3use std::path::{Path, PathBuf, absolute};
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    // If the environment variable is not empty and contains an absolute path
34    // then use it. Otherwise use dir().
35    let path = if let Ok(path) = std::env::var(name)
36        && !path.is_empty()
37        && Path::new(&path).is_absolute()
38    {
39        PathBuf::from(path)
40    } else {
41        dir()?
42    };
43    AbsolutePathBuf::try_from(absolute(&path).unwrap_or(path)).ok()
44}
45
46// List of special paths that can be written to and/or read from, even though they
47// don't appear as directory entries.
48// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
49// In rare circumstances, reserved paths _can_ exist as regular files in a
50// directory which shadow their special counterpart, so the safe way of referring
51// to these paths is by prefixing them with '\\.\' (this instructs the Windows APIs
52// to access the Win32 device namespace instead of the Win32 file namespace)
53// https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
54#[cfg(windows)]
55pub fn is_windows_device_path(path: &Path) -> bool {
56    match path.components().next() {
57        Some(Component::Prefix(prefix)) if matches!(prefix.kind(), Prefix::DeviceNS(_)) => {
58            return true;
59        }
60        _ => {}
61    }
62    let special_paths: [&Path; 28] = [
63        Path::new("CON"),
64        Path::new("PRN"),
65        Path::new("AUX"),
66        Path::new("NUL"),
67        Path::new("COM1"),
68        Path::new("COM2"),
69        Path::new("COM3"),
70        Path::new("COM4"),
71        Path::new("COM5"),
72        Path::new("COM6"),
73        Path::new("COM7"),
74        Path::new("COM8"),
75        Path::new("COM9"),
76        Path::new("COM¹"),
77        Path::new("COM²"),
78        Path::new("COM³"),
79        Path::new("LPT1"),
80        Path::new("LPT2"),
81        Path::new("LPT3"),
82        Path::new("LPT4"),
83        Path::new("LPT5"),
84        Path::new("LPT6"),
85        Path::new("LPT7"),
86        Path::new("LPT8"),
87        Path::new("LPT9"),
88        Path::new("LPT¹"),
89        Path::new("LPT²"),
90        Path::new("LPT³"),
91    ];
92    if special_paths.contains(&path) {
93        return true;
94    }
95    false
96}
97
98#[cfg(not(windows))]
99pub fn is_windows_device_path(_path: &Path) -> bool {
100    false
101}
102
103#[cfg(test)]
104mod test_is_windows_device_path {
105    use crate::is_windows_device_path;
106    use std::path::Path;
107
108    #[cfg_attr(not(windows), ignore = "only for Windows")]
109    #[test]
110    fn device_namespace() {
111        assert!(is_windows_device_path(Path::new(r"\\.\CON")))
112    }
113
114    #[cfg_attr(not(windows), ignore = "only for Windows")]
115    #[test]
116    fn reserved_device_name() {
117        assert!(is_windows_device_path(Path::new(r"NUL")))
118    }
119
120    #[cfg_attr(not(windows), ignore = "only for Windows")]
121    #[test]
122    fn normal_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 absolute_path() {
129        assert!(!is_windows_device_path(Path::new(r"\dir\file")))
130    }
131
132    #[cfg_attr(not(windows), ignore = "only for Windows")]
133    #[test]
134    fn unc_path() {
135        assert!(!is_windows_device_path(Path::new(r"\\server\share")))
136    }
137
138    #[cfg_attr(not(windows), ignore = "only for Windows")]
139    #[test]
140    fn verbatim_path() {
141        assert!(!is_windows_device_path(Path::new(r"\\?\dir\file")))
142    }
143}