1#[cfg(windows)]
2use omnipath::WinPathExt;
3use std::path::{Component, Path, PathBuf};
4
5pub fn expand_ndots(path: impl AsRef<Path>) -> PathBuf {
12 fn is_ndots(s: &std::ffi::OsStr) -> bool {
14 s.as_encoded_bytes().iter().all(|c| *c == b'.') && s.len() >= 3
15 }
16
17 let path = path.as_ref();
18
19 let mut result = PathBuf::with_capacity(path.as_os_str().len());
20 for component in crate::components(path) {
21 match component {
22 Component::Normal(s) if is_ndots(s) => {
23 let n = s.len();
24 for _ in 0..n - 1 {
26 result.push("..");
27 }
28 }
29 _ => result.push(component),
30 }
31 }
32
33 result
34}
35
36pub fn expand_dots(path: impl AsRef<Path>) -> PathBuf {
47 fn last_component_is_normal(path: &Path) -> bool {
49 matches!(path.components().last(), Some(Component::Normal(_)))
50 }
51
52 let path = path.as_ref();
53
54 let mut result = PathBuf::with_capacity(path.as_os_str().len());
55 for component in crate::components(path) {
56 match component {
57 Component::ParentDir if last_component_is_normal(&result) => {
58 result.pop();
59 }
60 Component::CurDir if last_component_is_normal(&result) => {
61 }
63 _ => {
64 let prev_component = result.components().last();
65 if prev_component == Some(Component::RootDir) && component == Component::ParentDir {
66 continue;
67 }
68 result.push(component)
69 }
70 }
71 }
72
73 simiplified(&result)
74}
75
76pub fn expand_ndots_safe(path: impl AsRef<Path>) -> PathBuf {
79 let string = path.as_ref().to_string_lossy();
80
81 if string.contains("...") && !string.contains("://") && !string.starts_with("./") {
87 expand_ndots(path)
88 } else {
89 path.as_ref().to_owned()
90 }
91}
92
93#[cfg(windows)]
94fn simiplified(path: &std::path::Path) -> PathBuf {
95 path.to_winuser_path()
96 .unwrap_or_else(|_| path.to_path_buf())
97}
98
99#[cfg(not(windows))]
100fn simiplified(path: &std::path::Path) -> PathBuf {
101 path.to_path_buf()
102}
103
104#[cfg(test)]
105mod test_expand_ndots {
106 use super::*;
107 use crate::assert_path_eq;
108
109 #[test]
110 fn empty_path() {
111 let path = Path::new("");
112 assert_path_eq!(expand_ndots(path), "");
113 }
114
115 #[test]
116 fn root_dir() {
117 let path = Path::new("/");
118 let expected = if cfg!(windows) { "\\" } else { "/" };
119 assert_path_eq!(expand_ndots(path), expected);
120 }
121
122 #[test]
123 fn two_dots() {
124 let path = Path::new("..");
125 assert_path_eq!(expand_ndots(path), "..");
126 }
127
128 #[test]
129 fn three_dots() {
130 let path = Path::new("...");
131 let expected = if cfg!(windows) { r"..\.." } else { "../.." };
132 assert_path_eq!(expand_ndots(path), expected);
133 }
134
135 #[test]
136 fn five_dots() {
137 let path = Path::new(".....");
138 let expected = if cfg!(windows) {
139 r"..\..\..\.."
140 } else {
141 "../../../.."
142 };
143 assert_path_eq!(expand_ndots(path), expected);
144 }
145
146 #[test]
147 fn three_dots_with_trailing_slash() {
148 let path = Path::new("/tmp/.../");
149 let expected = if cfg!(windows) {
150 r"\tmp\..\..\"
151 } else {
152 "/tmp/../../"
153 };
154 assert_path_eq!(expand_ndots(path), expected);
155 }
156
157 #[test]
158 fn filenames_with_dots() {
159 let path = Path::new("...foo.../");
160 let expected = if cfg!(windows) {
161 r"...foo...\"
162 } else {
163 "...foo.../"
164 };
165 assert_path_eq!(expand_ndots(path), expected);
166 }
167
168 #[test]
169 fn multiple_ndots() {
170 let path = Path::new("..././...");
171 let expected = if cfg!(windows) {
172 r"..\..\..\.."
173 } else {
174 "../../../.."
175 };
176 assert_path_eq!(expand_ndots(path), expected);
177 }
178
179 #[test]
180 fn trailing_dots() {
181 let path = Path::new("/foo/bar/..");
182 let expected = if cfg!(windows) {
183 r"\foo\bar\.."
184 } else {
185 "/foo/bar/.."
186 };
187 assert_path_eq!(expand_ndots(path), expected);
188 }
189
190 #[test]
191 fn leading_dot_slash() {
192 let path = Path::new("./...");
193 assert_path_eq!(expand_ndots_safe(path), "./...");
194 }
195}
196
197#[cfg(test)]
198mod test_expand_dots {
199 use super::*;
200 use crate::assert_path_eq;
201
202 #[test]
203 fn empty_path() {
204 let path = Path::new("");
205 assert_path_eq!(expand_dots(path), "");
206 }
207
208 #[test]
209 fn single_dot() {
210 let path = Path::new("./");
211 let expected = if cfg!(windows) { r".\" } else { "./" };
212 assert_path_eq!(expand_dots(path), expected);
213 }
214
215 #[test]
216 fn more_single_dots() {
217 let path = Path::new("././.");
218 let expected = ".";
219 assert_path_eq!(expand_dots(path), expected);
220 }
221
222 #[test]
223 fn double_dots() {
224 let path = Path::new("../../..");
225 let expected = if cfg!(windows) {
226 r"..\..\.."
227 } else {
228 "../../.."
229 };
230 assert_path_eq!(expand_dots(path), expected);
231 }
232
233 #[test]
234 fn backtrack_once() {
235 let path = Path::new("/foo/bar/../baz/");
236 let expected = if cfg!(windows) {
237 r"\foo\baz\"
238 } else {
239 "/foo/baz/"
240 };
241 assert_path_eq!(expand_dots(path), expected);
242 }
243
244 #[test]
245 fn backtrack_to_root() {
246 let path = Path::new("/foo/bar/../../../../baz");
247 let expected = if cfg!(windows) { r"\baz" } else { "/baz" };
248 assert_path_eq!(expand_dots(path), expected);
249 }
250}