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