lxutil/
path.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(unix)]
5use super::unix as sys;
6#[cfg(windows)]
7use super::windows as sys;
8use std::borrow::Cow;
9use std::path::Path;
10use std::path::PathBuf;
11
12/// Extensions to `PathBuf` to convert Unix-style paths to a native path.
13///
14/// # Windows
15///
16/// Paths are converted to native by switching the separators from / to \\, and by escaping
17/// characters that are not legal in NTFS file names (such as \\, :, and others) by mapping
18/// them into a private unicode range (0xf000).
19///
20/// This is primarily useful for relative paths which will be passed to the methods of `LxVolume`.
21/// While you can translate absolute paths, the result will have no drive letter.
22///
23/// # Unix
24///
25/// Paths are already native so no conversion is performed.
26pub trait PathBufExt {
27    /// Creates a `PathBuf` by converting a Unix-style path to its native representation.
28    fn from_lx(path: impl AsRef<lx::LxStr>) -> lx::Result<PathBuf>;
29
30    /// Extends `self` with `path`, first converting it from a Unix-style path to its native
31    /// representation.
32    fn push_lx(&mut self, path: impl AsRef<lx::LxStr>) -> lx::Result<()>;
33}
34
35impl PathBufExt for PathBuf {
36    fn from_lx(path: impl AsRef<lx::LxStr>) -> lx::Result<PathBuf> {
37        Ok(Self::from(Path::from_lx(&path)?))
38    }
39
40    fn push_lx(&mut self, path: impl AsRef<lx::LxStr>) -> lx::Result<()> {
41        self.push(Path::from_lx(&path)?);
42        Ok(())
43    }
44}
45
46/// Extensions to `Path` to convert Unix-style paths to a native path, using the same rules as
47/// `PathBuf`.
48pub trait PathExt {
49    /// Creates a `Path` by converting a Unix-style path to its native representation, avoiding
50    /// allocation if unnecessary.
51    ///
52    /// # Windows
53    ///
54    /// This function does not allocate (returns a `Cow::Borrowed`) if the path contains no
55    /// separators and no characters that need to be escaped. Otherwise, it allocates a `PathBuf`
56    /// and returns a `Cow::Owned`.
57    ///
58    /// # Unix
59    ///
60    /// This function never allocates and always returns a `Cow::Borrowed`.
61    fn from_lx(path: &(impl AsRef<lx::LxStr> + ?Sized)) -> lx::Result<Cow<'_, Self>>
62    where
63        Self: ToOwned;
64}
65
66impl PathExt for Path {
67    fn from_lx(path: &(impl AsRef<lx::LxStr> + ?Sized)) -> lx::Result<Cow<'_, Self>> {
68        sys::path::path_from_lx(path.as_ref().as_bytes())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use std::borrow::Cow;
76
77    #[test]
78    fn from_lx() {
79        let path = Path::from_lx("test").unwrap();
80        println!("{:?}", path);
81        assert!(matches!(path, Cow::Borrowed(_)));
82        assert_eq!(path.to_str(), Some("test"));
83
84        let path = Path::from_lx("foo:bar").unwrap();
85        println!("{:?}", path);
86        if cfg!(windows) {
87            assert!(matches!(path, Cow::Owned(_)));
88            assert_eq!(path.to_str(), Some("foo\u{f03a}bar"));
89        } else {
90            assert!(matches!(path, Cow::Borrowed(_)));
91            assert_eq!(path.to_str(), Some("foo:bar"));
92        }
93
94        let path = Path::from_lx("dir/file").unwrap();
95        println!("{:?}", path);
96        if cfg!(windows) {
97            assert!(matches!(path, Cow::Owned(_)));
98            assert_eq!(path.to_str(), Some("dir\\file"));
99        } else {
100            assert!(matches!(path, Cow::Borrowed(_)));
101            assert_eq!(path.to_str(), Some("dir/file"));
102        }
103
104        let path = PathBuf::from_lx("dir/foo:bar").unwrap();
105        if cfg!(windows) {
106            assert_eq!(path.to_str(), Some("dir\\foo\u{f03a}bar"));
107        } else {
108            assert_eq!(path.to_str(), Some("dir/foo:bar"));
109        }
110    }
111
112    #[test]
113    fn push() {
114        let mut path = PathBuf::new();
115        path.push_lx("dir/subdir").unwrap();
116        if cfg!(windows) {
117            assert_eq!(path.to_str(), Some("dir\\subdir"));
118        } else {
119            assert_eq!(path.to_str(), Some("dir/subdir"));
120        }
121
122        path.push_lx("foo:bar").unwrap();
123        if cfg!(windows) {
124            assert_eq!(path.to_str(), Some("dir\\subdir\\foo\u{f03a}bar"));
125        } else {
126            assert_eq!(path.to_str(), Some("dir/subdir/foo:bar"));
127        }
128    }
129}