1use std::{
2 borrow::Cow,
3 ffi::{OsStr, OsString},
4 path::{Component, Path, PathBuf},
5};
6
7use bstr::{BStr, BString};
8
9#[derive(Debug)]
10pub struct Utf8Error;
12
13impl std::fmt::Display for Utf8Error {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 f.write_str("Could not convert to UTF8 or from UTF8 due to ill-formed input")
16 }
17}
18
19impl std::error::Error for Utf8Error {}
20
21pub fn os_str_into_bstr(path: &OsStr) -> Result<&BStr, Utf8Error> {
23 let path = try_into_bstr(Cow::Borrowed(path.as_ref()))?;
24 match path {
25 Cow::Borrowed(path) => Ok(path),
26 Cow::Owned(_) => unreachable!("borrowed cows stay borrowed"),
27 }
28}
29
30pub fn os_string_into_bstring(path: OsString) -> Result<BString, Utf8Error> {
32 let path = try_into_bstr(Cow::Owned(path.into()))?;
33 match path {
34 Cow::Borrowed(_path) => unreachable!("borrowed cows stay borrowed"),
35 Cow::Owned(path) => Ok(path),
36 }
37}
38
39pub fn try_os_str_into_bstr(path: Cow<'_, OsStr>) -> Result<Cow<'_, BStr>, Utf8Error> {
41 match path {
42 Cow::Borrowed(path) => os_str_into_bstr(path).map(Cow::Borrowed),
43 Cow::Owned(path) => os_string_into_bstring(path).map(Cow::Owned),
44 }
45}
46
47pub fn try_into_bstr<'a>(path: impl Into<Cow<'a, Path>>) -> Result<Cow<'a, BStr>, Utf8Error> {
52 let path = path.into();
53 let path_str = match path {
54 Cow::Owned(path) => Cow::Owned({
55 #[cfg(unix)]
56 let p: BString = {
57 use std::os::unix::ffi::OsStringExt;
58 path.into_os_string().into_vec().into()
59 };
60 #[cfg(not(unix))]
61 let p: BString = path.into_os_string().into_string().map_err(|_| Utf8Error)?.into();
62 p
63 }),
64 Cow::Borrowed(path) => Cow::Borrowed({
65 #[cfg(unix)]
66 let p: &BStr = {
67 use std::os::unix::ffi::OsStrExt;
68 path.as_os_str().as_bytes().into()
69 };
70 #[cfg(not(unix))]
71 let p: &BStr = path.to_str().ok_or(Utf8Error)?.as_bytes().into();
72 p
73 }),
74 };
75 Ok(path_str)
76}
77
78pub fn into_bstr<'a>(path: impl Into<Cow<'a, Path>>) -> Cow<'a, BStr> {
80 try_into_bstr(path).expect("prefix path doesn't contain ill-formed UTF-8")
81}
82
83pub fn join_bstr_unix_pathsep<'a, 'b>(base: impl Into<Cow<'a, BStr>>, path: impl Into<&'b BStr>) -> Cow<'a, BStr> {
85 let mut base = base.into();
86 if !base.is_empty() && base.last() != Some(&b'/') {
87 base.to_mut().push(b'/');
88 }
89 base.to_mut().extend_from_slice(path.into());
90 base
91}
92
93pub fn try_from_byte_slice(input: &[u8]) -> Result<&Path, Utf8Error> {
99 #[cfg(unix)]
100 let p = {
101 use std::os::unix::ffi::OsStrExt;
102 OsStr::from_bytes(input).as_ref()
103 };
104 #[cfg(not(unix))]
105 let p = Path::new(std::str::from_utf8(input).map_err(|_| Utf8Error)?);
106 Ok(p)
107}
108
109pub fn try_from_bstr<'a>(input: impl Into<Cow<'a, BStr>>) -> Result<Cow<'a, Path>, Utf8Error> {
111 let input = input.into();
112 match input {
113 Cow::Borrowed(input) => try_from_byte_slice(input).map(Cow::Borrowed),
114 Cow::Owned(input) => try_from_bstring(input).map(Cow::Owned),
115 }
116}
117
118pub fn from_bstr<'a>(input: impl Into<Cow<'a, BStr>>) -> Cow<'a, Path> {
120 try_from_bstr(input).expect("prefix path doesn't contain ill-formed UTF-8")
121}
122
123pub fn try_from_bstring(input: impl Into<BString>) -> Result<PathBuf, Utf8Error> {
125 let input = input.into();
126 #[cfg(unix)]
127 let p = {
128 use std::os::unix::ffi::OsStringExt;
129 std::ffi::OsString::from_vec(input.into()).into()
130 };
131 #[cfg(not(unix))]
132 let p = {
133 use bstr::ByteVec;
134 PathBuf::from(
135 {
136 let v: Vec<_> = input.into();
137 v
138 }
139 .into_string()
140 .map_err(|_| Utf8Error)?,
141 )
142 };
143 Ok(p)
144}
145
146pub fn from_bstring(input: impl Into<BString>) -> PathBuf {
148 try_from_bstring(input).expect("well-formed UTF-8 on windows")
149}
150
151pub fn from_byte_slice(input: &[u8]) -> &Path {
153 try_from_byte_slice(input).expect("well-formed UTF-8 on windows")
154}
155
156fn replace<'a>(path: impl Into<Cow<'a, BStr>>, find: u8, replace: u8) -> Cow<'a, BStr> {
157 let path = path.into();
158 match path {
159 Cow::Owned(mut path) => {
160 for b in path.iter_mut().filter(|b| **b == find) {
161 *b = replace;
162 }
163 path.into()
164 }
165 Cow::Borrowed(path) => {
166 if !path.contains(&find) {
167 return path.into();
168 }
169 let mut path = path.to_owned();
170 for b in path.iter_mut().filter(|b| **b == find) {
171 *b = replace;
172 }
173 path.into()
174 }
175 }
176}
177
178pub fn to_native_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
180 #[cfg(not(windows))]
181 let p = to_unix_separators(path);
182 #[cfg(windows)]
183 let p = to_windows_separators(path);
184 p
185}
186
187pub fn to_native_path_on_windows<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, std::path::Path> {
190 #[cfg(not(windows))]
191 {
192 crate::from_bstr(path)
193 }
194 #[cfg(windows)]
195 {
196 crate::from_bstr(to_windows_separators(path))
197 }
198}
199
200pub fn to_unix_separators_on_windows<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
202 #[cfg(windows)]
203 {
204 to_unix_separators(path)
205 }
206 #[cfg(not(windows))]
207 {
208 path.into()
209 }
210}
211
212pub fn to_unix_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
216 replace(path, b'\\', b'/')
217}
218
219pub fn to_windows_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
223 replace(path, b'/', b'\\')
224}
225
226pub fn normalize<'a>(path: Cow<'a, Path>, current_dir: &Path) -> Option<Cow<'a, Path>> {
263 use std::path::Component::ParentDir;
264
265 if !path.components().any(|c| matches!(c, ParentDir)) {
266 return Some(path);
267 }
268 let mut current_dir_opt = Some(current_dir);
269 let was_relative = path.is_relative();
270 let components = path.components();
271 let mut path = PathBuf::new();
272 for component in components {
273 if let ParentDir = component {
274 let path_was_dot = path == Path::new(".");
275 if path.as_os_str().is_empty() || path_was_dot {
276 path.push(current_dir_opt.take()?);
277 }
278 if !path.pop() {
279 return None;
280 }
281 } else {
282 path.push(component);
283 }
284 }
285
286 if (path.as_os_str().is_empty() || path == current_dir) && was_relative {
287 Cow::Borrowed(Path::new("."))
288 } else {
289 path.into()
290 }
291 .into()
292}
293
294pub fn relativize_with_prefix<'a>(relative_path: &'a Path, prefix: &Path) -> Cow<'a, Path> {
305 if prefix.as_os_str().is_empty() {
306 return Cow::Borrowed(relative_path);
307 }
308 debug_assert!(
309 relative_path.components().all(|c| matches!(c, Component::Normal(_))),
310 "BUG: all input is expected to be normalized, but relative_path was not"
311 );
312 debug_assert!(
313 prefix.components().all(|c| matches!(c, Component::Normal(_))),
314 "BUG: all input is expected to be normalized, but prefix was not"
315 );
316
317 let mut buf = PathBuf::new();
318 let mut rpc = relative_path.components().peekable();
319 let mut equal_thus_far = true;
320 for pcomp in prefix.components() {
321 if equal_thus_far {
322 if let (Component::Normal(pname), Some(Component::Normal(rpname))) = (pcomp, rpc.peek()) {
323 if &pname == rpname {
324 rpc.next();
325 continue;
326 } else {
327 equal_thus_far = false;
328 }
329 }
330 }
331 buf.push(Component::ParentDir);
332 }
333 buf.extend(rpc);
334 if buf.as_os_str().is_empty() {
335 Cow::Borrowed(Path::new("."))
336 } else {
337 Cow::Owned(buf)
338 }
339}