tinymist_std/
path.rs

1//! Path utilities.
2
3use std::path::{Component, Path};
4
5pub use path_clean::PathClean;
6
7/// Get the path cleaned as a unix-style string.
8pub fn unix_slash(root: &Path) -> String {
9    let mut res = String::with_capacity(root.as_os_str().len());
10    let mut parent_norm = false;
11    for comp in root.components() {
12        match comp {
13            Component::Prefix(p) => {
14                res.push_str(&p.as_os_str().to_string_lossy());
15                parent_norm = false;
16            }
17            Component::RootDir => {
18                res.push('/');
19                parent_norm = false;
20            }
21            Component::CurDir => {
22                parent_norm = false;
23            }
24            Component::ParentDir => {
25                if parent_norm {
26                    res.push('/');
27                }
28                res.push_str("..");
29                parent_norm = true;
30            }
31            Component::Normal(p) => {
32                if parent_norm {
33                    res.push('/');
34                }
35                res.push_str(&p.to_string_lossy());
36                parent_norm = true;
37            }
38        }
39    }
40
41    if res.is_empty() {
42        res.push('.');
43    }
44
45    res
46}
47
48/// Get the path cleaned as a platform-style string.
49pub use path_clean::clean;
50
51#[cfg(test)]
52mod test {
53    use std::path::{Path, PathBuf};
54
55    use super::{clean as inner_path_clean, unix_slash, PathClean};
56
57    pub fn clean<P: AsRef<Path>>(path: P) -> String {
58        unix_slash(&inner_path_clean(path))
59    }
60
61    #[test]
62    fn test_unix_slash() {
63        if cfg!(target_os = "windows") {
64            // windows group
65            assert_eq!(
66                unix_slash(std::path::Path::new("C:\\Users\\a\\b\\c")),
67                "C:/Users/a/b/c"
68            );
69            assert_eq!(
70                unix_slash(std::path::Path::new("C:\\Users\\a\\b\\c\\")),
71                "C:/Users/a/b/c"
72            );
73            assert_eq!(unix_slash(std::path::Path::new("a\\b\\c")), "a/b/c");
74            assert_eq!(unix_slash(std::path::Path::new("C:\\")), "C:/");
75            assert_eq!(unix_slash(std::path::Path::new("C:\\\\")), "C:/");
76            assert_eq!(unix_slash(std::path::Path::new("C:")), "C:");
77            assert_eq!(unix_slash(std::path::Path::new("C:\\a")), "C:/a");
78            assert_eq!(unix_slash(std::path::Path::new("C:\\a\\")), "C:/a");
79            assert_eq!(unix_slash(std::path::Path::new("C:\\a\\b")), "C:/a/b");
80            assert_eq!(
81                unix_slash(std::path::Path::new("C:\\Users\\a\\..\\b\\c")),
82                "C:/Users/a/../b/c"
83            );
84            assert_eq!(
85                unix_slash(std::path::Path::new("C:\\Users\\a\\..\\b\\c\\")),
86                "C:/Users/a/../b/c"
87            );
88            assert_eq!(
89                unix_slash(std::path::Path::new("C:\\Users\\a\\..\\..")),
90                "C:/Users/a/../.."
91            );
92            assert_eq!(
93                unix_slash(std::path::Path::new("C:\\Users\\a\\..\\..\\")),
94                "C:/Users/a/../.."
95            );
96        }
97        // unix group
98        assert_eq!(unix_slash(std::path::Path::new("/a/b/c")), "/a/b/c");
99        assert_eq!(unix_slash(std::path::Path::new("/a/b/c/")), "/a/b/c");
100        assert_eq!(unix_slash(std::path::Path::new("/")), "/");
101        assert_eq!(unix_slash(std::path::Path::new("//")), "/");
102        assert_eq!(unix_slash(std::path::Path::new("a")), "a");
103        assert_eq!(unix_slash(std::path::Path::new("a/")), "a");
104        assert_eq!(unix_slash(std::path::Path::new("a/b")), "a/b");
105        assert_eq!(unix_slash(std::path::Path::new("a/b/")), "a/b");
106        assert_eq!(unix_slash(std::path::Path::new("a/..")), "a/..");
107        assert_eq!(unix_slash(std::path::Path::new("a/../")), "a/..");
108        assert_eq!(unix_slash(std::path::Path::new("a/../..")), "a/../..");
109        assert_eq!(unix_slash(std::path::Path::new("a/../../")), "a/../..");
110        assert_eq!(unix_slash(std::path::Path::new("a/./b")), "a/b");
111        assert_eq!(unix_slash(std::path::Path::new("a/./b/")), "a/b");
112        assert_eq!(unix_slash(std::path::Path::new(".")), ".");
113        assert_eq!(unix_slash(std::path::Path::new("./")), ".");
114        assert_eq!(unix_slash(std::path::Path::new("./a")), "a");
115        assert_eq!(unix_slash(std::path::Path::new("./a/")), "a");
116        assert_eq!(unix_slash(std::path::Path::new("./a/b")), "a/b");
117        assert_eq!(unix_slash(std::path::Path::new("./a/b/")), "a/b");
118        assert_eq!(unix_slash(std::path::Path::new("./a/./b/")), "a/b");
119    }
120
121    #[test]
122    fn test_path_clean_empty_path_is_current_dir() {
123        assert_eq!(clean(""), ".");
124    }
125
126    #[test]
127    fn test_path_clean_clean_paths_dont_change() {
128        let tests = vec![(".", "."), ("..", ".."), ("/", "/")];
129
130        for test in tests {
131            assert_eq!(clean(test.0), test.1);
132        }
133    }
134
135    #[test]
136    fn test_path_clean_replace_multiple_slashes() {
137        let tests = vec![
138            ("/", "/"),
139            ("//", "/"),
140            ("///", "/"),
141            (".//", "."),
142            ("//..", "/"),
143            ("..//", ".."),
144            ("/..//", "/"),
145            ("/.//./", "/"),
146            ("././/./", "."),
147            ("path//to///thing", "path/to/thing"),
148            ("/path//to///thing", "/path/to/thing"),
149        ];
150
151        for test in tests {
152            assert_eq!(clean(test.0), test.1);
153        }
154    }
155
156    #[test]
157    fn test_path_clean_eliminate_current_dir() {
158        let tests = vec![
159            ("./", "."),
160            ("/./", "/"),
161            ("./test", "test"),
162            ("./test/./path", "test/path"),
163            ("/test/./path/", "/test/path"),
164            ("test/path/.", "test/path"),
165        ];
166
167        for test in tests {
168            assert_eq!(clean(test.0), test.1);
169        }
170    }
171
172    #[test]
173    fn test_path_clean_eliminate_parent_dir() {
174        let tests = vec![
175            ("/..", "/"),
176            ("/../test", "/test"),
177            ("test/..", "."),
178            ("test/path/..", "test"),
179            ("test/../path", "path"),
180            ("/test/../path", "/path"),
181            ("test/path/../../", "."),
182            ("test/path/../../..", ".."),
183            ("/test/path/../../..", "/"),
184            ("/test/path/../../../..", "/"),
185            ("test/path/../../../..", "../.."),
186            ("test/path/../../another/path", "another/path"),
187            ("test/path/../../another/path/..", "another"),
188            ("../test", "../test"),
189            ("../test/", "../test"),
190            ("../test/path", "../test/path"),
191            ("../test/..", ".."),
192        ];
193
194        for test in tests {
195            assert_eq!(clean(test.0), test.1);
196        }
197    }
198
199    #[test]
200    fn test_path_clean_pathbuf_trait() {
201        assert_eq!(
202            unix_slash(&PathBuf::from("/test/../path/").clean()),
203            "/path"
204        );
205    }
206
207    #[test]
208    fn test_path_clean_path_trait() {
209        assert_eq!(unix_slash(&Path::new("/test/../path/").clean()), "/path");
210    }
211
212    #[test]
213    #[cfg(target_os = "windows")]
214    fn test_path_clean_windows_paths() {
215        let tests = vec![
216            ("\\..", "/"),
217            ("\\..\\test", "/test"),
218            ("test\\..", "."),
219            ("test\\path\\..\\..\\..", ".."),
220            ("test\\path/..\\../another\\path", "another/path"), // Mixed
221            ("test\\path\\my/path", "test/path/my/path"),        // Mixed 2
222            ("/dir\\../otherDir/test.json", "/otherDir/test.json"), // User example
223            ("c:\\test\\..", "c:/"),                             // issue #12
224            ("c:/test/..", "c:/"),                               // issue #12
225        ];
226
227        for test in tests {
228            assert_eq!(clean(test.0), test.1);
229        }
230    }
231}