1 | // Copyright (c) The camino Contributors |
2 | // SPDX-License-Identifier: MIT OR Apache-2.0 |
3 | |
4 | #![warn (missing_docs)] |
5 | #![cfg_attr (doc_cfg, feature(doc_cfg, doc_auto_cfg))] |
6 | |
7 | //! UTF-8 encoded paths. |
8 | //! |
9 | //! `camino` is an extension of the `std::path` module that adds new [`Utf8PathBuf`] and [`Utf8Path`] |
10 | //! types. These are like the standard library's [`PathBuf`] and [`Path`] types, except they are |
11 | //! guaranteed to only contain UTF-8 encoded data. Therefore, they expose the ability to get their |
12 | //! contents as strings, they implement `Display`, etc. |
13 | //! |
14 | //! The `std::path` types are not guaranteed to be valid UTF-8. This is the right decision for the standard library, |
15 | //! since it must be as general as possible. However, on all platforms, non-Unicode paths are vanishingly uncommon for a |
16 | //! number of reasons: |
17 | //! * Unicode won. There are still some legacy codebases that store paths in encodings like Shift-JIS, but most |
18 | //! have been converted to Unicode at this point. |
19 | //! * Unicode is the common subset of supported paths across Windows and Unix platforms. (On Windows, Rust stores paths |
20 | //! as [an extension to UTF-8](https://simonsapin.github.io/wtf-8/), and converts them to UTF-16 at Win32 |
21 | //! API boundaries.) |
22 | //! * There are already many systems, such as Cargo, that only support UTF-8 paths. If your own tool interacts with any such |
23 | //! system, you can assume that paths are valid UTF-8 without creating any additional burdens on consumers. |
24 | //! * The ["makefile problem"](https://www.mercurial-scm.org/wiki/EncodingStrategy#The_.22makefile_problem.22) |
25 | //! (which also applies to `Cargo.toml`, and any other metadata file that lists the names of other files) has *no general, |
26 | //! cross-platform solution* in systems that support non-UTF-8 paths. However, restricting paths to UTF-8 eliminates |
27 | //! this problem. |
28 | //! |
29 | //! Therefore, many programs that want to manipulate paths *do* assume they contain UTF-8 data, and convert them to `str`s |
30 | //! as necessary. However, because this invariant is not encoded in the `Path` type, conversions such as |
31 | //! `path.to_str().unwrap()` need to be repeated again and again, creating a frustrating experience. |
32 | //! |
33 | //! Instead, `camino` allows you to check that your paths are UTF-8 *once*, and then manipulate them |
34 | //! as valid UTF-8 from there on, avoiding repeated lossy and confusing conversions. |
35 | |
36 | // General note: we use #[allow(clippy::incompatible_msrv)] for code that's already guarded by a |
37 | // version-specific cfg conditional. |
38 | |
39 | use std::{ |
40 | borrow::{Borrow, Cow}, |
41 | cmp::Ordering, |
42 | convert::{Infallible, TryFrom, TryInto}, |
43 | error, |
44 | ffi::{OsStr, OsString}, |
45 | fmt, |
46 | fs::{self, Metadata}, |
47 | hash::{Hash, Hasher}, |
48 | io, |
49 | iter::FusedIterator, |
50 | ops::Deref, |
51 | path::*, |
52 | rc::Rc, |
53 | str::FromStr, |
54 | sync::Arc, |
55 | }; |
56 | |
57 | #[cfg (feature = "proptest1" )] |
58 | mod proptest_impls; |
59 | #[cfg (feature = "serde1" )] |
60 | mod serde_impls; |
61 | #[cfg (test)] |
62 | mod tests; |
63 | |
64 | /// An owned, mutable UTF-8 path (akin to [`String`]). |
65 | /// |
66 | /// This type provides methods like [`push`] and [`set_extension`] that mutate |
67 | /// the path in place. It also implements [`Deref`] to [`Utf8Path`], meaning that |
68 | /// all methods on [`Utf8Path`] slices are available on `Utf8PathBuf` values as well. |
69 | /// |
70 | /// [`push`]: Utf8PathBuf::push |
71 | /// [`set_extension`]: Utf8PathBuf::set_extension |
72 | /// |
73 | /// # Examples |
74 | /// |
75 | /// You can use [`push`] to build up a `Utf8PathBuf` from |
76 | /// components: |
77 | /// |
78 | /// ``` |
79 | /// use camino::Utf8PathBuf; |
80 | /// |
81 | /// let mut path = Utf8PathBuf::new(); |
82 | /// |
83 | /// path.push(r"C:\" ); |
84 | /// path.push("windows" ); |
85 | /// path.push("system32" ); |
86 | /// |
87 | /// path.set_extension("dll" ); |
88 | /// ``` |
89 | /// |
90 | /// However, [`push`] is best used for dynamic situations. This is a better way |
91 | /// to do this when you know all of the components ahead of time: |
92 | /// |
93 | /// ``` |
94 | /// use camino::Utf8PathBuf; |
95 | /// |
96 | /// let path: Utf8PathBuf = [r"C:\" , "windows" , "system32.dll" ].iter().collect(); |
97 | /// ``` |
98 | /// |
99 | /// We can still do better than this! Since these are all strings, we can use |
100 | /// `From::from`: |
101 | /// |
102 | /// ``` |
103 | /// use camino::Utf8PathBuf; |
104 | /// |
105 | /// let path = Utf8PathBuf::from(r"C:\windows\system32.dll" ); |
106 | /// ``` |
107 | /// |
108 | /// Which method works best depends on what kind of situation you're in. |
109 | // NB: Internal PathBuf must only contain utf8 data |
110 | #[derive (Clone, Default)] |
111 | #[cfg_attr (feature = "serde1" , derive(serde::Serialize, serde::Deserialize))] |
112 | #[cfg_attr (feature = "serde1" , serde(transparent))] |
113 | #[repr (transparent)] |
114 | pub struct Utf8PathBuf(PathBuf); |
115 | |
116 | impl Utf8PathBuf { |
117 | /// Allocates an empty `Utf8PathBuf`. |
118 | /// |
119 | /// # Examples |
120 | /// |
121 | /// ``` |
122 | /// use camino::Utf8PathBuf; |
123 | /// |
124 | /// let path = Utf8PathBuf::new(); |
125 | /// ``` |
126 | #[must_use ] |
127 | pub fn new() -> Utf8PathBuf { |
128 | Utf8PathBuf(PathBuf::new()) |
129 | } |
130 | |
131 | /// Creates a new `Utf8PathBuf` from a `PathBuf` containing valid UTF-8 characters. |
132 | /// |
133 | /// Errors with the original `PathBuf` if it is not valid UTF-8. |
134 | /// |
135 | /// For a version that returns a type that implements [`std::error::Error`], use the |
136 | /// `TryFrom<PathBuf>` impl. |
137 | /// |
138 | /// # Examples |
139 | /// |
140 | /// ``` |
141 | /// use camino::Utf8PathBuf; |
142 | /// use std::ffi::OsStr; |
143 | /// # #[cfg (unix)] |
144 | /// use std::os::unix::ffi::OsStrExt; |
145 | /// use std::path::PathBuf; |
146 | /// |
147 | /// let unicode_path = PathBuf::from("/valid/unicode" ); |
148 | /// Utf8PathBuf::from_path_buf(unicode_path).expect("valid Unicode path succeeded" ); |
149 | /// |
150 | /// // Paths on Unix can be non-UTF-8. |
151 | /// # #[cfg (unix)] |
152 | /// let non_unicode_str = OsStr::from_bytes(b" \xFF\xFF\xFF" ); |
153 | /// # #[cfg (unix)] |
154 | /// let non_unicode_path = PathBuf::from(non_unicode_str); |
155 | /// # #[cfg (unix)] |
156 | /// Utf8PathBuf::from_path_buf(non_unicode_path).expect_err("non-Unicode path failed" ); |
157 | /// ``` |
158 | pub fn from_path_buf(path: PathBuf) -> Result<Utf8PathBuf, PathBuf> { |
159 | match path.into_os_string().into_string() { |
160 | Ok(string) => Ok(Utf8PathBuf::from(string)), |
161 | Err(os_string) => Err(PathBuf::from(os_string)), |
162 | } |
163 | } |
164 | |
165 | /// Converts a `Utf8PathBuf` to a [`PathBuf`]. |
166 | /// |
167 | /// This is equivalent to the `From<Utf8PathBuf> for PathBuf` impl, but may aid in type |
168 | /// inference. |
169 | /// |
170 | /// # Examples |
171 | /// |
172 | /// ``` |
173 | /// use camino::Utf8PathBuf; |
174 | /// use std::path::PathBuf; |
175 | /// |
176 | /// let utf8_path_buf = Utf8PathBuf::from("foo.txt" ); |
177 | /// let std_path_buf = utf8_path_buf.into_std_path_buf(); |
178 | /// assert_eq!(std_path_buf.to_str(), Some("foo.txt" )); |
179 | /// |
180 | /// // Convert back to a Utf8PathBuf. |
181 | /// let new_utf8_path_buf = Utf8PathBuf::from_path_buf(std_path_buf).unwrap(); |
182 | /// assert_eq!(new_utf8_path_buf, "foo.txt" ); |
183 | /// ``` |
184 | #[must_use = "`self` will be dropped if the result is not used" ] |
185 | pub fn into_std_path_buf(self) -> PathBuf { |
186 | self.into() |
187 | } |
188 | |
189 | /// Creates a new `Utf8PathBuf` with a given capacity used to create the internal [`PathBuf`]. |
190 | /// See [`with_capacity`] defined on [`PathBuf`]. |
191 | /// |
192 | /// *Requires Rust 1.44 or newer.* |
193 | /// |
194 | /// # Examples |
195 | /// |
196 | /// ``` |
197 | /// use camino::Utf8PathBuf; |
198 | /// |
199 | /// let mut path = Utf8PathBuf::with_capacity(10); |
200 | /// let capacity = path.capacity(); |
201 | /// |
202 | /// // This push is done without reallocating |
203 | /// path.push(r"C:\" ); |
204 | /// |
205 | /// assert_eq!(capacity, path.capacity()); |
206 | /// ``` |
207 | /// |
208 | /// [`with_capacity`]: PathBuf::with_capacity |
209 | #[cfg (path_buf_capacity)] |
210 | #[allow (clippy::incompatible_msrv)] |
211 | #[must_use ] |
212 | pub fn with_capacity(capacity: usize) -> Utf8PathBuf { |
213 | Utf8PathBuf(PathBuf::with_capacity(capacity)) |
214 | } |
215 | |
216 | /// Coerces to a [`Utf8Path`] slice. |
217 | /// |
218 | /// # Examples |
219 | /// |
220 | /// ``` |
221 | /// use camino::{Utf8Path, Utf8PathBuf}; |
222 | /// |
223 | /// let p = Utf8PathBuf::from("/test" ); |
224 | /// assert_eq!(Utf8Path::new("/test" ), p.as_path()); |
225 | /// ``` |
226 | #[must_use ] |
227 | pub fn as_path(&self) -> &Utf8Path { |
228 | // SAFETY: every Utf8PathBuf constructor ensures that self is valid UTF-8 |
229 | unsafe { Utf8Path::assume_utf8(&self.0) } |
230 | } |
231 | |
232 | /// Extends `self` with `path`. |
233 | /// |
234 | /// If `path` is absolute, it replaces the current path. |
235 | /// |
236 | /// On Windows: |
237 | /// |
238 | /// * if `path` has a root but no prefix (e.g., `\windows`), it |
239 | /// replaces everything except for the prefix (if any) of `self`. |
240 | /// * if `path` has a prefix but no root, it replaces `self`. |
241 | /// |
242 | /// # Examples |
243 | /// |
244 | /// Pushing a relative path extends the existing path: |
245 | /// |
246 | /// ``` |
247 | /// use camino::Utf8PathBuf; |
248 | /// |
249 | /// let mut path = Utf8PathBuf::from("/tmp" ); |
250 | /// path.push("file.bk" ); |
251 | /// assert_eq!(path, Utf8PathBuf::from("/tmp/file.bk" )); |
252 | /// ``` |
253 | /// |
254 | /// Pushing an absolute path replaces the existing path: |
255 | /// |
256 | /// ``` |
257 | /// use camino::Utf8PathBuf; |
258 | /// |
259 | /// let mut path = Utf8PathBuf::from("/tmp" ); |
260 | /// path.push("/etc" ); |
261 | /// assert_eq!(path, Utf8PathBuf::from("/etc" )); |
262 | /// ``` |
263 | pub fn push(&mut self, path: impl AsRef<Utf8Path>) { |
264 | self.0.push(&path.as_ref().0) |
265 | } |
266 | |
267 | /// Truncates `self` to [`self.parent`]. |
268 | /// |
269 | /// Returns `false` and does nothing if [`self.parent`] is [`None`]. |
270 | /// Otherwise, returns `true`. |
271 | /// |
272 | /// [`self.parent`]: Utf8Path::parent |
273 | /// |
274 | /// # Examples |
275 | /// |
276 | /// ``` |
277 | /// use camino::{Utf8Path, Utf8PathBuf}; |
278 | /// |
279 | /// let mut p = Utf8PathBuf::from("/spirited/away.rs" ); |
280 | /// |
281 | /// p.pop(); |
282 | /// assert_eq!(Utf8Path::new("/spirited" ), p); |
283 | /// p.pop(); |
284 | /// assert_eq!(Utf8Path::new("/" ), p); |
285 | /// ``` |
286 | pub fn pop(&mut self) -> bool { |
287 | self.0.pop() |
288 | } |
289 | |
290 | /// Updates [`self.file_name`] to `file_name`. |
291 | /// |
292 | /// If [`self.file_name`] was [`None`], this is equivalent to pushing |
293 | /// `file_name`. |
294 | /// |
295 | /// Otherwise it is equivalent to calling [`pop`] and then pushing |
296 | /// `file_name`. The new path will be a sibling of the original path. |
297 | /// (That is, it will have the same parent.) |
298 | /// |
299 | /// [`self.file_name`]: Utf8Path::file_name |
300 | /// [`pop`]: Utf8PathBuf::pop |
301 | /// |
302 | /// # Examples |
303 | /// |
304 | /// ``` |
305 | /// use camino::Utf8PathBuf; |
306 | /// |
307 | /// let mut buf = Utf8PathBuf::from("/" ); |
308 | /// assert_eq!(buf.file_name(), None); |
309 | /// buf.set_file_name("bar" ); |
310 | /// assert_eq!(buf, Utf8PathBuf::from("/bar" )); |
311 | /// assert!(buf.file_name().is_some()); |
312 | /// buf.set_file_name("baz.txt" ); |
313 | /// assert_eq!(buf, Utf8PathBuf::from("/baz.txt" )); |
314 | /// ``` |
315 | pub fn set_file_name(&mut self, file_name: impl AsRef<str>) { |
316 | self.0.set_file_name(file_name.as_ref()) |
317 | } |
318 | |
319 | /// Updates [`self.extension`] to `extension`. |
320 | /// |
321 | /// Returns `false` and does nothing if [`self.file_name`] is [`None`], |
322 | /// returns `true` and updates the extension otherwise. |
323 | /// |
324 | /// If [`self.extension`] is [`None`], the extension is added; otherwise |
325 | /// it is replaced. |
326 | /// |
327 | /// [`self.file_name`]: Utf8Path::file_name |
328 | /// [`self.extension`]: Utf8Path::extension |
329 | /// |
330 | /// # Examples |
331 | /// |
332 | /// ``` |
333 | /// use camino::{Utf8Path, Utf8PathBuf}; |
334 | /// |
335 | /// let mut p = Utf8PathBuf::from("/feel/the" ); |
336 | /// |
337 | /// p.set_extension("force" ); |
338 | /// assert_eq!(Utf8Path::new("/feel/the.force" ), p.as_path()); |
339 | /// |
340 | /// p.set_extension("dark_side" ); |
341 | /// assert_eq!(Utf8Path::new("/feel/the.dark_side" ), p.as_path()); |
342 | /// ``` |
343 | pub fn set_extension(&mut self, extension: impl AsRef<str>) -> bool { |
344 | self.0.set_extension(extension.as_ref()) |
345 | } |
346 | |
347 | /// Consumes the `Utf8PathBuf`, yielding its internal [`String`] storage. |
348 | /// |
349 | /// # Examples |
350 | /// |
351 | /// ``` |
352 | /// use camino::Utf8PathBuf; |
353 | /// |
354 | /// let p = Utf8PathBuf::from("/the/head" ); |
355 | /// let s = p.into_string(); |
356 | /// assert_eq!(s, "/the/head" ); |
357 | /// ``` |
358 | #[must_use = "`self` will be dropped if the result is not used" ] |
359 | pub fn into_string(self) -> String { |
360 | self.into_os_string().into_string().unwrap() |
361 | } |
362 | |
363 | /// Consumes the `Utf8PathBuf`, yielding its internal [`OsString`] storage. |
364 | /// |
365 | /// # Examples |
366 | /// |
367 | /// ``` |
368 | /// use camino::Utf8PathBuf; |
369 | /// use std::ffi::OsStr; |
370 | /// |
371 | /// let p = Utf8PathBuf::from("/the/head" ); |
372 | /// let s = p.into_os_string(); |
373 | /// assert_eq!(s, OsStr::new("/the/head" )); |
374 | /// ``` |
375 | #[must_use = "`self` will be dropped if the result is not used" ] |
376 | pub fn into_os_string(self) -> OsString { |
377 | self.0.into_os_string() |
378 | } |
379 | |
380 | /// Converts this `Utf8PathBuf` into a [boxed](Box) [`Utf8Path`]. |
381 | #[must_use = "`self` will be dropped if the result is not used" ] |
382 | pub fn into_boxed_path(self) -> Box<Utf8Path> { |
383 | let ptr = Box::into_raw(self.0.into_boxed_path()) as *mut Utf8Path; |
384 | // SAFETY: |
385 | // * self is valid UTF-8 |
386 | // * ptr was constructed by consuming self so it represents an owned path |
387 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *mut Path to |
388 | // *mut Utf8Path is valid |
389 | unsafe { Box::from_raw(ptr) } |
390 | } |
391 | |
392 | /// Invokes [`capacity`] on the underlying instance of [`PathBuf`]. |
393 | /// |
394 | /// *Requires Rust 1.44 or newer.* |
395 | /// |
396 | /// [`capacity`]: PathBuf::capacity |
397 | #[cfg (path_buf_capacity)] |
398 | #[allow (clippy::incompatible_msrv)] |
399 | #[must_use ] |
400 | pub fn capacity(&self) -> usize { |
401 | self.0.capacity() |
402 | } |
403 | |
404 | /// Invokes [`clear`] on the underlying instance of [`PathBuf`]. |
405 | /// |
406 | /// *Requires Rust 1.44 or newer.* |
407 | /// |
408 | /// [`clear`]: PathBuf::clear |
409 | #[cfg (path_buf_capacity)] |
410 | #[allow (clippy::incompatible_msrv)] |
411 | pub fn clear(&mut self) { |
412 | self.0.clear() |
413 | } |
414 | |
415 | /// Invokes [`reserve`] on the underlying instance of [`PathBuf`]. |
416 | /// |
417 | /// *Requires Rust 1.44 or newer.* |
418 | /// |
419 | /// [`reserve`]: PathBuf::reserve |
420 | #[cfg (path_buf_capacity)] |
421 | #[allow (clippy::incompatible_msrv)] |
422 | pub fn reserve(&mut self, additional: usize) { |
423 | self.0.reserve(additional) |
424 | } |
425 | |
426 | /// Invokes [`try_reserve`] on the underlying instance of [`PathBuf`]. |
427 | /// |
428 | /// *Requires Rust 1.63 or newer.* |
429 | /// |
430 | /// [`try_reserve`]: PathBuf::try_reserve |
431 | #[cfg (try_reserve_2)] |
432 | #[allow (clippy::incompatible_msrv)] |
433 | #[inline ] |
434 | pub fn try_reserve( |
435 | &mut self, |
436 | additional: usize, |
437 | ) -> Result<(), std::collections::TryReserveError> { |
438 | self.0.try_reserve(additional) |
439 | } |
440 | |
441 | /// Invokes [`reserve_exact`] on the underlying instance of [`PathBuf`]. |
442 | /// |
443 | /// *Requires Rust 1.44 or newer.* |
444 | /// |
445 | /// [`reserve_exact`]: PathBuf::reserve_exact |
446 | #[cfg (path_buf_capacity)] |
447 | #[allow (clippy::incompatible_msrv)] |
448 | pub fn reserve_exact(&mut self, additional: usize) { |
449 | self.0.reserve_exact(additional) |
450 | } |
451 | |
452 | /// Invokes [`try_reserve_exact`] on the underlying instance of [`PathBuf`]. |
453 | /// |
454 | /// *Requires Rust 1.63 or newer.* |
455 | /// |
456 | /// [`try_reserve_exact`]: PathBuf::try_reserve_exact |
457 | #[cfg (try_reserve_2)] |
458 | #[allow (clippy::incompatible_msrv)] |
459 | #[inline ] |
460 | pub fn try_reserve_exact( |
461 | &mut self, |
462 | additional: usize, |
463 | ) -> Result<(), std::collections::TryReserveError> { |
464 | self.0.try_reserve_exact(additional) |
465 | } |
466 | |
467 | /// Invokes [`shrink_to_fit`] on the underlying instance of [`PathBuf`]. |
468 | /// |
469 | /// *Requires Rust 1.44 or newer.* |
470 | /// |
471 | /// [`shrink_to_fit`]: PathBuf::shrink_to_fit |
472 | #[cfg (path_buf_capacity)] |
473 | #[allow (clippy::incompatible_msrv)] |
474 | pub fn shrink_to_fit(&mut self) { |
475 | self.0.shrink_to_fit() |
476 | } |
477 | |
478 | /// Invokes [`shrink_to`] on the underlying instance of [`PathBuf`]. |
479 | /// |
480 | /// *Requires Rust 1.56 or newer.* |
481 | /// |
482 | /// [`shrink_to`]: PathBuf::shrink_to |
483 | #[cfg (shrink_to)] |
484 | #[allow (clippy::incompatible_msrv)] |
485 | #[inline ] |
486 | pub fn shrink_to(&mut self, min_capacity: usize) { |
487 | self.0.shrink_to(min_capacity) |
488 | } |
489 | } |
490 | |
491 | impl Deref for Utf8PathBuf { |
492 | type Target = Utf8Path; |
493 | |
494 | fn deref(&self) -> &Utf8Path { |
495 | self.as_path() |
496 | } |
497 | } |
498 | |
499 | /// *Requires Rust 1.68 or newer.* |
500 | #[cfg (path_buf_deref_mut)] |
501 | #[allow (clippy::incompatible_msrv)] |
502 | impl std::ops::DerefMut for Utf8PathBuf { |
503 | fn deref_mut(&mut self) -> &mut Self::Target { |
504 | unsafe { Utf8Path::assume_utf8_mut(&mut self.0) } |
505 | } |
506 | } |
507 | |
508 | impl fmt::Debug for Utf8PathBuf { |
509 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
510 | fmt::Debug::fmt(&**self, f) |
511 | } |
512 | } |
513 | |
514 | impl fmt::Display for Utf8PathBuf { |
515 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
516 | fmt::Display::fmt(self.as_str(), f) |
517 | } |
518 | } |
519 | |
520 | impl<P: AsRef<Utf8Path>> Extend<P> for Utf8PathBuf { |
521 | fn extend<I: IntoIterator<Item = P>>(&mut self, iter: I) { |
522 | for path: P in iter { |
523 | self.push(path); |
524 | } |
525 | } |
526 | } |
527 | |
528 | /// A slice of a UTF-8 path (akin to [`str`]). |
529 | /// |
530 | /// This type supports a number of operations for inspecting a path, including |
531 | /// breaking the path into its components (separated by `/` on Unix and by either |
532 | /// `/` or `\` on Windows), extracting the file name, determining whether the path |
533 | /// is absolute, and so on. |
534 | /// |
535 | /// This is an *unsized* type, meaning that it must always be used behind a |
536 | /// pointer like `&` or [`Box`]. For an owned version of this type, |
537 | /// see [`Utf8PathBuf`]. |
538 | /// |
539 | /// # Examples |
540 | /// |
541 | /// ``` |
542 | /// use camino::Utf8Path; |
543 | /// |
544 | /// // Note: this example does work on Windows |
545 | /// let path = Utf8Path::new("./foo/bar.txt" ); |
546 | /// |
547 | /// let parent = path.parent(); |
548 | /// assert_eq!(parent, Some(Utf8Path::new("./foo" ))); |
549 | /// |
550 | /// let file_stem = path.file_stem(); |
551 | /// assert_eq!(file_stem, Some("bar" )); |
552 | /// |
553 | /// let extension = path.extension(); |
554 | /// assert_eq!(extension, Some("txt" )); |
555 | /// ``` |
556 | // NB: Internal Path must only contain utf8 data |
557 | #[repr (transparent)] |
558 | pub struct Utf8Path(Path); |
559 | |
560 | impl Utf8Path { |
561 | /// Directly wraps a string slice as a `Utf8Path` slice. |
562 | /// |
563 | /// This is a cost-free conversion. |
564 | /// |
565 | /// # Examples |
566 | /// |
567 | /// ``` |
568 | /// use camino::Utf8Path; |
569 | /// |
570 | /// Utf8Path::new("foo.txt" ); |
571 | /// ``` |
572 | /// |
573 | /// You can create `Utf8Path`s from `String`s, or even other `Utf8Path`s: |
574 | /// |
575 | /// ``` |
576 | /// use camino::Utf8Path; |
577 | /// |
578 | /// let string = String::from("foo.txt" ); |
579 | /// let from_string = Utf8Path::new(&string); |
580 | /// let from_path = Utf8Path::new(&from_string); |
581 | /// assert_eq!(from_string, from_path); |
582 | /// ``` |
583 | pub fn new(s: &(impl AsRef<str> + ?Sized)) -> &Utf8Path { |
584 | let path = Path::new(s.as_ref()); |
585 | // SAFETY: s is a str which means it is always valid UTF-8 |
586 | unsafe { Utf8Path::assume_utf8(path) } |
587 | } |
588 | |
589 | /// Converts a [`Path`] to a `Utf8Path`. |
590 | /// |
591 | /// Returns `None` if the path is not valid UTF-8. |
592 | /// |
593 | /// For a version that returns a type that implements [`std::error::Error`], use the |
594 | /// [`TryFrom<&Path>`][tryfrom] impl. |
595 | /// |
596 | /// [tryfrom]: #impl-TryFrom<%26'a+Path>-for-%26'a+Utf8Path |
597 | /// |
598 | /// # Examples |
599 | /// |
600 | /// ``` |
601 | /// use camino::Utf8Path; |
602 | /// use std::ffi::OsStr; |
603 | /// # #[cfg (unix)] |
604 | /// use std::os::unix::ffi::OsStrExt; |
605 | /// use std::path::Path; |
606 | /// |
607 | /// let unicode_path = Path::new("/valid/unicode" ); |
608 | /// Utf8Path::from_path(unicode_path).expect("valid Unicode path succeeded" ); |
609 | /// |
610 | /// // Paths on Unix can be non-UTF-8. |
611 | /// # #[cfg (unix)] |
612 | /// let non_unicode_str = OsStr::from_bytes(b" \xFF\xFF\xFF" ); |
613 | /// # #[cfg (unix)] |
614 | /// let non_unicode_path = Path::new(non_unicode_str); |
615 | /// # #[cfg (unix)] |
616 | /// assert!(Utf8Path::from_path(non_unicode_path).is_none(), "non-Unicode path failed" ); |
617 | /// ``` |
618 | pub fn from_path(path: &Path) -> Option<&Utf8Path> { |
619 | path.as_os_str().to_str().map(Utf8Path::new) |
620 | } |
621 | |
622 | /// Converts a `Utf8Path` to a [`Path`]. |
623 | /// |
624 | /// This is equivalent to the `AsRef<&Path> for &Utf8Path` impl, but may aid in type inference. |
625 | /// |
626 | /// # Examples |
627 | /// |
628 | /// ``` |
629 | /// use camino::Utf8Path; |
630 | /// use std::path::Path; |
631 | /// |
632 | /// let utf8_path = Utf8Path::new("foo.txt" ); |
633 | /// let std_path: &Path = utf8_path.as_std_path(); |
634 | /// assert_eq!(std_path.to_str(), Some("foo.txt" )); |
635 | /// |
636 | /// // Convert back to a Utf8Path. |
637 | /// let new_utf8_path = Utf8Path::from_path(std_path).unwrap(); |
638 | /// assert_eq!(new_utf8_path, "foo.txt" ); |
639 | /// ``` |
640 | #[inline ] |
641 | pub fn as_std_path(&self) -> &Path { |
642 | self.as_ref() |
643 | } |
644 | |
645 | /// Yields the underlying [`str`] slice. |
646 | /// |
647 | /// Unlike [`Path::to_str`], this always returns a slice because the contents of a `Utf8Path` |
648 | /// are guaranteed to be valid UTF-8. |
649 | /// |
650 | /// # Examples |
651 | /// |
652 | /// ``` |
653 | /// use camino::Utf8Path; |
654 | /// |
655 | /// let s = Utf8Path::new("foo.txt" ).as_str(); |
656 | /// assert_eq!(s, "foo.txt" ); |
657 | /// ``` |
658 | /// |
659 | /// [`str`]: str |
660 | #[inline ] |
661 | #[must_use ] |
662 | pub fn as_str(&self) -> &str { |
663 | // SAFETY: every Utf8Path constructor ensures that self is valid UTF-8 |
664 | unsafe { str_assume_utf8(self.as_os_str()) } |
665 | } |
666 | |
667 | /// Yields the underlying [`OsStr`] slice. |
668 | /// |
669 | /// # Examples |
670 | /// |
671 | /// ``` |
672 | /// use camino::Utf8Path; |
673 | /// |
674 | /// let os_str = Utf8Path::new("foo.txt" ).as_os_str(); |
675 | /// assert_eq!(os_str, std::ffi::OsStr::new("foo.txt" )); |
676 | /// ``` |
677 | #[inline ] |
678 | #[must_use ] |
679 | pub fn as_os_str(&self) -> &OsStr { |
680 | self.0.as_os_str() |
681 | } |
682 | |
683 | /// Converts a `Utf8Path` to an owned [`Utf8PathBuf`]. |
684 | /// |
685 | /// # Examples |
686 | /// |
687 | /// ``` |
688 | /// use camino::{Utf8Path, Utf8PathBuf}; |
689 | /// |
690 | /// let path_buf = Utf8Path::new("foo.txt" ).to_path_buf(); |
691 | /// assert_eq!(path_buf, Utf8PathBuf::from("foo.txt" )); |
692 | /// ``` |
693 | #[inline ] |
694 | #[must_use = "this returns the result of the operation, \ |
695 | without modifying the original" ] |
696 | pub fn to_path_buf(&self) -> Utf8PathBuf { |
697 | Utf8PathBuf(self.0.to_path_buf()) |
698 | } |
699 | |
700 | /// Returns `true` if the `Utf8Path` is absolute, i.e., if it is independent of |
701 | /// the current directory. |
702 | /// |
703 | /// * On Unix, a path is absolute if it starts with the root, so |
704 | /// `is_absolute` and [`has_root`] are equivalent. |
705 | /// |
706 | /// * On Windows, a path is absolute if it has a prefix and starts with the |
707 | /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. |
708 | /// |
709 | /// # Examples |
710 | /// |
711 | /// ``` |
712 | /// use camino::Utf8Path; |
713 | /// |
714 | /// assert!(!Utf8Path::new("foo.txt" ).is_absolute()); |
715 | /// ``` |
716 | /// |
717 | /// [`has_root`]: Utf8Path::has_root |
718 | #[inline ] |
719 | #[must_use ] |
720 | pub fn is_absolute(&self) -> bool { |
721 | self.0.is_absolute() |
722 | } |
723 | |
724 | /// Returns `true` if the `Utf8Path` is relative, i.e., not absolute. |
725 | /// |
726 | /// See [`is_absolute`]'s documentation for more details. |
727 | /// |
728 | /// # Examples |
729 | /// |
730 | /// ``` |
731 | /// use camino::Utf8Path; |
732 | /// |
733 | /// assert!(Utf8Path::new("foo.txt" ).is_relative()); |
734 | /// ``` |
735 | /// |
736 | /// [`is_absolute`]: Utf8Path::is_absolute |
737 | #[inline ] |
738 | #[must_use ] |
739 | pub fn is_relative(&self) -> bool { |
740 | self.0.is_relative() |
741 | } |
742 | |
743 | /// Returns `true` if the `Utf8Path` has a root. |
744 | /// |
745 | /// * On Unix, a path has a root if it begins with `/`. |
746 | /// |
747 | /// * On Windows, a path has a root if it: |
748 | /// * has no prefix and begins with a separator, e.g., `\windows` |
749 | /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows` |
750 | /// * has any non-disk prefix, e.g., `\\server\share` |
751 | /// |
752 | /// # Examples |
753 | /// |
754 | /// ``` |
755 | /// use camino::Utf8Path; |
756 | /// |
757 | /// assert!(Utf8Path::new("/etc/passwd" ).has_root()); |
758 | /// ``` |
759 | #[inline ] |
760 | #[must_use ] |
761 | pub fn has_root(&self) -> bool { |
762 | self.0.has_root() |
763 | } |
764 | |
765 | /// Returns the `Path` without its final component, if there is one. |
766 | /// |
767 | /// Returns [`None`] if the path terminates in a root or prefix. |
768 | /// |
769 | /// # Examples |
770 | /// |
771 | /// ``` |
772 | /// use camino::Utf8Path; |
773 | /// |
774 | /// let path = Utf8Path::new("/foo/bar" ); |
775 | /// let parent = path.parent().unwrap(); |
776 | /// assert_eq!(parent, Utf8Path::new("/foo" )); |
777 | /// |
778 | /// let grand_parent = parent.parent().unwrap(); |
779 | /// assert_eq!(grand_parent, Utf8Path::new("/" )); |
780 | /// assert_eq!(grand_parent.parent(), None); |
781 | /// ``` |
782 | #[inline ] |
783 | #[must_use ] |
784 | pub fn parent(&self) -> Option<&Utf8Path> { |
785 | self.0.parent().map(|path| { |
786 | // SAFETY: self is valid UTF-8, so parent is valid UTF-8 as well |
787 | unsafe { Utf8Path::assume_utf8(path) } |
788 | }) |
789 | } |
790 | |
791 | /// Produces an iterator over `Utf8Path` and its ancestors. |
792 | /// |
793 | /// The iterator will yield the `Utf8Path` that is returned if the [`parent`] method is used zero |
794 | /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, |
795 | /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns |
796 | /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, |
797 | /// namely `&self`. |
798 | /// |
799 | /// # Examples |
800 | /// |
801 | /// ``` |
802 | /// use camino::Utf8Path; |
803 | /// |
804 | /// let mut ancestors = Utf8Path::new("/foo/bar" ).ancestors(); |
805 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo/bar" ))); |
806 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo" ))); |
807 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/" ))); |
808 | /// assert_eq!(ancestors.next(), None); |
809 | /// |
810 | /// let mut ancestors = Utf8Path::new("../foo/bar" ).ancestors(); |
811 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo/bar" ))); |
812 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo" ))); |
813 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new(".." ))); |
814 | /// assert_eq!(ancestors.next(), Some(Utf8Path::new("" ))); |
815 | /// assert_eq!(ancestors.next(), None); |
816 | /// ``` |
817 | /// |
818 | /// [`parent`]: Utf8Path::parent |
819 | #[inline ] |
820 | pub fn ancestors(&self) -> Utf8Ancestors<'_> { |
821 | Utf8Ancestors(self.0.ancestors()) |
822 | } |
823 | |
824 | /// Returns the final component of the `Utf8Path`, if there is one. |
825 | /// |
826 | /// If the path is a normal file, this is the file name. If it's the path of a directory, this |
827 | /// is the directory name. |
828 | /// |
829 | /// Returns [`None`] if the path terminates in `..`. |
830 | /// |
831 | /// # Examples |
832 | /// |
833 | /// ``` |
834 | /// use camino::Utf8Path; |
835 | /// |
836 | /// assert_eq!(Some("bin" ), Utf8Path::new("/usr/bin/" ).file_name()); |
837 | /// assert_eq!(Some("foo.txt" ), Utf8Path::new("tmp/foo.txt" ).file_name()); |
838 | /// assert_eq!(Some("foo.txt" ), Utf8Path::new("foo.txt/." ).file_name()); |
839 | /// assert_eq!(Some("foo.txt" ), Utf8Path::new("foo.txt/.//" ).file_name()); |
840 | /// assert_eq!(None, Utf8Path::new("foo.txt/.." ).file_name()); |
841 | /// assert_eq!(None, Utf8Path::new("/" ).file_name()); |
842 | /// ``` |
843 | #[inline ] |
844 | #[must_use ] |
845 | pub fn file_name(&self) -> Option<&str> { |
846 | self.0.file_name().map(|s| { |
847 | // SAFETY: self is valid UTF-8, so file_name is valid UTF-8 as well |
848 | unsafe { str_assume_utf8(s) } |
849 | }) |
850 | } |
851 | |
852 | /// Returns a path that, when joined onto `base`, yields `self`. |
853 | /// |
854 | /// # Errors |
855 | /// |
856 | /// If `base` is not a prefix of `self` (i.e., [`starts_with`] |
857 | /// returns `false`), returns [`Err`]. |
858 | /// |
859 | /// [`starts_with`]: Utf8Path::starts_with |
860 | /// |
861 | /// # Examples |
862 | /// |
863 | /// ``` |
864 | /// use camino::{Utf8Path, Utf8PathBuf}; |
865 | /// |
866 | /// let path = Utf8Path::new("/test/haha/foo.txt" ); |
867 | /// |
868 | /// assert_eq!(path.strip_prefix("/" ), Ok(Utf8Path::new("test/haha/foo.txt" ))); |
869 | /// assert_eq!(path.strip_prefix("/test" ), Ok(Utf8Path::new("haha/foo.txt" ))); |
870 | /// assert_eq!(path.strip_prefix("/test/" ), Ok(Utf8Path::new("haha/foo.txt" ))); |
871 | /// assert_eq!(path.strip_prefix("/test/haha/foo.txt" ), Ok(Utf8Path::new("" ))); |
872 | /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/" ), Ok(Utf8Path::new("" ))); |
873 | /// |
874 | /// assert!(path.strip_prefix("test" ).is_err()); |
875 | /// assert!(path.strip_prefix("/haha" ).is_err()); |
876 | /// |
877 | /// let prefix = Utf8PathBuf::from("/test/" ); |
878 | /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8Path::new("haha/foo.txt" ))); |
879 | /// ``` |
880 | #[inline ] |
881 | pub fn strip_prefix(&self, base: impl AsRef<Path>) -> Result<&Utf8Path, StripPrefixError> { |
882 | self.0.strip_prefix(base).map(|path| { |
883 | // SAFETY: self is valid UTF-8, and strip_prefix returns a part of self (or an empty |
884 | // string), so it is valid UTF-8 as well. |
885 | unsafe { Utf8Path::assume_utf8(path) } |
886 | }) |
887 | } |
888 | |
889 | /// Determines whether `base` is a prefix of `self`. |
890 | /// |
891 | /// Only considers whole path components to match. |
892 | /// |
893 | /// # Examples |
894 | /// |
895 | /// ``` |
896 | /// use camino::Utf8Path; |
897 | /// |
898 | /// let path = Utf8Path::new("/etc/passwd" ); |
899 | /// |
900 | /// assert!(path.starts_with("/etc" )); |
901 | /// assert!(path.starts_with("/etc/" )); |
902 | /// assert!(path.starts_with("/etc/passwd" )); |
903 | /// assert!(path.starts_with("/etc/passwd/" )); // extra slash is okay |
904 | /// assert!(path.starts_with("/etc/passwd///" )); // multiple extra slashes are okay |
905 | /// |
906 | /// assert!(!path.starts_with("/e" )); |
907 | /// assert!(!path.starts_with("/etc/passwd.txt" )); |
908 | /// |
909 | /// assert!(!Utf8Path::new("/etc/foo.rs" ).starts_with("/etc/foo" )); |
910 | /// ``` |
911 | #[inline ] |
912 | #[must_use ] |
913 | pub fn starts_with(&self, base: impl AsRef<Path>) -> bool { |
914 | self.0.starts_with(base) |
915 | } |
916 | |
917 | /// Determines whether `child` is a suffix of `self`. |
918 | /// |
919 | /// Only considers whole path components to match. |
920 | /// |
921 | /// # Examples |
922 | /// |
923 | /// ``` |
924 | /// use camino::Utf8Path; |
925 | /// |
926 | /// let path = Utf8Path::new("/etc/resolv.conf" ); |
927 | /// |
928 | /// assert!(path.ends_with("resolv.conf" )); |
929 | /// assert!(path.ends_with("etc/resolv.conf" )); |
930 | /// assert!(path.ends_with("/etc/resolv.conf" )); |
931 | /// |
932 | /// assert!(!path.ends_with("/resolv.conf" )); |
933 | /// assert!(!path.ends_with("conf" )); // use .extension() instead |
934 | /// ``` |
935 | #[inline ] |
936 | #[must_use ] |
937 | pub fn ends_with(&self, base: impl AsRef<Path>) -> bool { |
938 | self.0.ends_with(base) |
939 | } |
940 | |
941 | /// Extracts the stem (non-extension) portion of [`self.file_name`]. |
942 | /// |
943 | /// [`self.file_name`]: Utf8Path::file_name |
944 | /// |
945 | /// The stem is: |
946 | /// |
947 | /// * [`None`], if there is no file name; |
948 | /// * The entire file name if there is no embedded `.`; |
949 | /// * The entire file name if the file name begins with `.` and has no other `.`s within; |
950 | /// * Otherwise, the portion of the file name before the final `.` |
951 | /// |
952 | /// # Examples |
953 | /// |
954 | /// ``` |
955 | /// use camino::Utf8Path; |
956 | /// |
957 | /// assert_eq!("foo" , Utf8Path::new("foo.rs" ).file_stem().unwrap()); |
958 | /// assert_eq!("foo.tar" , Utf8Path::new("foo.tar.gz" ).file_stem().unwrap()); |
959 | /// ``` |
960 | #[inline ] |
961 | #[must_use ] |
962 | pub fn file_stem(&self) -> Option<&str> { |
963 | self.0.file_stem().map(|s| { |
964 | // SAFETY: self is valid UTF-8, so file_stem is valid UTF-8 as well |
965 | unsafe { str_assume_utf8(s) } |
966 | }) |
967 | } |
968 | |
969 | /// Extracts the extension of [`self.file_name`], if possible. |
970 | /// |
971 | /// The extension is: |
972 | /// |
973 | /// * [`None`], if there is no file name; |
974 | /// * [`None`], if there is no embedded `.`; |
975 | /// * [`None`], if the file name begins with `.` and has no other `.`s within; |
976 | /// * Otherwise, the portion of the file name after the final `.` |
977 | /// |
978 | /// [`self.file_name`]: Utf8Path::file_name |
979 | /// |
980 | /// # Examples |
981 | /// |
982 | /// ``` |
983 | /// use camino::Utf8Path; |
984 | /// |
985 | /// assert_eq!("rs" , Utf8Path::new("foo.rs" ).extension().unwrap()); |
986 | /// assert_eq!("gz" , Utf8Path::new("foo.tar.gz" ).extension().unwrap()); |
987 | /// ``` |
988 | #[inline ] |
989 | #[must_use ] |
990 | pub fn extension(&self) -> Option<&str> { |
991 | self.0.extension().map(|s| { |
992 | // SAFETY: self is valid UTF-8, so extension is valid UTF-8 as well |
993 | unsafe { str_assume_utf8(s) } |
994 | }) |
995 | } |
996 | |
997 | /// Creates an owned [`Utf8PathBuf`] with `path` adjoined to `self`. |
998 | /// |
999 | /// See [`Utf8PathBuf::push`] for more details on what it means to adjoin a path. |
1000 | /// |
1001 | /// # Examples |
1002 | /// |
1003 | /// ``` |
1004 | /// use camino::{Utf8Path, Utf8PathBuf}; |
1005 | /// |
1006 | /// assert_eq!(Utf8Path::new("/etc" ).join("passwd" ), Utf8PathBuf::from("/etc/passwd" )); |
1007 | /// ``` |
1008 | #[inline ] |
1009 | #[must_use ] |
1010 | pub fn join(&self, path: impl AsRef<Utf8Path>) -> Utf8PathBuf { |
1011 | Utf8PathBuf(self.0.join(&path.as_ref().0)) |
1012 | } |
1013 | |
1014 | /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. |
1015 | /// |
1016 | /// See [`PathBuf::push`] for more details on what it means to adjoin a path. |
1017 | /// |
1018 | /// # Examples |
1019 | /// |
1020 | /// ``` |
1021 | /// use camino::Utf8Path; |
1022 | /// use std::path::PathBuf; |
1023 | /// |
1024 | /// assert_eq!(Utf8Path::new("/etc" ).join_os("passwd" ), PathBuf::from("/etc/passwd" )); |
1025 | /// ``` |
1026 | #[inline ] |
1027 | #[must_use ] |
1028 | pub fn join_os(&self, path: impl AsRef<Path>) -> PathBuf { |
1029 | self.0.join(path) |
1030 | } |
1031 | |
1032 | /// Creates an owned [`Utf8PathBuf`] like `self` but with the given file name. |
1033 | /// |
1034 | /// See [`Utf8PathBuf::set_file_name`] for more details. |
1035 | /// |
1036 | /// # Examples |
1037 | /// |
1038 | /// ``` |
1039 | /// use camino::{Utf8Path, Utf8PathBuf}; |
1040 | /// |
1041 | /// let path = Utf8Path::new("/tmp/foo.txt" ); |
1042 | /// assert_eq!(path.with_file_name("bar.txt" ), Utf8PathBuf::from("/tmp/bar.txt" )); |
1043 | /// |
1044 | /// let path = Utf8Path::new("/tmp" ); |
1045 | /// assert_eq!(path.with_file_name("var" ), Utf8PathBuf::from("/var" )); |
1046 | /// ``` |
1047 | #[inline ] |
1048 | #[must_use ] |
1049 | pub fn with_file_name(&self, file_name: impl AsRef<str>) -> Utf8PathBuf { |
1050 | Utf8PathBuf(self.0.with_file_name(file_name.as_ref())) |
1051 | } |
1052 | |
1053 | /// Creates an owned [`Utf8PathBuf`] like `self` but with the given extension. |
1054 | /// |
1055 | /// See [`Utf8PathBuf::set_extension`] for more details. |
1056 | /// |
1057 | /// # Examples |
1058 | /// |
1059 | /// ``` |
1060 | /// use camino::{Utf8Path, Utf8PathBuf}; |
1061 | /// |
1062 | /// let path = Utf8Path::new("foo.rs" ); |
1063 | /// assert_eq!(path.with_extension("txt" ), Utf8PathBuf::from("foo.txt" )); |
1064 | /// |
1065 | /// let path = Utf8Path::new("foo.tar.gz" ); |
1066 | /// assert_eq!(path.with_extension("" ), Utf8PathBuf::from("foo.tar" )); |
1067 | /// assert_eq!(path.with_extension("xz" ), Utf8PathBuf::from("foo.tar.xz" )); |
1068 | /// assert_eq!(path.with_extension("" ).with_extension("txt" ), Utf8PathBuf::from("foo.txt" )); |
1069 | /// ``` |
1070 | #[inline ] |
1071 | pub fn with_extension(&self, extension: impl AsRef<str>) -> Utf8PathBuf { |
1072 | Utf8PathBuf(self.0.with_extension(extension.as_ref())) |
1073 | } |
1074 | |
1075 | /// Produces an iterator over the [`Utf8Component`]s of the path. |
1076 | /// |
1077 | /// When parsing the path, there is a small amount of normalization: |
1078 | /// |
1079 | /// * Repeated separators are ignored, so `a/b` and `a//b` both have |
1080 | /// `a` and `b` as components. |
1081 | /// |
1082 | /// * Occurrences of `.` are normalized away, except if they are at the |
1083 | /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and |
1084 | /// `a/b` all have `a` and `b` as components, but `./a/b` starts with |
1085 | /// an additional [`CurDir`] component. |
1086 | /// |
1087 | /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. |
1088 | /// |
1089 | /// Note that no other normalization takes place; in particular, `a/c` |
1090 | /// and `a/b/../c` are distinct, to account for the possibility that `b` |
1091 | /// is a symbolic link (so its parent isn't `a`). |
1092 | /// |
1093 | /// # Examples |
1094 | /// |
1095 | /// ``` |
1096 | /// use camino::{Utf8Component, Utf8Path}; |
1097 | /// |
1098 | /// let mut components = Utf8Path::new("/tmp/foo.txt" ).components(); |
1099 | /// |
1100 | /// assert_eq!(components.next(), Some(Utf8Component::RootDir)); |
1101 | /// assert_eq!(components.next(), Some(Utf8Component::Normal("tmp" ))); |
1102 | /// assert_eq!(components.next(), Some(Utf8Component::Normal("foo.txt" ))); |
1103 | /// assert_eq!(components.next(), None) |
1104 | /// ``` |
1105 | /// |
1106 | /// [`CurDir`]: Utf8Component::CurDir |
1107 | #[inline ] |
1108 | pub fn components(&self) -> Utf8Components { |
1109 | Utf8Components(self.0.components()) |
1110 | } |
1111 | |
1112 | /// Produces an iterator over the path's components viewed as [`str`] |
1113 | /// slices. |
1114 | /// |
1115 | /// For more information about the particulars of how the path is separated |
1116 | /// into components, see [`components`]. |
1117 | /// |
1118 | /// [`components`]: Utf8Path::components |
1119 | /// |
1120 | /// # Examples |
1121 | /// |
1122 | /// ``` |
1123 | /// use camino::Utf8Path; |
1124 | /// |
1125 | /// let mut it = Utf8Path::new("/tmp/foo.txt" ).iter(); |
1126 | /// assert_eq!(it.next(), Some(std::path::MAIN_SEPARATOR.to_string().as_str())); |
1127 | /// assert_eq!(it.next(), Some("tmp" )); |
1128 | /// assert_eq!(it.next(), Some("foo.txt" )); |
1129 | /// assert_eq!(it.next(), None) |
1130 | /// ``` |
1131 | #[inline ] |
1132 | pub fn iter(&self) -> Iter<'_> { |
1133 | Iter { |
1134 | inner: self.components(), |
1135 | } |
1136 | } |
1137 | |
1138 | /// Queries the file system to get information about a file, directory, etc. |
1139 | /// |
1140 | /// This function will traverse symbolic links to query information about the |
1141 | /// destination file. |
1142 | /// |
1143 | /// This is an alias to [`fs::metadata`]. |
1144 | /// |
1145 | /// # Examples |
1146 | /// |
1147 | /// ```no_run |
1148 | /// use camino::Utf8Path; |
1149 | /// |
1150 | /// let path = Utf8Path::new("/Minas/tirith" ); |
1151 | /// let metadata = path.metadata().expect("metadata call failed" ); |
1152 | /// println!("{:?}" , metadata.file_type()); |
1153 | /// ``` |
1154 | #[inline ] |
1155 | pub fn metadata(&self) -> io::Result<fs::Metadata> { |
1156 | self.0.metadata() |
1157 | } |
1158 | |
1159 | /// Queries the metadata about a file without following symlinks. |
1160 | /// |
1161 | /// This is an alias to [`fs::symlink_metadata`]. |
1162 | /// |
1163 | /// # Examples |
1164 | /// |
1165 | /// ```no_run |
1166 | /// use camino::Utf8Path; |
1167 | /// |
1168 | /// let path = Utf8Path::new("/Minas/tirith" ); |
1169 | /// let metadata = path.symlink_metadata().expect("symlink_metadata call failed" ); |
1170 | /// println!("{:?}" , metadata.file_type()); |
1171 | /// ``` |
1172 | #[inline ] |
1173 | pub fn symlink_metadata(&self) -> io::Result<fs::Metadata> { |
1174 | self.0.symlink_metadata() |
1175 | } |
1176 | |
1177 | /// Returns the canonical, absolute form of the path with all intermediate |
1178 | /// components normalized and symbolic links resolved. |
1179 | /// |
1180 | /// This returns a [`PathBuf`] because even if a symlink is valid Unicode, its target may not |
1181 | /// be. For a version that returns a [`Utf8PathBuf`], see |
1182 | /// [`canonicalize_utf8`](Self::canonicalize_utf8). |
1183 | /// |
1184 | /// This is an alias to [`fs::canonicalize`]. |
1185 | /// |
1186 | /// # Examples |
1187 | /// |
1188 | /// ```no_run |
1189 | /// use camino::Utf8Path; |
1190 | /// use std::path::PathBuf; |
1191 | /// |
1192 | /// let path = Utf8Path::new("/foo/test/../test/bar.rs" ); |
1193 | /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs" )); |
1194 | /// ``` |
1195 | #[inline ] |
1196 | pub fn canonicalize(&self) -> io::Result<PathBuf> { |
1197 | self.0.canonicalize() |
1198 | } |
1199 | |
1200 | /// Returns the canonical, absolute form of the path with all intermediate |
1201 | /// components normalized and symbolic links resolved. |
1202 | /// |
1203 | /// This method attempts to convert the resulting [`PathBuf`] into a [`Utf8PathBuf`]. For a |
1204 | /// version that does not attempt to do this conversion, see |
1205 | /// [`canonicalize`](Self::canonicalize). |
1206 | /// |
1207 | /// # Errors |
1208 | /// |
1209 | /// The I/O operation may return an error: see the [`fs::canonicalize`] |
1210 | /// documentation for more. |
1211 | /// |
1212 | /// If the resulting path is not UTF-8, an [`io::Error`] is returned with the |
1213 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a |
1214 | /// [`FromPathBufError`]. |
1215 | /// |
1216 | /// # Examples |
1217 | /// |
1218 | /// ```no_run |
1219 | /// use camino::{Utf8Path, Utf8PathBuf}; |
1220 | /// |
1221 | /// let path = Utf8Path::new("/foo/test/../test/bar.rs" ); |
1222 | /// assert_eq!(path.canonicalize_utf8().unwrap(), Utf8PathBuf::from("/foo/test/bar.rs" )); |
1223 | /// ``` |
1224 | pub fn canonicalize_utf8(&self) -> io::Result<Utf8PathBuf> { |
1225 | self.canonicalize() |
1226 | .and_then(|path| path.try_into().map_err(FromPathBufError::into_io_error)) |
1227 | } |
1228 | |
1229 | /// Reads a symbolic link, returning the file that the link points to. |
1230 | /// |
1231 | /// This returns a [`PathBuf`] because even if a symlink is valid Unicode, its target may not |
1232 | /// be. For a version that returns a [`Utf8PathBuf`], see |
1233 | /// [`read_link_utf8`](Self::read_link_utf8). |
1234 | /// |
1235 | /// This is an alias to [`fs::read_link`]. |
1236 | /// |
1237 | /// # Examples |
1238 | /// |
1239 | /// ```no_run |
1240 | /// use camino::Utf8Path; |
1241 | /// |
1242 | /// let path = Utf8Path::new("/laputa/sky_castle.rs" ); |
1243 | /// let path_link = path.read_link().expect("read_link call failed" ); |
1244 | /// ``` |
1245 | #[inline ] |
1246 | pub fn read_link(&self) -> io::Result<PathBuf> { |
1247 | self.0.read_link() |
1248 | } |
1249 | |
1250 | /// Reads a symbolic link, returning the file that the link points to. |
1251 | /// |
1252 | /// This method attempts to convert the resulting [`PathBuf`] into a [`Utf8PathBuf`]. For a |
1253 | /// version that does not attempt to do this conversion, see [`read_link`](Self::read_link). |
1254 | /// |
1255 | /// # Errors |
1256 | /// |
1257 | /// The I/O operation may return an error: see the [`fs::read_link`] |
1258 | /// documentation for more. |
1259 | /// |
1260 | /// If the resulting path is not UTF-8, an [`io::Error`] is returned with the |
1261 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a |
1262 | /// [`FromPathBufError`]. |
1263 | /// |
1264 | /// # Examples |
1265 | /// |
1266 | /// ```no_run |
1267 | /// use camino::Utf8Path; |
1268 | /// |
1269 | /// let path = Utf8Path::new("/laputa/sky_castle.rs" ); |
1270 | /// let path_link = path.read_link_utf8().expect("read_link call failed" ); |
1271 | /// ``` |
1272 | pub fn read_link_utf8(&self) -> io::Result<Utf8PathBuf> { |
1273 | self.read_link() |
1274 | .and_then(|path| path.try_into().map_err(FromPathBufError::into_io_error)) |
1275 | } |
1276 | |
1277 | /// Returns an iterator over the entries within a directory. |
1278 | /// |
1279 | /// The iterator will yield instances of [`io::Result`]`<`[`fs::DirEntry`]`>`. New |
1280 | /// errors may be encountered after an iterator is initially constructed. |
1281 | /// |
1282 | /// This is an alias to [`fs::read_dir`]. |
1283 | /// |
1284 | /// # Examples |
1285 | /// |
1286 | /// ```no_run |
1287 | /// use camino::Utf8Path; |
1288 | /// |
1289 | /// let path = Utf8Path::new("/laputa" ); |
1290 | /// for entry in path.read_dir().expect("read_dir call failed" ) { |
1291 | /// if let Ok(entry) = entry { |
1292 | /// println!("{:?}" , entry.path()); |
1293 | /// } |
1294 | /// } |
1295 | /// ``` |
1296 | #[inline ] |
1297 | pub fn read_dir(&self) -> io::Result<fs::ReadDir> { |
1298 | self.0.read_dir() |
1299 | } |
1300 | |
1301 | /// Returns an iterator over the entries within a directory. |
1302 | /// |
1303 | /// The iterator will yield instances of [`io::Result`]`<`[`Utf8DirEntry`]`>`. New |
1304 | /// errors may be encountered after an iterator is initially constructed. |
1305 | /// |
1306 | /// # Errors |
1307 | /// |
1308 | /// The I/O operation may return an error: see the [`fs::read_dir`] |
1309 | /// documentation for more. |
1310 | /// |
1311 | /// If a directory entry is not UTF-8, an [`io::Error`] is returned with the |
1312 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a |
1313 | /// [`FromPathBufError`]. |
1314 | /// |
1315 | /// # Examples |
1316 | /// |
1317 | /// ```no_run |
1318 | /// use camino::Utf8Path; |
1319 | /// |
1320 | /// let path = Utf8Path::new("/laputa" ); |
1321 | /// for entry in path.read_dir_utf8().expect("read_dir call failed" ) { |
1322 | /// if let Ok(entry) = entry { |
1323 | /// println!("{}" , entry.path()); |
1324 | /// } |
1325 | /// } |
1326 | /// ``` |
1327 | #[inline ] |
1328 | pub fn read_dir_utf8(&self) -> io::Result<ReadDirUtf8> { |
1329 | self.0.read_dir().map(|inner| ReadDirUtf8 { inner }) |
1330 | } |
1331 | |
1332 | /// Returns `true` if the path points at an existing entity. |
1333 | /// |
1334 | /// Warning: this method may be error-prone, consider using [`try_exists()`] instead! |
1335 | /// It also has a risk of introducing time-of-check to time-of-use (TOCTOU) bugs. |
1336 | /// |
1337 | /// This function will traverse symbolic links to query information about the |
1338 | /// destination file. In case of broken symbolic links this will return `false`. |
1339 | /// |
1340 | /// If you cannot access the directory containing the file, e.g., because of a |
1341 | /// permission error, this will return `false`. |
1342 | /// |
1343 | /// # Examples |
1344 | /// |
1345 | /// ```no_run |
1346 | /// use camino::Utf8Path; |
1347 | /// assert!(!Utf8Path::new("does_not_exist.txt" ).exists()); |
1348 | /// ``` |
1349 | /// |
1350 | /// # See Also |
1351 | /// |
1352 | /// This is a convenience function that coerces errors to false. If you want to |
1353 | /// check errors, call [`fs::metadata`]. |
1354 | /// |
1355 | /// [`try_exists()`]: Self::try_exists |
1356 | #[must_use ] |
1357 | #[inline ] |
1358 | pub fn exists(&self) -> bool { |
1359 | self.0.exists() |
1360 | } |
1361 | |
1362 | /// Returns `Ok(true)` if the path points at an existing entity. |
1363 | /// |
1364 | /// This function will traverse symbolic links to query information about the |
1365 | /// destination file. In case of broken symbolic links this will return `Ok(false)`. |
1366 | /// |
1367 | /// As opposed to the [`exists()`] method, this one doesn't silently ignore errors |
1368 | /// unrelated to the path not existing. (E.g. it will return `Err(_)` in case of permission |
1369 | /// denied on some of the parent directories.) |
1370 | /// |
1371 | /// Note that while this avoids some pitfalls of the `exists()` method, it still can not |
1372 | /// prevent time-of-check to time-of-use (TOCTOU) bugs. You should only use it in scenarios |
1373 | /// where those bugs are not an issue. |
1374 | /// |
1375 | /// # Examples |
1376 | /// |
1377 | /// ```no_run |
1378 | /// use camino::Utf8Path; |
1379 | /// assert!(!Utf8Path::new("does_not_exist.txt" ).try_exists().expect("Can't check existence of file does_not_exist.txt" )); |
1380 | /// assert!(Utf8Path::new("/root/secret_file.txt" ).try_exists().is_err()); |
1381 | /// ``` |
1382 | /// |
1383 | /// [`exists()`]: Self::exists |
1384 | #[inline ] |
1385 | pub fn try_exists(&self) -> io::Result<bool> { |
1386 | // Note: this block is written this way rather than with a pattern guard to appease Rust |
1387 | // 1.34. |
1388 | match fs::metadata(self) { |
1389 | Ok(_) => Ok(true), |
1390 | Err(error) => { |
1391 | if error.kind() == io::ErrorKind::NotFound { |
1392 | Ok(false) |
1393 | } else { |
1394 | Err(error) |
1395 | } |
1396 | } |
1397 | } |
1398 | } |
1399 | |
1400 | /// Returns `true` if the path exists on disk and is pointing at a regular file. |
1401 | /// |
1402 | /// This function will traverse symbolic links to query information about the |
1403 | /// destination file. In case of broken symbolic links this will return `false`. |
1404 | /// |
1405 | /// If you cannot access the directory containing the file, e.g., because of a |
1406 | /// permission error, this will return `false`. |
1407 | /// |
1408 | /// # Examples |
1409 | /// |
1410 | /// ```no_run |
1411 | /// use camino::Utf8Path; |
1412 | /// assert_eq!(Utf8Path::new("./is_a_directory/" ).is_file(), false); |
1413 | /// assert_eq!(Utf8Path::new("a_file.txt" ).is_file(), true); |
1414 | /// ``` |
1415 | /// |
1416 | /// # See Also |
1417 | /// |
1418 | /// This is a convenience function that coerces errors to false. If you want to |
1419 | /// check errors, call [`fs::metadata`] and handle its [`Result`]. Then call |
1420 | /// [`fs::Metadata::is_file`] if it was [`Ok`]. |
1421 | /// |
1422 | /// When the goal is simply to read from (or write to) the source, the most |
1423 | /// reliable way to test the source can be read (or written to) is to open |
1424 | /// it. Only using `is_file` can break workflows like `diff <( prog_a )` on |
1425 | /// a Unix-like system for example. See [`fs::File::open`] or |
1426 | /// [`fs::OpenOptions::open`] for more information. |
1427 | #[must_use ] |
1428 | #[inline ] |
1429 | pub fn is_file(&self) -> bool { |
1430 | self.0.is_file() |
1431 | } |
1432 | |
1433 | /// Returns `true` if the path exists on disk and is pointing at a directory. |
1434 | /// |
1435 | /// This function will traverse symbolic links to query information about the |
1436 | /// destination file. In case of broken symbolic links this will return `false`. |
1437 | /// |
1438 | /// If you cannot access the directory containing the file, e.g., because of a |
1439 | /// permission error, this will return `false`. |
1440 | /// |
1441 | /// # Examples |
1442 | /// |
1443 | /// ```no_run |
1444 | /// use camino::Utf8Path; |
1445 | /// assert_eq!(Utf8Path::new("./is_a_directory/" ).is_dir(), true); |
1446 | /// assert_eq!(Utf8Path::new("a_file.txt" ).is_dir(), false); |
1447 | /// ``` |
1448 | /// |
1449 | /// # See Also |
1450 | /// |
1451 | /// This is a convenience function that coerces errors to false. If you want to |
1452 | /// check errors, call [`fs::metadata`] and handle its [`Result`]. Then call |
1453 | /// [`fs::Metadata::is_dir`] if it was [`Ok`]. |
1454 | #[must_use ] |
1455 | #[inline ] |
1456 | pub fn is_dir(&self) -> bool { |
1457 | self.0.is_dir() |
1458 | } |
1459 | |
1460 | /// Returns `true` if the path exists on disk and is pointing at a symbolic link. |
1461 | /// |
1462 | /// This function will not traverse symbolic links. |
1463 | /// In case of a broken symbolic link this will also return true. |
1464 | /// |
1465 | /// If you cannot access the directory containing the file, e.g., because of a |
1466 | /// permission error, this will return false. |
1467 | /// |
1468 | /// # Examples |
1469 | /// |
1470 | #[cfg_attr (unix, doc = "```no_run" )] |
1471 | #[cfg_attr (not(unix), doc = "```ignore" )] |
1472 | /// use camino::Utf8Path; |
1473 | /// use std::os::unix::fs::symlink; |
1474 | /// |
1475 | /// let link_path = Utf8Path::new("link" ); |
1476 | /// symlink("/origin_does_not_exist/" , link_path).unwrap(); |
1477 | /// assert_eq!(link_path.is_symlink(), true); |
1478 | /// assert_eq!(link_path.exists(), false); |
1479 | /// ``` |
1480 | /// |
1481 | /// # See Also |
1482 | /// |
1483 | /// This is a convenience function that coerces errors to false. If you want to |
1484 | /// check errors, call [`Utf8Path::symlink_metadata`] and handle its [`Result`]. Then call |
1485 | /// [`fs::Metadata::is_symlink`] if it was [`Ok`]. |
1486 | #[must_use ] |
1487 | pub fn is_symlink(&self) -> bool { |
1488 | self.symlink_metadata() |
1489 | .map(|m| m.file_type().is_symlink()) |
1490 | .unwrap_or(false) |
1491 | } |
1492 | |
1493 | /// Converts a `Box<Utf8Path>` into a [`Utf8PathBuf`] without copying or allocating. |
1494 | #[must_use = "`self` will be dropped if the result is not used" ] |
1495 | #[inline ] |
1496 | pub fn into_path_buf(self: Box<Utf8Path>) -> Utf8PathBuf { |
1497 | let ptr = Box::into_raw(self) as *mut Path; |
1498 | // SAFETY: |
1499 | // * self is valid UTF-8 |
1500 | // * ptr was constructed by consuming self so it represents an owned path. |
1501 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from a *mut Utf8Path to a |
1502 | // *mut Path is valid. |
1503 | let boxed_path = unsafe { Box::from_raw(ptr) }; |
1504 | Utf8PathBuf(boxed_path.into_path_buf()) |
1505 | } |
1506 | |
1507 | // invariant: Path must be guaranteed to be utf-8 data |
1508 | #[inline ] |
1509 | unsafe fn assume_utf8(path: &Path) -> &Utf8Path { |
1510 | // SAFETY: Utf8Path is marked as #[repr(transparent)] so the conversion from a |
1511 | // *const Path to a *const Utf8Path is valid. |
1512 | &*(path as *const Path as *const Utf8Path) |
1513 | } |
1514 | |
1515 | #[cfg (path_buf_deref_mut)] |
1516 | #[inline ] |
1517 | unsafe fn assume_utf8_mut(path: &mut Path) -> &mut Utf8Path { |
1518 | &mut *(path as *mut Path as *mut Utf8Path) |
1519 | } |
1520 | } |
1521 | |
1522 | impl Clone for Box<Utf8Path> { |
1523 | fn clone(&self) -> Self { |
1524 | let boxed: Box<Path> = self.0.into(); |
1525 | let ptr: *mut Utf8Path = Box::into_raw(boxed) as *mut Utf8Path; |
1526 | // SAFETY: |
1527 | // * self is valid UTF-8 |
1528 | // * ptr was created by consuming a Box<Path> so it represents an rced pointer |
1529 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *mut Path to |
1530 | // *mut Utf8Path is valid |
1531 | unsafe { Box::from_raw(ptr) } |
1532 | } |
1533 | } |
1534 | |
1535 | impl fmt::Display for Utf8Path { |
1536 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1537 | fmt::Display::fmt(self.as_str(), f) |
1538 | } |
1539 | } |
1540 | |
1541 | impl fmt::Debug for Utf8Path { |
1542 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1543 | fmt::Debug::fmt(self.as_str(), f) |
1544 | } |
1545 | } |
1546 | |
1547 | /// An iterator over [`Utf8Path`] and its ancestors. |
1548 | /// |
1549 | /// This `struct` is created by the [`ancestors`] method on [`Utf8Path`]. |
1550 | /// See its documentation for more. |
1551 | /// |
1552 | /// # Examples |
1553 | /// |
1554 | /// ``` |
1555 | /// use camino::Utf8Path; |
1556 | /// |
1557 | /// let path = Utf8Path::new("/foo/bar" ); |
1558 | /// |
1559 | /// for ancestor in path.ancestors() { |
1560 | /// println!("{}" , ancestor); |
1561 | /// } |
1562 | /// ``` |
1563 | /// |
1564 | /// [`ancestors`]: Utf8Path::ancestors |
1565 | #[derive (Copy, Clone)] |
1566 | #[must_use = "iterators are lazy and do nothing unless consumed" ] |
1567 | #[repr (transparent)] |
1568 | pub struct Utf8Ancestors<'a>(Ancestors<'a>); |
1569 | |
1570 | impl<'a> fmt::Debug for Utf8Ancestors<'a> { |
1571 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1572 | fmt::Debug::fmt(&self.0, f) |
1573 | } |
1574 | } |
1575 | |
1576 | impl<'a> Iterator for Utf8Ancestors<'a> { |
1577 | type Item = &'a Utf8Path; |
1578 | |
1579 | #[inline ] |
1580 | fn next(&mut self) -> Option<Self::Item> { |
1581 | self.0.next().map(|path: &'a Path| { |
1582 | // SAFETY: Utf8Ancestors was constructed from a Utf8Path, so it is guaranteed to |
1583 | // be valid UTF-8 |
1584 | unsafe { Utf8Path::assume_utf8(path) } |
1585 | }) |
1586 | } |
1587 | } |
1588 | |
1589 | impl<'a> FusedIterator for Utf8Ancestors<'a> {} |
1590 | |
1591 | /// An iterator over the [`Utf8Component`]s of a [`Utf8Path`]. |
1592 | /// |
1593 | /// This `struct` is created by the [`components`] method on [`Utf8Path`]. |
1594 | /// See its documentation for more. |
1595 | /// |
1596 | /// # Examples |
1597 | /// |
1598 | /// ``` |
1599 | /// use camino::Utf8Path; |
1600 | /// |
1601 | /// let path = Utf8Path::new("/tmp/foo/bar.txt" ); |
1602 | /// |
1603 | /// for component in path.components() { |
1604 | /// println!("{:?}" , component); |
1605 | /// } |
1606 | /// ``` |
1607 | /// |
1608 | /// [`components`]: Utf8Path::components |
1609 | #[derive (Clone, Eq, Ord, PartialEq, PartialOrd)] |
1610 | #[must_use = "iterators are lazy and do nothing unless consumed" ] |
1611 | pub struct Utf8Components<'a>(Components<'a>); |
1612 | |
1613 | impl<'a> Utf8Components<'a> { |
1614 | /// Extracts a slice corresponding to the portion of the path remaining for iteration. |
1615 | /// |
1616 | /// # Examples |
1617 | /// |
1618 | /// ``` |
1619 | /// use camino::Utf8Path; |
1620 | /// |
1621 | /// let mut components = Utf8Path::new("/tmp/foo/bar.txt" ).components(); |
1622 | /// components.next(); |
1623 | /// components.next(); |
1624 | /// |
1625 | /// assert_eq!(Utf8Path::new("foo/bar.txt" ), components.as_path()); |
1626 | /// ``` |
1627 | #[must_use ] |
1628 | #[inline ] |
1629 | pub fn as_path(&self) -> &'a Utf8Path { |
1630 | // SAFETY: Utf8Components was constructed from a Utf8Path, so it is guaranteed to be valid |
1631 | // UTF-8 |
1632 | unsafe { Utf8Path::assume_utf8(self.0.as_path()) } |
1633 | } |
1634 | } |
1635 | |
1636 | impl<'a> Iterator for Utf8Components<'a> { |
1637 | type Item = Utf8Component<'a>; |
1638 | |
1639 | #[inline ] |
1640 | fn next(&mut self) -> Option<Self::Item> { |
1641 | self.0.next().map(|component: Component<'a>| { |
1642 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be |
1643 | // valid UTF-8 |
1644 | unsafe { Utf8Component::new(component) } |
1645 | }) |
1646 | } |
1647 | } |
1648 | |
1649 | impl<'a> FusedIterator for Utf8Components<'a> {} |
1650 | |
1651 | impl<'a> DoubleEndedIterator for Utf8Components<'a> { |
1652 | #[inline ] |
1653 | fn next_back(&mut self) -> Option<Self::Item> { |
1654 | self.0.next_back().map(|component: Component<'a>| { |
1655 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be |
1656 | // valid UTF-8 |
1657 | unsafe { Utf8Component::new(component) } |
1658 | }) |
1659 | } |
1660 | } |
1661 | |
1662 | impl<'a> fmt::Debug for Utf8Components<'a> { |
1663 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1664 | fmt::Debug::fmt(&self.0, f) |
1665 | } |
1666 | } |
1667 | |
1668 | impl AsRef<Utf8Path> for Utf8Components<'_> { |
1669 | #[inline ] |
1670 | fn as_ref(&self) -> &Utf8Path { |
1671 | self.as_path() |
1672 | } |
1673 | } |
1674 | |
1675 | impl AsRef<Path> for Utf8Components<'_> { |
1676 | #[inline ] |
1677 | fn as_ref(&self) -> &Path { |
1678 | self.as_path().as_ref() |
1679 | } |
1680 | } |
1681 | |
1682 | impl AsRef<str> for Utf8Components<'_> { |
1683 | #[inline ] |
1684 | fn as_ref(&self) -> &str { |
1685 | self.as_path().as_ref() |
1686 | } |
1687 | } |
1688 | |
1689 | impl AsRef<OsStr> for Utf8Components<'_> { |
1690 | #[inline ] |
1691 | fn as_ref(&self) -> &OsStr { |
1692 | self.as_path().as_os_str() |
1693 | } |
1694 | } |
1695 | |
1696 | /// An iterator over the [`Utf8Component`]s of a [`Utf8Path`], as [`str`] slices. |
1697 | /// |
1698 | /// This `struct` is created by the [`iter`] method on [`Utf8Path`]. |
1699 | /// See its documentation for more. |
1700 | /// |
1701 | /// [`iter`]: Utf8Path::iter |
1702 | #[derive (Clone)] |
1703 | #[must_use = "iterators are lazy and do nothing unless consumed" ] |
1704 | pub struct Iter<'a> { |
1705 | inner: Utf8Components<'a>, |
1706 | } |
1707 | |
1708 | impl fmt::Debug for Iter<'_> { |
1709 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1710 | struct DebugHelper<'a>(&'a Utf8Path); |
1711 | |
1712 | impl fmt::Debug for DebugHelper<'_> { |
1713 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1714 | f.debug_list().entries(self.0.iter()).finish() |
1715 | } |
1716 | } |
1717 | |
1718 | f&mut DebugTuple<'_, '_>.debug_tuple(name:"Iter" ) |
1719 | .field(&DebugHelper(self.as_path())) |
1720 | .finish() |
1721 | } |
1722 | } |
1723 | |
1724 | impl<'a> Iter<'a> { |
1725 | /// Extracts a slice corresponding to the portion of the path remaining for iteration. |
1726 | /// |
1727 | /// # Examples |
1728 | /// |
1729 | /// ``` |
1730 | /// use camino::Utf8Path; |
1731 | /// |
1732 | /// let mut iter = Utf8Path::new("/tmp/foo/bar.txt" ).iter(); |
1733 | /// iter.next(); |
1734 | /// iter.next(); |
1735 | /// |
1736 | /// assert_eq!(Utf8Path::new("foo/bar.txt" ), iter.as_path()); |
1737 | /// ``` |
1738 | #[must_use ] |
1739 | #[inline ] |
1740 | pub fn as_path(&self) -> &'a Utf8Path { |
1741 | self.inner.as_path() |
1742 | } |
1743 | } |
1744 | |
1745 | impl AsRef<Utf8Path> for Iter<'_> { |
1746 | #[inline ] |
1747 | fn as_ref(&self) -> &Utf8Path { |
1748 | self.as_path() |
1749 | } |
1750 | } |
1751 | |
1752 | impl AsRef<Path> for Iter<'_> { |
1753 | #[inline ] |
1754 | fn as_ref(&self) -> &Path { |
1755 | self.as_path().as_ref() |
1756 | } |
1757 | } |
1758 | |
1759 | impl AsRef<str> for Iter<'_> { |
1760 | #[inline ] |
1761 | fn as_ref(&self) -> &str { |
1762 | self.as_path().as_ref() |
1763 | } |
1764 | } |
1765 | |
1766 | impl AsRef<OsStr> for Iter<'_> { |
1767 | #[inline ] |
1768 | fn as_ref(&self) -> &OsStr { |
1769 | self.as_path().as_os_str() |
1770 | } |
1771 | } |
1772 | |
1773 | impl<'a> Iterator for Iter<'a> { |
1774 | type Item = &'a str; |
1775 | |
1776 | #[inline ] |
1777 | fn next(&mut self) -> Option<&'a str> { |
1778 | self.inner.next().map(|component: Utf8Component<'a>| component.as_str()) |
1779 | } |
1780 | } |
1781 | |
1782 | impl<'a> DoubleEndedIterator for Iter<'a> { |
1783 | #[inline ] |
1784 | fn next_back(&mut self) -> Option<&'a str> { |
1785 | self.inner.next_back().map(|component: Utf8Component<'a>| component.as_str()) |
1786 | } |
1787 | } |
1788 | |
1789 | impl FusedIterator for Iter<'_> {} |
1790 | |
1791 | /// A single component of a path. |
1792 | /// |
1793 | /// A `Utf8Component` roughly corresponds to a substring between path separators |
1794 | /// (`/` or `\`). |
1795 | /// |
1796 | /// This `enum` is created by iterating over [`Utf8Components`], which in turn is |
1797 | /// created by the [`components`](Utf8Path::components) method on [`Utf8Path`]. |
1798 | /// |
1799 | /// # Examples |
1800 | /// |
1801 | /// ```rust |
1802 | /// use camino::{Utf8Component, Utf8Path}; |
1803 | /// |
1804 | /// let path = Utf8Path::new("/tmp/foo/bar.txt" ); |
1805 | /// let components = path.components().collect::<Vec<_>>(); |
1806 | /// assert_eq!(&components, &[ |
1807 | /// Utf8Component::RootDir, |
1808 | /// Utf8Component::Normal("tmp" ), |
1809 | /// Utf8Component::Normal("foo" ), |
1810 | /// Utf8Component::Normal("bar.txt" ), |
1811 | /// ]); |
1812 | /// ``` |
1813 | #[derive (Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] |
1814 | pub enum Utf8Component<'a> { |
1815 | /// A Windows path prefix, e.g., `C:` or `\\server\share`. |
1816 | /// |
1817 | /// There is a large variety of prefix types, see [`Utf8Prefix`]'s documentation |
1818 | /// for more. |
1819 | /// |
1820 | /// Does not occur on Unix. |
1821 | Prefix(Utf8PrefixComponent<'a>), |
1822 | |
1823 | /// The root directory component, appears after any prefix and before anything else. |
1824 | /// |
1825 | /// It represents a separator that designates that a path starts from root. |
1826 | RootDir, |
1827 | |
1828 | /// A reference to the current directory, i.e., `.`. |
1829 | CurDir, |
1830 | |
1831 | /// A reference to the parent directory, i.e., `..`. |
1832 | ParentDir, |
1833 | |
1834 | /// A normal component, e.g., `a` and `b` in `a/b`. |
1835 | /// |
1836 | /// This variant is the most common one, it represents references to files |
1837 | /// or directories. |
1838 | Normal(&'a str), |
1839 | } |
1840 | |
1841 | impl<'a> Utf8Component<'a> { |
1842 | unsafe fn new(component: Component<'a>) -> Utf8Component<'a> { |
1843 | match component { |
1844 | Component::Prefix(prefix) => Utf8Component::Prefix(Utf8PrefixComponent(prefix)), |
1845 | Component::RootDir => Utf8Component::RootDir, |
1846 | Component::CurDir => Utf8Component::CurDir, |
1847 | Component::ParentDir => Utf8Component::ParentDir, |
1848 | Component::Normal(s) => Utf8Component::Normal(str_assume_utf8(s)), |
1849 | } |
1850 | } |
1851 | |
1852 | /// Extracts the underlying [`str`] slice. |
1853 | /// |
1854 | /// # Examples |
1855 | /// |
1856 | /// ``` |
1857 | /// use camino::Utf8Path; |
1858 | /// |
1859 | /// let path = Utf8Path::new("./tmp/foo/bar.txt" ); |
1860 | /// let components: Vec<_> = path.components().map(|comp| comp.as_str()).collect(); |
1861 | /// assert_eq!(&components, &["." , "tmp" , "foo" , "bar.txt" ]); |
1862 | /// ``` |
1863 | #[must_use ] |
1864 | #[inline ] |
1865 | pub fn as_str(&self) -> &'a str { |
1866 | // SAFETY: Utf8Component was constructed from a Utf8Path, so it is guaranteed to be |
1867 | // valid UTF-8 |
1868 | unsafe { str_assume_utf8(self.as_os_str()) } |
1869 | } |
1870 | |
1871 | /// Extracts the underlying [`OsStr`] slice. |
1872 | /// |
1873 | /// # Examples |
1874 | /// |
1875 | /// ``` |
1876 | /// use camino::Utf8Path; |
1877 | /// |
1878 | /// let path = Utf8Path::new("./tmp/foo/bar.txt" ); |
1879 | /// let components: Vec<_> = path.components().map(|comp| comp.as_os_str()).collect(); |
1880 | /// assert_eq!(&components, &["." , "tmp" , "foo" , "bar.txt" ]); |
1881 | /// ``` |
1882 | #[must_use ] |
1883 | pub fn as_os_str(&self) -> &'a OsStr { |
1884 | match *self { |
1885 | Utf8Component::Prefix(prefix) => prefix.as_os_str(), |
1886 | Utf8Component::RootDir => Component::RootDir.as_os_str(), |
1887 | Utf8Component::CurDir => Component::CurDir.as_os_str(), |
1888 | Utf8Component::ParentDir => Component::ParentDir.as_os_str(), |
1889 | Utf8Component::Normal(s) => OsStr::new(s), |
1890 | } |
1891 | } |
1892 | } |
1893 | |
1894 | impl<'a> fmt::Debug for Utf8Component<'a> { |
1895 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1896 | fmt::Debug::fmt(self.as_os_str(), f) |
1897 | } |
1898 | } |
1899 | |
1900 | impl<'a> fmt::Display for Utf8Component<'a> { |
1901 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1902 | fmt::Display::fmt(self.as_str(), f) |
1903 | } |
1904 | } |
1905 | |
1906 | impl AsRef<Utf8Path> for Utf8Component<'_> { |
1907 | #[inline ] |
1908 | fn as_ref(&self) -> &Utf8Path { |
1909 | self.as_str().as_ref() |
1910 | } |
1911 | } |
1912 | |
1913 | impl AsRef<Path> for Utf8Component<'_> { |
1914 | #[inline ] |
1915 | fn as_ref(&self) -> &Path { |
1916 | self.as_os_str().as_ref() |
1917 | } |
1918 | } |
1919 | |
1920 | impl AsRef<str> for Utf8Component<'_> { |
1921 | #[inline ] |
1922 | fn as_ref(&self) -> &str { |
1923 | self.as_str() |
1924 | } |
1925 | } |
1926 | |
1927 | impl AsRef<OsStr> for Utf8Component<'_> { |
1928 | #[inline ] |
1929 | fn as_ref(&self) -> &OsStr { |
1930 | self.as_os_str() |
1931 | } |
1932 | } |
1933 | |
1934 | /// Windows path prefixes, e.g., `C:` or `\\server\share`. |
1935 | /// |
1936 | /// Windows uses a variety of path prefix styles, including references to drive |
1937 | /// volumes (like `C:`), network shared folders (like `\\server\share`), and |
1938 | /// others. In addition, some path prefixes are "verbatim" (i.e., prefixed with |
1939 | /// `\\?\`), in which case `/` is *not* treated as a separator and essentially |
1940 | /// no normalization is performed. |
1941 | /// |
1942 | /// # Examples |
1943 | /// |
1944 | /// ``` |
1945 | /// use camino::{Utf8Component, Utf8Path, Utf8Prefix}; |
1946 | /// use camino::Utf8Prefix::*; |
1947 | /// |
1948 | /// fn get_path_prefix(s: &str) -> Utf8Prefix { |
1949 | /// let path = Utf8Path::new(s); |
1950 | /// match path.components().next().unwrap() { |
1951 | /// Utf8Component::Prefix(prefix_component) => prefix_component.kind(), |
1952 | /// _ => panic!(), |
1953 | /// } |
1954 | /// } |
1955 | /// |
1956 | /// # if cfg!(windows) { |
1957 | /// assert_eq!(Verbatim("pictures" ), get_path_prefix(r"\\?\pictures\kittens" )); |
1958 | /// assert_eq!(VerbatimUNC("server" , "share" ), get_path_prefix(r"\\?\UNC\server\share" )); |
1959 | /// assert_eq!(VerbatimDisk(b'C' ), get_path_prefix(r"\\?\c:\" )); |
1960 | /// assert_eq!(DeviceNS("BrainInterface" ), get_path_prefix(r"\\.\BrainInterface" )); |
1961 | /// assert_eq!(UNC("server" , "share" ), get_path_prefix(r"\\server\share" )); |
1962 | /// assert_eq!(Disk(b'C' ), get_path_prefix(r"C:\Users\Rust\Pictures\Ferris" )); |
1963 | /// # } |
1964 | /// ``` |
1965 | #[derive (Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] |
1966 | pub enum Utf8Prefix<'a> { |
1967 | /// Verbatim prefix, e.g., `\\?\cat_pics`. |
1968 | /// |
1969 | /// Verbatim prefixes consist of `\\?\` immediately followed by the given |
1970 | /// component. |
1971 | Verbatim(&'a str), |
1972 | |
1973 | /// Verbatim prefix using Windows' _**U**niform **N**aming **C**onvention_, |
1974 | /// e.g., `\\?\UNC\server\share`. |
1975 | /// |
1976 | /// Verbatim UNC prefixes consist of `\\?\UNC\` immediately followed by the |
1977 | /// server's hostname and a share name. |
1978 | VerbatimUNC(&'a str, &'a str), |
1979 | |
1980 | /// Verbatim disk prefix, e.g., `\\?\C:`. |
1981 | /// |
1982 | /// Verbatim disk prefixes consist of `\\?\` immediately followed by the |
1983 | /// drive letter and `:`. |
1984 | VerbatimDisk(u8), |
1985 | |
1986 | /// Device namespace prefix, e.g., `\\.\COM42`. |
1987 | /// |
1988 | /// Device namespace prefixes consist of `\\.\` immediately followed by the |
1989 | /// device name. |
1990 | DeviceNS(&'a str), |
1991 | |
1992 | /// Prefix using Windows' _**U**niform **N**aming **C**onvention_, e.g. |
1993 | /// `\\server\share`. |
1994 | /// |
1995 | /// UNC prefixes consist of the server's hostname and a share name. |
1996 | UNC(&'a str, &'a str), |
1997 | |
1998 | /// Prefix `C:` for the given disk drive. |
1999 | Disk(u8), |
2000 | } |
2001 | |
2002 | impl<'a> Utf8Prefix<'a> { |
2003 | /// Determines if the prefix is verbatim, i.e., begins with `\\?\`. |
2004 | /// |
2005 | /// # Examples |
2006 | /// |
2007 | /// ``` |
2008 | /// use camino::Utf8Prefix::*; |
2009 | /// |
2010 | /// assert!(Verbatim("pictures" ).is_verbatim()); |
2011 | /// assert!(VerbatimUNC("server" , "share" ).is_verbatim()); |
2012 | /// assert!(VerbatimDisk(b'C' ).is_verbatim()); |
2013 | /// assert!(!DeviceNS("BrainInterface" ).is_verbatim()); |
2014 | /// assert!(!UNC("server" , "share" ).is_verbatim()); |
2015 | /// assert!(!Disk(b'C' ).is_verbatim()); |
2016 | /// ``` |
2017 | #[must_use ] |
2018 | pub fn is_verbatim(&self) -> bool { |
2019 | use Utf8Prefix::*; |
2020 | match self { |
2021 | Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(..) => true, |
2022 | _ => false, |
2023 | } |
2024 | } |
2025 | } |
2026 | |
2027 | /// A structure wrapping a Windows path prefix as well as its unparsed string |
2028 | /// representation. |
2029 | /// |
2030 | /// In addition to the parsed [`Utf8Prefix`] information returned by [`kind`], |
2031 | /// `Utf8PrefixComponent` also holds the raw and unparsed [`str`] slice, |
2032 | /// returned by [`as_str`]. |
2033 | /// |
2034 | /// Instances of this `struct` can be obtained by matching against the |
2035 | /// [`Prefix` variant] on [`Utf8Component`]. |
2036 | /// |
2037 | /// Does not occur on Unix. |
2038 | /// |
2039 | /// # Examples |
2040 | /// |
2041 | /// ``` |
2042 | /// # if cfg!(windows) { |
2043 | /// use camino::{Utf8Component, Utf8Path, Utf8Prefix}; |
2044 | /// use std::ffi::OsStr; |
2045 | /// |
2046 | /// let path = Utf8Path::new(r"c:\you\later\" ); |
2047 | /// match path.components().next().unwrap() { |
2048 | /// Utf8Component::Prefix(prefix_component) => { |
2049 | /// assert_eq!(Utf8Prefix::Disk(b'C' ), prefix_component.kind()); |
2050 | /// assert_eq!("c:" , prefix_component.as_str()); |
2051 | /// } |
2052 | /// _ => unreachable!(), |
2053 | /// } |
2054 | /// # } |
2055 | /// ``` |
2056 | /// |
2057 | /// [`as_str`]: Utf8PrefixComponent::as_str |
2058 | /// [`kind`]: Utf8PrefixComponent::kind |
2059 | /// [`Prefix` variant]: Utf8Component::Prefix |
2060 | #[repr (transparent)] |
2061 | #[derive (Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] |
2062 | pub struct Utf8PrefixComponent<'a>(PrefixComponent<'a>); |
2063 | |
2064 | impl<'a> Utf8PrefixComponent<'a> { |
2065 | /// Returns the parsed prefix data. |
2066 | /// |
2067 | /// See [`Utf8Prefix`]'s documentation for more information on the different |
2068 | /// kinds of prefixes. |
2069 | #[must_use ] |
2070 | pub fn kind(&self) -> Utf8Prefix<'a> { |
2071 | // SAFETY for all the below unsafe blocks: the path self was originally constructed from was |
2072 | // UTF-8 so any parts of it are valid UTF-8 |
2073 | match self.0.kind() { |
2074 | Prefix::Verbatim(prefix) => Utf8Prefix::Verbatim(unsafe { str_assume_utf8(prefix) }), |
2075 | Prefix::VerbatimUNC(server, share) => { |
2076 | let server = unsafe { str_assume_utf8(server) }; |
2077 | let share = unsafe { str_assume_utf8(share) }; |
2078 | Utf8Prefix::VerbatimUNC(server, share) |
2079 | } |
2080 | Prefix::VerbatimDisk(drive) => Utf8Prefix::VerbatimDisk(drive), |
2081 | Prefix::DeviceNS(prefix) => Utf8Prefix::DeviceNS(unsafe { str_assume_utf8(prefix) }), |
2082 | Prefix::UNC(server, share) => { |
2083 | let server = unsafe { str_assume_utf8(server) }; |
2084 | let share = unsafe { str_assume_utf8(share) }; |
2085 | Utf8Prefix::UNC(server, share) |
2086 | } |
2087 | Prefix::Disk(drive) => Utf8Prefix::Disk(drive), |
2088 | } |
2089 | } |
2090 | |
2091 | /// Returns the [`str`] slice for this prefix. |
2092 | #[must_use ] |
2093 | #[inline ] |
2094 | pub fn as_str(&self) -> &'a str { |
2095 | // SAFETY: Utf8PrefixComponent was constructed from a Utf8Path, so it is guaranteed to be |
2096 | // valid UTF-8 |
2097 | unsafe { str_assume_utf8(self.as_os_str()) } |
2098 | } |
2099 | |
2100 | /// Returns the raw [`OsStr`] slice for this prefix. |
2101 | #[must_use ] |
2102 | #[inline ] |
2103 | pub fn as_os_str(&self) -> &'a OsStr { |
2104 | self.0.as_os_str() |
2105 | } |
2106 | } |
2107 | |
2108 | impl<'a> fmt::Debug for Utf8PrefixComponent<'a> { |
2109 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
2110 | fmt::Debug::fmt(&self.0, f) |
2111 | } |
2112 | } |
2113 | |
2114 | impl<'a> fmt::Display for Utf8PrefixComponent<'a> { |
2115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
2116 | fmt::Display::fmt(self.as_str(), f) |
2117 | } |
2118 | } |
2119 | |
2120 | // --- |
2121 | // read_dir_utf8 |
2122 | // --- |
2123 | |
2124 | /// Iterator over the entries in a directory. |
2125 | /// |
2126 | /// This iterator is returned from [`Utf8Path::read_dir_utf8`] and will yield instances of |
2127 | /// <code>[io::Result]<[Utf8DirEntry]></code>. Through a [`Utf8DirEntry`] information like the entry's path |
2128 | /// and possibly other metadata can be learned. |
2129 | /// |
2130 | /// The order in which this iterator returns entries is platform and filesystem |
2131 | /// dependent. |
2132 | /// |
2133 | /// # Errors |
2134 | /// |
2135 | /// This [`io::Result`] will be an [`Err`] if there's some sort of intermittent |
2136 | /// IO error during iteration. |
2137 | /// |
2138 | /// If a directory entry is not UTF-8, an [`io::Error`] is returned with the |
2139 | /// [`ErrorKind`](io::ErrorKind) set to `InvalidData` and the payload set to a [`FromPathBufError`]. |
2140 | #[derive (Debug)] |
2141 | pub struct ReadDirUtf8 { |
2142 | inner: fs::ReadDir, |
2143 | } |
2144 | |
2145 | impl Iterator for ReadDirUtf8 { |
2146 | type Item = io::Result<Utf8DirEntry>; |
2147 | |
2148 | fn next(&mut self) -> Option<io::Result<Utf8DirEntry>> { |
2149 | self.inner |
2150 | .next() |
2151 | .map(|entry: Result| entry.and_then(op:Utf8DirEntry::new)) |
2152 | } |
2153 | } |
2154 | |
2155 | /// Entries returned by the [`ReadDirUtf8`] iterator. |
2156 | /// |
2157 | /// An instance of `Utf8DirEntry` represents an entry inside of a directory on the filesystem. Each |
2158 | /// entry can be inspected via methods to learn about the full path or possibly other metadata. |
2159 | #[derive (Debug)] |
2160 | pub struct Utf8DirEntry { |
2161 | inner: fs::DirEntry, |
2162 | path: Utf8PathBuf, |
2163 | } |
2164 | |
2165 | impl Utf8DirEntry { |
2166 | fn new(inner: fs::DirEntry) -> io::Result<Self> { |
2167 | let path = inner |
2168 | .path() |
2169 | .try_into() |
2170 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; |
2171 | Ok(Self { inner, path }) |
2172 | } |
2173 | |
2174 | /// Returns the full path to the file that this entry represents. |
2175 | /// |
2176 | /// The full path is created by joining the original path to `read_dir` |
2177 | /// with the filename of this entry. |
2178 | /// |
2179 | /// # Examples |
2180 | /// |
2181 | /// ```no_run |
2182 | /// use camino::Utf8Path; |
2183 | /// |
2184 | /// fn main() -> std::io::Result<()> { |
2185 | /// for entry in Utf8Path::new("." ).read_dir_utf8()? { |
2186 | /// let dir = entry?; |
2187 | /// println!("{}" , dir.path()); |
2188 | /// } |
2189 | /// Ok(()) |
2190 | /// } |
2191 | /// ``` |
2192 | /// |
2193 | /// This prints output like: |
2194 | /// |
2195 | /// ```text |
2196 | /// ./whatever.txt |
2197 | /// ./foo.html |
2198 | /// ./hello_world.rs |
2199 | /// ``` |
2200 | /// |
2201 | /// The exact text, of course, depends on what files you have in `.`. |
2202 | #[inline ] |
2203 | pub fn path(&self) -> &Utf8Path { |
2204 | &self.path |
2205 | } |
2206 | |
2207 | /// Returns the metadata for the file that this entry points at. |
2208 | /// |
2209 | /// This function will not traverse symlinks if this entry points at a symlink. To traverse |
2210 | /// symlinks use [`Utf8Path::metadata`] or [`fs::File::metadata`]. |
2211 | /// |
2212 | /// # Platform-specific behavior |
2213 | /// |
2214 | /// On Windows this function is cheap to call (no extra system calls |
2215 | /// needed), but on Unix platforms this function is the equivalent of |
2216 | /// calling `symlink_metadata` on the path. |
2217 | /// |
2218 | /// # Examples |
2219 | /// |
2220 | /// ``` |
2221 | /// use camino::Utf8Path; |
2222 | /// |
2223 | /// if let Ok(entries) = Utf8Path::new("." ).read_dir_utf8() { |
2224 | /// for entry in entries { |
2225 | /// if let Ok(entry) = entry { |
2226 | /// // Here, `entry` is a `Utf8DirEntry`. |
2227 | /// if let Ok(metadata) = entry.metadata() { |
2228 | /// // Now let's show our entry's permissions! |
2229 | /// println!("{}: {:?}" , entry.path(), metadata.permissions()); |
2230 | /// } else { |
2231 | /// println!("Couldn't get metadata for {}" , entry.path()); |
2232 | /// } |
2233 | /// } |
2234 | /// } |
2235 | /// } |
2236 | /// ``` |
2237 | #[inline ] |
2238 | pub fn metadata(&self) -> io::Result<Metadata> { |
2239 | self.inner.metadata() |
2240 | } |
2241 | |
2242 | /// Returns the file type for the file that this entry points at. |
2243 | /// |
2244 | /// This function will not traverse symlinks if this entry points at a |
2245 | /// symlink. |
2246 | /// |
2247 | /// # Platform-specific behavior |
2248 | /// |
2249 | /// On Windows and most Unix platforms this function is free (no extra |
2250 | /// system calls needed), but some Unix platforms may require the equivalent |
2251 | /// call to `symlink_metadata` to learn about the target file type. |
2252 | /// |
2253 | /// # Examples |
2254 | /// |
2255 | /// ``` |
2256 | /// use camino::Utf8Path; |
2257 | /// |
2258 | /// if let Ok(entries) = Utf8Path::new("." ).read_dir_utf8() { |
2259 | /// for entry in entries { |
2260 | /// if let Ok(entry) = entry { |
2261 | /// // Here, `entry` is a `DirEntry`. |
2262 | /// if let Ok(file_type) = entry.file_type() { |
2263 | /// // Now let's show our entry's file type! |
2264 | /// println!("{}: {:?}" , entry.path(), file_type); |
2265 | /// } else { |
2266 | /// println!("Couldn't get file type for {}" , entry.path()); |
2267 | /// } |
2268 | /// } |
2269 | /// } |
2270 | /// } |
2271 | /// ``` |
2272 | #[inline ] |
2273 | pub fn file_type(&self) -> io::Result<fs::FileType> { |
2274 | self.inner.file_type() |
2275 | } |
2276 | |
2277 | /// Returns the bare file name of this directory entry without any other |
2278 | /// leading path component. |
2279 | /// |
2280 | /// # Examples |
2281 | /// |
2282 | /// ``` |
2283 | /// use camino::Utf8Path; |
2284 | /// |
2285 | /// if let Ok(entries) = Utf8Path::new("." ).read_dir_utf8() { |
2286 | /// for entry in entries { |
2287 | /// if let Ok(entry) = entry { |
2288 | /// // Here, `entry` is a `DirEntry`. |
2289 | /// println!("{}" , entry.file_name()); |
2290 | /// } |
2291 | /// } |
2292 | /// } |
2293 | /// ``` |
2294 | pub fn file_name(&self) -> &str { |
2295 | self.path |
2296 | .file_name() |
2297 | .expect("path created through DirEntry must have a filename" ) |
2298 | } |
2299 | |
2300 | /// Returns the original [`fs::DirEntry`] within this [`Utf8DirEntry`]. |
2301 | #[inline ] |
2302 | pub fn into_inner(self) -> fs::DirEntry { |
2303 | self.inner |
2304 | } |
2305 | |
2306 | /// Returns the full path to the file that this entry represents. |
2307 | /// |
2308 | /// This is analogous to [`path`], but moves ownership of the path. |
2309 | /// |
2310 | /// [`path`]: struct.Utf8DirEntry.html#method.path |
2311 | #[inline ] |
2312 | #[must_use = "`self` will be dropped if the result is not used" ] |
2313 | pub fn into_path(self) -> Utf8PathBuf { |
2314 | self.path |
2315 | } |
2316 | } |
2317 | |
2318 | impl From<String> for Utf8PathBuf { |
2319 | fn from(string: String) -> Utf8PathBuf { |
2320 | Utf8PathBuf(string.into()) |
2321 | } |
2322 | } |
2323 | |
2324 | impl FromStr for Utf8PathBuf { |
2325 | type Err = Infallible; |
2326 | |
2327 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
2328 | Ok(Utf8PathBuf(s.into())) |
2329 | } |
2330 | } |
2331 | |
2332 | // --- |
2333 | // From impls: borrowed -> borrowed |
2334 | // --- |
2335 | |
2336 | impl<'a> From<&'a str> for &'a Utf8Path { |
2337 | fn from(s: &'a str) -> &'a Utf8Path { |
2338 | Utf8Path::new(s) |
2339 | } |
2340 | } |
2341 | |
2342 | // --- |
2343 | // From impls: borrowed -> owned |
2344 | // --- |
2345 | |
2346 | impl<T: ?Sized + AsRef<str>> From<&T> for Utf8PathBuf { |
2347 | fn from(s: &T) -> Utf8PathBuf { |
2348 | Utf8PathBuf::from(s.as_ref().to_owned()) |
2349 | } |
2350 | } |
2351 | |
2352 | impl<T: ?Sized + AsRef<str>> From<&T> for Box<Utf8Path> { |
2353 | fn from(s: &T) -> Box<Utf8Path> { |
2354 | Utf8PathBuf::from(s).into_boxed_path() |
2355 | } |
2356 | } |
2357 | |
2358 | impl From<&'_ Utf8Path> for Arc<Utf8Path> { |
2359 | fn from(path: &Utf8Path) -> Arc<Utf8Path> { |
2360 | let arc: Arc<Path> = Arc::from(AsRef::<Path>::as_ref(self:path)); |
2361 | let ptr: *const Utf8Path = Arc::into_raw(this:arc) as *const Utf8Path; |
2362 | // SAFETY: |
2363 | // * path is valid UTF-8 |
2364 | // * ptr was created by consuming an Arc<Path> so it represents an arced pointer |
2365 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to |
2366 | // *const Utf8Path is valid |
2367 | unsafe { Arc::from_raw(ptr) } |
2368 | } |
2369 | } |
2370 | |
2371 | impl From<&'_ Utf8Path> for Rc<Utf8Path> { |
2372 | fn from(path: &Utf8Path) -> Rc<Utf8Path> { |
2373 | let rc: Rc<Path> = Rc::from(AsRef::<Path>::as_ref(self:path)); |
2374 | let ptr: *const Utf8Path = Rc::into_raw(this:rc) as *const Utf8Path; |
2375 | // SAFETY: |
2376 | // * path is valid UTF-8 |
2377 | // * ptr was created by consuming an Rc<Path> so it represents an rced pointer |
2378 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to |
2379 | // *const Utf8Path is valid |
2380 | unsafe { Rc::from_raw(ptr) } |
2381 | } |
2382 | } |
2383 | |
2384 | impl<'a> From<&'a Utf8Path> for Cow<'a, Utf8Path> { |
2385 | fn from(path: &'a Utf8Path) -> Cow<'a, Utf8Path> { |
2386 | Cow::Borrowed(path) |
2387 | } |
2388 | } |
2389 | |
2390 | impl From<&'_ Utf8Path> for Box<Path> { |
2391 | fn from(path: &Utf8Path) -> Box<Path> { |
2392 | AsRef::<Path>::as_ref(self:path).into() |
2393 | } |
2394 | } |
2395 | |
2396 | impl From<&'_ Utf8Path> for Arc<Path> { |
2397 | fn from(path: &Utf8Path) -> Arc<Path> { |
2398 | AsRef::<Path>::as_ref(self:path).into() |
2399 | } |
2400 | } |
2401 | |
2402 | impl From<&'_ Utf8Path> for Rc<Path> { |
2403 | fn from(path: &Utf8Path) -> Rc<Path> { |
2404 | AsRef::<Path>::as_ref(self:path).into() |
2405 | } |
2406 | } |
2407 | |
2408 | impl<'a> From<&'a Utf8Path> for Cow<'a, Path> { |
2409 | fn from(path: &'a Utf8Path) -> Cow<'a, Path> { |
2410 | Cow::Borrowed(path.as_ref()) |
2411 | } |
2412 | } |
2413 | |
2414 | // --- |
2415 | // From impls: owned -> owned |
2416 | // --- |
2417 | |
2418 | impl From<Box<Utf8Path>> for Utf8PathBuf { |
2419 | fn from(path: Box<Utf8Path>) -> Utf8PathBuf { |
2420 | path.into_path_buf() |
2421 | } |
2422 | } |
2423 | |
2424 | impl From<Utf8PathBuf> for Box<Utf8Path> { |
2425 | fn from(path: Utf8PathBuf) -> Box<Utf8Path> { |
2426 | path.into_boxed_path() |
2427 | } |
2428 | } |
2429 | |
2430 | impl<'a> From<Cow<'a, Utf8Path>> for Utf8PathBuf { |
2431 | fn from(path: Cow<'a, Utf8Path>) -> Utf8PathBuf { |
2432 | path.into_owned() |
2433 | } |
2434 | } |
2435 | |
2436 | impl From<Utf8PathBuf> for String { |
2437 | fn from(path: Utf8PathBuf) -> String { |
2438 | path.into_string() |
2439 | } |
2440 | } |
2441 | |
2442 | impl From<Utf8PathBuf> for OsString { |
2443 | fn from(path: Utf8PathBuf) -> OsString { |
2444 | path.into_os_string() |
2445 | } |
2446 | } |
2447 | |
2448 | impl<'a> From<Utf8PathBuf> for Cow<'a, Utf8Path> { |
2449 | fn from(path: Utf8PathBuf) -> Cow<'a, Utf8Path> { |
2450 | Cow::Owned(path) |
2451 | } |
2452 | } |
2453 | |
2454 | impl From<Utf8PathBuf> for Arc<Utf8Path> { |
2455 | fn from(path: Utf8PathBuf) -> Arc<Utf8Path> { |
2456 | let arc: Arc<Path> = Arc::from(path.0); |
2457 | let ptr: *const Utf8Path = Arc::into_raw(this:arc) as *const Utf8Path; |
2458 | // SAFETY: |
2459 | // * path is valid UTF-8 |
2460 | // * ptr was created by consuming an Arc<Path> so it represents an arced pointer |
2461 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to |
2462 | // *const Utf8Path is valid |
2463 | unsafe { Arc::from_raw(ptr) } |
2464 | } |
2465 | } |
2466 | |
2467 | impl From<Utf8PathBuf> for Rc<Utf8Path> { |
2468 | fn from(path: Utf8PathBuf) -> Rc<Utf8Path> { |
2469 | let rc: Rc<Path> = Rc::from(path.0); |
2470 | let ptr: *const Utf8Path = Rc::into_raw(this:rc) as *const Utf8Path; |
2471 | // SAFETY: |
2472 | // * path is valid UTF-8 |
2473 | // * ptr was created by consuming an Rc<Path> so it represents an rced pointer |
2474 | // * Utf8Path is marked as #[repr(transparent)] so the conversion from *const Path to |
2475 | // *const Utf8Path is valid |
2476 | unsafe { Rc::from_raw(ptr) } |
2477 | } |
2478 | } |
2479 | |
2480 | impl From<Utf8PathBuf> for PathBuf { |
2481 | fn from(path: Utf8PathBuf) -> PathBuf { |
2482 | path.0 |
2483 | } |
2484 | } |
2485 | |
2486 | impl From<Utf8PathBuf> for Box<Path> { |
2487 | fn from(path: Utf8PathBuf) -> Box<Path> { |
2488 | PathBuf::from(path).into_boxed_path() |
2489 | } |
2490 | } |
2491 | |
2492 | impl From<Utf8PathBuf> for Arc<Path> { |
2493 | fn from(path: Utf8PathBuf) -> Arc<Path> { |
2494 | PathBuf::from(path).into() |
2495 | } |
2496 | } |
2497 | |
2498 | impl From<Utf8PathBuf> for Rc<Path> { |
2499 | fn from(path: Utf8PathBuf) -> Rc<Path> { |
2500 | PathBuf::from(path).into() |
2501 | } |
2502 | } |
2503 | |
2504 | impl<'a> From<Utf8PathBuf> for Cow<'a, Path> { |
2505 | fn from(path: Utf8PathBuf) -> Cow<'a, Path> { |
2506 | PathBuf::from(path).into() |
2507 | } |
2508 | } |
2509 | |
2510 | // --- |
2511 | // TryFrom impls |
2512 | // --- |
2513 | |
2514 | impl TryFrom<PathBuf> for Utf8PathBuf { |
2515 | type Error = FromPathBufError; |
2516 | |
2517 | fn try_from(path: PathBuf) -> Result<Utf8PathBuf, Self::Error> { |
2518 | Utf8PathBuf::from_path_buf(path).map_err(|path: PathBuf| FromPathBufError { |
2519 | path, |
2520 | error: FromPathError(()), |
2521 | }) |
2522 | } |
2523 | } |
2524 | |
2525 | /// Converts a [`Path`] to a [`Utf8Path`]. |
2526 | /// |
2527 | /// Returns [`FromPathError`] if the path is not valid UTF-8. |
2528 | /// |
2529 | /// # Examples |
2530 | /// |
2531 | /// ``` |
2532 | /// use camino::Utf8Path; |
2533 | /// use std::convert::TryFrom; |
2534 | /// use std::ffi::OsStr; |
2535 | /// # #[cfg (unix)] |
2536 | /// use std::os::unix::ffi::OsStrExt; |
2537 | /// use std::path::Path; |
2538 | /// |
2539 | /// let unicode_path = Path::new("/valid/unicode" ); |
2540 | /// <&Utf8Path>::try_from(unicode_path).expect("valid Unicode path succeeded" ); |
2541 | /// |
2542 | /// // Paths on Unix can be non-UTF-8. |
2543 | /// # #[cfg (unix)] |
2544 | /// let non_unicode_str = OsStr::from_bytes(b" \xFF\xFF\xFF" ); |
2545 | /// # #[cfg (unix)] |
2546 | /// let non_unicode_path = Path::new(non_unicode_str); |
2547 | /// # #[cfg (unix)] |
2548 | /// assert!(<&Utf8Path>::try_from(non_unicode_path).is_err(), "non-Unicode path failed" ); |
2549 | /// ``` |
2550 | impl<'a> TryFrom<&'a Path> for &'a Utf8Path { |
2551 | type Error = FromPathError; |
2552 | |
2553 | fn try_from(path: &'a Path) -> Result<&'a Utf8Path, Self::Error> { |
2554 | Utf8Path::from_path(path).ok_or(err:FromPathError(())) |
2555 | } |
2556 | } |
2557 | |
2558 | /// A possible error value while converting a [`PathBuf`] to a [`Utf8PathBuf`]. |
2559 | /// |
2560 | /// Produced by the `TryFrom<PathBuf>` implementation for [`Utf8PathBuf`]. |
2561 | /// |
2562 | /// # Examples |
2563 | /// |
2564 | /// ``` |
2565 | /// use camino::{Utf8PathBuf, FromPathBufError}; |
2566 | /// use std::convert::{TryFrom, TryInto}; |
2567 | /// use std::ffi::OsStr; |
2568 | /// # #[cfg (unix)] |
2569 | /// use std::os::unix::ffi::OsStrExt; |
2570 | /// use std::path::PathBuf; |
2571 | /// |
2572 | /// let unicode_path = PathBuf::from("/valid/unicode" ); |
2573 | /// let utf8_path_buf: Utf8PathBuf = unicode_path.try_into().expect("valid Unicode path succeeded" ); |
2574 | /// |
2575 | /// // Paths on Unix can be non-UTF-8. |
2576 | /// # #[cfg (unix)] |
2577 | /// let non_unicode_str = OsStr::from_bytes(b" \xFF\xFF\xFF" ); |
2578 | /// # #[cfg (unix)] |
2579 | /// let non_unicode_path = PathBuf::from(non_unicode_str); |
2580 | /// # #[cfg (unix)] |
2581 | /// let err: FromPathBufError = Utf8PathBuf::try_from(non_unicode_path.clone()) |
2582 | /// .expect_err("non-Unicode path failed" ); |
2583 | /// # #[cfg (unix)] |
2584 | /// assert_eq!(err.as_path(), &non_unicode_path); |
2585 | /// # #[cfg (unix)] |
2586 | /// assert_eq!(err.into_path_buf(), non_unicode_path); |
2587 | /// ``` |
2588 | #[derive (Clone, Debug, Eq, PartialEq)] |
2589 | pub struct FromPathBufError { |
2590 | path: PathBuf, |
2591 | error: FromPathError, |
2592 | } |
2593 | |
2594 | impl FromPathBufError { |
2595 | /// Returns the [`Path`] slice that was attempted to be converted to [`Utf8PathBuf`]. |
2596 | #[inline ] |
2597 | pub fn as_path(&self) -> &Path { |
2598 | &self.path |
2599 | } |
2600 | |
2601 | /// Returns the [`PathBuf`] that was attempted to be converted to [`Utf8PathBuf`]. |
2602 | #[inline ] |
2603 | pub fn into_path_buf(self) -> PathBuf { |
2604 | self.path |
2605 | } |
2606 | |
2607 | /// Fetches a [`FromPathError`] for more about the conversion failure. |
2608 | /// |
2609 | /// At the moment this struct does not contain any additional information, but is provided for |
2610 | /// completeness. |
2611 | #[inline ] |
2612 | pub fn from_path_error(&self) -> FromPathError { |
2613 | self.error |
2614 | } |
2615 | |
2616 | /// Converts self into a [`std::io::Error`] with kind |
2617 | /// [`InvalidData`](io::ErrorKind::InvalidData). |
2618 | /// |
2619 | /// Many users of `FromPathBufError` will want to convert it into an `io::Error`. This is a |
2620 | /// convenience method to do that. |
2621 | pub fn into_io_error(self) -> io::Error { |
2622 | // NOTE: we don't currently implement `From<FromPathBufError> for io::Error` because we want |
2623 | // to ensure the user actually desires that conversion. |
2624 | io::Error::new(io::ErrorKind::InvalidData, self) |
2625 | } |
2626 | } |
2627 | |
2628 | impl fmt::Display for FromPathBufError { |
2629 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
2630 | write!(f, "PathBuf contains invalid UTF-8: {}" , self.path.display()) |
2631 | } |
2632 | } |
2633 | |
2634 | impl error::Error for FromPathBufError { |
2635 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
2636 | Some(&self.error) |
2637 | } |
2638 | } |
2639 | |
2640 | /// A possible error value while converting a [`Path`] to a [`Utf8Path`]. |
2641 | /// |
2642 | /// Produced by the `TryFrom<&Path>` implementation for [`&Utf8Path`](Utf8Path). |
2643 | /// |
2644 | /// |
2645 | /// # Examples |
2646 | /// |
2647 | /// ``` |
2648 | /// use camino::{Utf8Path, FromPathError}; |
2649 | /// use std::convert::{TryFrom, TryInto}; |
2650 | /// use std::ffi::OsStr; |
2651 | /// # #[cfg (unix)] |
2652 | /// use std::os::unix::ffi::OsStrExt; |
2653 | /// use std::path::Path; |
2654 | /// |
2655 | /// let unicode_path = Path::new("/valid/unicode" ); |
2656 | /// let utf8_path: &Utf8Path = unicode_path.try_into().expect("valid Unicode path succeeded" ); |
2657 | /// |
2658 | /// // Paths on Unix can be non-UTF-8. |
2659 | /// # #[cfg (unix)] |
2660 | /// let non_unicode_str = OsStr::from_bytes(b" \xFF\xFF\xFF" ); |
2661 | /// # #[cfg (unix)] |
2662 | /// let non_unicode_path = Path::new(non_unicode_str); |
2663 | /// # #[cfg (unix)] |
2664 | /// let err: FromPathError = <&Utf8Path>::try_from(non_unicode_path) |
2665 | /// .expect_err("non-Unicode path failed" ); |
2666 | /// ``` |
2667 | #[derive (Copy, Clone, Debug, Eq, PartialEq)] |
2668 | pub struct FromPathError(()); |
2669 | |
2670 | impl FromPathError { |
2671 | /// Converts self into a [`std::io::Error`] with kind |
2672 | /// [`InvalidData`](io::ErrorKind::InvalidData). |
2673 | /// |
2674 | /// Many users of `FromPathError` will want to convert it into an `io::Error`. This is a |
2675 | /// convenience method to do that. |
2676 | pub fn into_io_error(self) -> io::Error { |
2677 | // NOTE: we don't currently implement `From<FromPathBufError> for io::Error` because we want |
2678 | // to ensure the user actually desires that conversion. |
2679 | io::Error::new(kind:io::ErrorKind::InvalidData, self) |
2680 | } |
2681 | } |
2682 | |
2683 | impl fmt::Display for FromPathError { |
2684 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
2685 | write!(f, "Path contains invalid UTF-8" ) |
2686 | } |
2687 | } |
2688 | |
2689 | impl error::Error for FromPathError { |
2690 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
2691 | None |
2692 | } |
2693 | } |
2694 | |
2695 | // --- |
2696 | // AsRef impls |
2697 | // --- |
2698 | |
2699 | impl AsRef<Utf8Path> for Utf8Path { |
2700 | #[inline ] |
2701 | fn as_ref(&self) -> &Utf8Path { |
2702 | self |
2703 | } |
2704 | } |
2705 | |
2706 | impl AsRef<Utf8Path> for Utf8PathBuf { |
2707 | #[inline ] |
2708 | fn as_ref(&self) -> &Utf8Path { |
2709 | self.as_path() |
2710 | } |
2711 | } |
2712 | |
2713 | impl AsRef<Utf8Path> for str { |
2714 | #[inline ] |
2715 | fn as_ref(&self) -> &Utf8Path { |
2716 | Utf8Path::new(self) |
2717 | } |
2718 | } |
2719 | |
2720 | impl AsRef<Utf8Path> for String { |
2721 | #[inline ] |
2722 | fn as_ref(&self) -> &Utf8Path { |
2723 | Utf8Path::new(self) |
2724 | } |
2725 | } |
2726 | |
2727 | impl AsRef<Path> for Utf8Path { |
2728 | #[inline ] |
2729 | fn as_ref(&self) -> &Path { |
2730 | &self.0 |
2731 | } |
2732 | } |
2733 | |
2734 | impl AsRef<Path> for Utf8PathBuf { |
2735 | #[inline ] |
2736 | fn as_ref(&self) -> &Path { |
2737 | &self.0 |
2738 | } |
2739 | } |
2740 | |
2741 | impl AsRef<str> for Utf8Path { |
2742 | #[inline ] |
2743 | fn as_ref(&self) -> &str { |
2744 | self.as_str() |
2745 | } |
2746 | } |
2747 | |
2748 | impl AsRef<str> for Utf8PathBuf { |
2749 | #[inline ] |
2750 | fn as_ref(&self) -> &str { |
2751 | self.as_str() |
2752 | } |
2753 | } |
2754 | |
2755 | impl AsRef<OsStr> for Utf8Path { |
2756 | #[inline ] |
2757 | fn as_ref(&self) -> &OsStr { |
2758 | self.as_os_str() |
2759 | } |
2760 | } |
2761 | |
2762 | impl AsRef<OsStr> for Utf8PathBuf { |
2763 | #[inline ] |
2764 | fn as_ref(&self) -> &OsStr { |
2765 | self.as_os_str() |
2766 | } |
2767 | } |
2768 | |
2769 | // --- |
2770 | // Borrow and ToOwned |
2771 | // --- |
2772 | |
2773 | impl Borrow<Utf8Path> for Utf8PathBuf { |
2774 | #[inline ] |
2775 | fn borrow(&self) -> &Utf8Path { |
2776 | self.as_path() |
2777 | } |
2778 | } |
2779 | |
2780 | impl ToOwned for Utf8Path { |
2781 | type Owned = Utf8PathBuf; |
2782 | |
2783 | #[inline ] |
2784 | fn to_owned(&self) -> Utf8PathBuf { |
2785 | self.to_path_buf() |
2786 | } |
2787 | } |
2788 | |
2789 | impl<P: AsRef<Utf8Path>> std::iter::FromIterator<P> for Utf8PathBuf { |
2790 | fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Utf8PathBuf { |
2791 | let mut buf: Utf8PathBuf = Utf8PathBuf::new(); |
2792 | buf.extend(iter); |
2793 | buf |
2794 | } |
2795 | } |
2796 | |
2797 | // --- |
2798 | // [Partial]Eq, [Partial]Ord, Hash |
2799 | // --- |
2800 | |
2801 | impl PartialEq for Utf8PathBuf { |
2802 | #[inline ] |
2803 | fn eq(&self, other: &Utf8PathBuf) -> bool { |
2804 | self.components() == other.components() |
2805 | } |
2806 | } |
2807 | |
2808 | impl Eq for Utf8PathBuf {} |
2809 | |
2810 | impl Hash for Utf8PathBuf { |
2811 | #[inline ] |
2812 | fn hash<H: Hasher>(&self, state: &mut H) { |
2813 | self.as_path().hash(state) |
2814 | } |
2815 | } |
2816 | |
2817 | impl PartialOrd for Utf8PathBuf { |
2818 | #[inline ] |
2819 | fn partial_cmp(&self, other: &Utf8PathBuf) -> Option<Ordering> { |
2820 | Some(self.cmp(other)) |
2821 | } |
2822 | } |
2823 | |
2824 | impl Ord for Utf8PathBuf { |
2825 | fn cmp(&self, other: &Utf8PathBuf) -> Ordering { |
2826 | self.components().cmp(other.components()) |
2827 | } |
2828 | } |
2829 | |
2830 | impl PartialEq for Utf8Path { |
2831 | #[inline ] |
2832 | fn eq(&self, other: &Utf8Path) -> bool { |
2833 | self.components().eq(other.components()) |
2834 | } |
2835 | } |
2836 | |
2837 | impl Eq for Utf8Path {} |
2838 | |
2839 | impl Hash for Utf8Path { |
2840 | fn hash<H: Hasher>(&self, state: &mut H) { |
2841 | for component: Utf8Component<'_> in self.components() { |
2842 | component.hash(state) |
2843 | } |
2844 | } |
2845 | } |
2846 | |
2847 | impl PartialOrd for Utf8Path { |
2848 | #[inline ] |
2849 | fn partial_cmp(&self, other: &Utf8Path) -> Option<Ordering> { |
2850 | Some(self.cmp(other)) |
2851 | } |
2852 | } |
2853 | |
2854 | impl Ord for Utf8Path { |
2855 | fn cmp(&self, other: &Utf8Path) -> Ordering { |
2856 | self.components().cmp(other.components()) |
2857 | } |
2858 | } |
2859 | |
2860 | impl<'a> IntoIterator for &'a Utf8PathBuf { |
2861 | type Item = &'a str; |
2862 | type IntoIter = Iter<'a>; |
2863 | #[inline ] |
2864 | fn into_iter(self) -> Iter<'a> { |
2865 | self.iter() |
2866 | } |
2867 | } |
2868 | |
2869 | impl<'a> IntoIterator for &'a Utf8Path { |
2870 | type Item = &'a str; |
2871 | type IntoIter = Iter<'a>; |
2872 | #[inline ] |
2873 | fn into_iter(self) -> Iter<'a> { |
2874 | self.iter() |
2875 | } |
2876 | } |
2877 | |
2878 | macro_rules! impl_cmp { |
2879 | ($lhs:ty, $rhs: ty) => { |
2880 | #[allow(clippy::extra_unused_lifetimes)] |
2881 | impl<'a, 'b> PartialEq<$rhs> for $lhs { |
2882 | #[inline] |
2883 | fn eq(&self, other: &$rhs) -> bool { |
2884 | <Utf8Path as PartialEq>::eq(self, other) |
2885 | } |
2886 | } |
2887 | |
2888 | #[allow(clippy::extra_unused_lifetimes)] |
2889 | impl<'a, 'b> PartialEq<$lhs> for $rhs { |
2890 | #[inline] |
2891 | fn eq(&self, other: &$lhs) -> bool { |
2892 | <Utf8Path as PartialEq>::eq(self, other) |
2893 | } |
2894 | } |
2895 | |
2896 | #[allow(clippy::extra_unused_lifetimes)] |
2897 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { |
2898 | #[inline] |
2899 | fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> { |
2900 | <Utf8Path as PartialOrd>::partial_cmp(self, other) |
2901 | } |
2902 | } |
2903 | |
2904 | #[allow(clippy::extra_unused_lifetimes)] |
2905 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { |
2906 | #[inline] |
2907 | fn partial_cmp(&self, other: &$lhs) -> Option<Ordering> { |
2908 | <Utf8Path as PartialOrd>::partial_cmp(self, other) |
2909 | } |
2910 | } |
2911 | }; |
2912 | } |
2913 | |
2914 | impl_cmp!(Utf8PathBuf, Utf8Path); |
2915 | impl_cmp!(Utf8PathBuf, &'a Utf8Path); |
2916 | impl_cmp!(Cow<'a, Utf8Path>, Utf8Path); |
2917 | impl_cmp!(Cow<'a, Utf8Path>, &'b Utf8Path); |
2918 | impl_cmp!(Cow<'a, Utf8Path>, Utf8PathBuf); |
2919 | |
2920 | macro_rules! impl_cmp_std_path { |
2921 | ($lhs:ty, $rhs: ty) => { |
2922 | #[allow(clippy::extra_unused_lifetimes)] |
2923 | impl<'a, 'b> PartialEq<$rhs> for $lhs { |
2924 | #[inline] |
2925 | fn eq(&self, other: &$rhs) -> bool { |
2926 | <Path as PartialEq>::eq(self.as_ref(), other) |
2927 | } |
2928 | } |
2929 | |
2930 | #[allow(clippy::extra_unused_lifetimes)] |
2931 | impl<'a, 'b> PartialEq<$lhs> for $rhs { |
2932 | #[inline] |
2933 | fn eq(&self, other: &$lhs) -> bool { |
2934 | <Path as PartialEq>::eq(self, other.as_ref()) |
2935 | } |
2936 | } |
2937 | |
2938 | #[allow(clippy::extra_unused_lifetimes)] |
2939 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { |
2940 | #[inline] |
2941 | fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> { |
2942 | <Path as PartialOrd>::partial_cmp(self.as_ref(), other) |
2943 | } |
2944 | } |
2945 | |
2946 | #[allow(clippy::extra_unused_lifetimes)] |
2947 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { |
2948 | #[inline] |
2949 | fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> { |
2950 | <Path as PartialOrd>::partial_cmp(self, other.as_ref()) |
2951 | } |
2952 | } |
2953 | }; |
2954 | } |
2955 | |
2956 | impl_cmp_std_path!(Utf8PathBuf, Path); |
2957 | impl_cmp_std_path!(Utf8PathBuf, &'a Path); |
2958 | impl_cmp_std_path!(Utf8PathBuf, Cow<'a, Path>); |
2959 | impl_cmp_std_path!(Utf8PathBuf, PathBuf); |
2960 | impl_cmp_std_path!(Utf8Path, Path); |
2961 | impl_cmp_std_path!(Utf8Path, &'a Path); |
2962 | impl_cmp_std_path!(Utf8Path, Cow<'a, Path>); |
2963 | impl_cmp_std_path!(Utf8Path, PathBuf); |
2964 | impl_cmp_std_path!(&'a Utf8Path, Path); |
2965 | impl_cmp_std_path!(&'a Utf8Path, Cow<'b, Path>); |
2966 | impl_cmp_std_path!(&'a Utf8Path, PathBuf); |
2967 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) |
2968 | |
2969 | macro_rules! impl_cmp_str { |
2970 | ($lhs:ty, $rhs: ty) => { |
2971 | #[allow(clippy::extra_unused_lifetimes)] |
2972 | impl<'a, 'b> PartialEq<$rhs> for $lhs { |
2973 | #[inline] |
2974 | fn eq(&self, other: &$rhs) -> bool { |
2975 | <Utf8Path as PartialEq>::eq(self, Utf8Path::new(other)) |
2976 | } |
2977 | } |
2978 | |
2979 | #[allow(clippy::extra_unused_lifetimes)] |
2980 | impl<'a, 'b> PartialEq<$lhs> for $rhs { |
2981 | #[inline] |
2982 | fn eq(&self, other: &$lhs) -> bool { |
2983 | <Utf8Path as PartialEq>::eq(Utf8Path::new(self), other) |
2984 | } |
2985 | } |
2986 | |
2987 | #[allow(clippy::extra_unused_lifetimes)] |
2988 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { |
2989 | #[inline] |
2990 | fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> { |
2991 | <Utf8Path as PartialOrd>::partial_cmp(self, Utf8Path::new(other)) |
2992 | } |
2993 | } |
2994 | |
2995 | #[allow(clippy::extra_unused_lifetimes)] |
2996 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { |
2997 | #[inline] |
2998 | fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> { |
2999 | <Utf8Path as PartialOrd>::partial_cmp(Utf8Path::new(self), other) |
3000 | } |
3001 | } |
3002 | }; |
3003 | } |
3004 | |
3005 | impl_cmp_str!(Utf8PathBuf, str); |
3006 | impl_cmp_str!(Utf8PathBuf, &'a str); |
3007 | impl_cmp_str!(Utf8PathBuf, Cow<'a, str>); |
3008 | impl_cmp_str!(Utf8PathBuf, String); |
3009 | impl_cmp_str!(Utf8Path, str); |
3010 | impl_cmp_str!(Utf8Path, &'a str); |
3011 | impl_cmp_str!(Utf8Path, Cow<'a, str>); |
3012 | impl_cmp_str!(Utf8Path, String); |
3013 | impl_cmp_str!(&'a Utf8Path, str); |
3014 | impl_cmp_str!(&'a Utf8Path, Cow<'b, str>); |
3015 | impl_cmp_str!(&'a Utf8Path, String); |
3016 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) |
3017 | |
3018 | macro_rules! impl_cmp_os_str { |
3019 | ($lhs:ty, $rhs: ty) => { |
3020 | #[allow(clippy::extra_unused_lifetimes)] |
3021 | impl<'a, 'b> PartialEq<$rhs> for $lhs { |
3022 | #[inline] |
3023 | fn eq(&self, other: &$rhs) -> bool { |
3024 | <Path as PartialEq>::eq(self.as_ref(), other.as_ref()) |
3025 | } |
3026 | } |
3027 | |
3028 | #[allow(clippy::extra_unused_lifetimes)] |
3029 | impl<'a, 'b> PartialEq<$lhs> for $rhs { |
3030 | #[inline] |
3031 | fn eq(&self, other: &$lhs) -> bool { |
3032 | <Path as PartialEq>::eq(self.as_ref(), other.as_ref()) |
3033 | } |
3034 | } |
3035 | |
3036 | #[allow(clippy::extra_unused_lifetimes)] |
3037 | impl<'a, 'b> PartialOrd<$rhs> for $lhs { |
3038 | #[inline] |
3039 | fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> { |
3040 | <Path as PartialOrd>::partial_cmp(self.as_ref(), other.as_ref()) |
3041 | } |
3042 | } |
3043 | |
3044 | #[allow(clippy::extra_unused_lifetimes)] |
3045 | impl<'a, 'b> PartialOrd<$lhs> for $rhs { |
3046 | #[inline] |
3047 | fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> { |
3048 | <Path as PartialOrd>::partial_cmp(self.as_ref(), other.as_ref()) |
3049 | } |
3050 | } |
3051 | }; |
3052 | } |
3053 | |
3054 | impl_cmp_os_str!(Utf8PathBuf, OsStr); |
3055 | impl_cmp_os_str!(Utf8PathBuf, &'a OsStr); |
3056 | impl_cmp_os_str!(Utf8PathBuf, Cow<'a, OsStr>); |
3057 | impl_cmp_os_str!(Utf8PathBuf, OsString); |
3058 | impl_cmp_os_str!(Utf8Path, OsStr); |
3059 | impl_cmp_os_str!(Utf8Path, &'a OsStr); |
3060 | impl_cmp_os_str!(Utf8Path, Cow<'a, OsStr>); |
3061 | impl_cmp_os_str!(Utf8Path, OsString); |
3062 | impl_cmp_os_str!(&'a Utf8Path, OsStr); |
3063 | impl_cmp_os_str!(&'a Utf8Path, Cow<'b, OsStr>); |
3064 | impl_cmp_os_str!(&'a Utf8Path, OsString); |
3065 | // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) |
3066 | |
3067 | /// Makes the path absolute without accessing the filesystem, converting it to a [`Utf8PathBuf`]. |
3068 | /// |
3069 | /// If the path is relative, the current directory is used as the base directory. All intermediate |
3070 | /// components will be resolved according to platform-specific rules, but unlike |
3071 | /// [`canonicalize`][Utf8Path::canonicalize] or [`canonicalize_utf8`](Utf8Path::canonicalize_utf8), |
3072 | /// this does not resolve symlinks and may succeed even if the path does not exist. |
3073 | /// |
3074 | /// *Requires Rust 1.79 or newer.* |
3075 | /// |
3076 | /// # Errors |
3077 | /// |
3078 | /// Errors if: |
3079 | /// |
3080 | /// * The path is empty. |
3081 | /// * The [current directory][std::env::current_dir] cannot be determined. |
3082 | /// * The path is not valid UTF-8. |
3083 | /// |
3084 | /// # Examples |
3085 | /// |
3086 | /// ## POSIX paths |
3087 | /// |
3088 | /// ``` |
3089 | /// # #[cfg (unix)] |
3090 | /// fn main() -> std::io::Result<()> { |
3091 | /// use camino::Utf8Path; |
3092 | /// |
3093 | /// // Relative to absolute |
3094 | /// let absolute = camino::absolute_utf8("foo/./bar" )?; |
3095 | /// assert!(absolute.ends_with("foo/bar" )); |
3096 | /// |
3097 | /// // Absolute to absolute |
3098 | /// let absolute = camino::absolute_utf8("/foo//test/.././bar.rs" )?; |
3099 | /// assert_eq!(absolute, Utf8Path::new("/foo/test/../bar.rs" )); |
3100 | /// Ok(()) |
3101 | /// } |
3102 | /// # #[cfg (not(unix))] |
3103 | /// # fn main() {} |
3104 | /// ``` |
3105 | /// |
3106 | /// The path is resolved using [POSIX semantics][posix-semantics] except that it stops short of |
3107 | /// resolving symlinks. This means it will keep `..` components and trailing slashes. |
3108 | /// |
3109 | /// ## Windows paths |
3110 | /// |
3111 | /// ``` |
3112 | /// # #[cfg (windows)] |
3113 | /// fn main() -> std::io::Result<()> { |
3114 | /// use camino::Utf8Path; |
3115 | /// |
3116 | /// // Relative to absolute |
3117 | /// let absolute = camino::absolute_utf8("foo/./bar" )?; |
3118 | /// assert!(absolute.ends_with(r"foo\bar" )); |
3119 | /// |
3120 | /// // Absolute to absolute |
3121 | /// let absolute = camino::absolute_utf8(r"C:\foo//test\..\./bar.rs" )?; |
3122 | /// |
3123 | /// assert_eq!(absolute, Utf8Path::new(r"C:\foo\bar.rs" )); |
3124 | /// Ok(()) |
3125 | /// } |
3126 | /// # #[cfg (not(windows))] |
3127 | /// # fn main() {} |
3128 | /// ``` |
3129 | /// |
3130 | /// For verbatim paths this will simply return the path as given. For other paths this is currently |
3131 | /// equivalent to calling [`GetFullPathNameW`][windows-path]. |
3132 | /// |
3133 | /// Note that this [may change in the future][changes]. |
3134 | /// |
3135 | /// [changes]: io#platform-specific-behavior |
3136 | /// [posix-semantics]: |
3137 | /// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 |
3138 | /// [windows-path]: |
3139 | /// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew |
3140 | #[cfg (absolute_path)] |
3141 | pub fn absolute_utf8<P: AsRef<Path>>(path: P) -> io::Result<Utf8PathBuf> { |
3142 | // Note that even if the passed in path is valid UTF-8, it is not guaranteed that the absolute |
3143 | // path will be valid UTF-8. For example, the current directory may not be valid UTF-8. |
3144 | // |
3145 | // That's why we take `AsRef<Path>` instead of `AsRef<Utf8Path>` here -- we have to pay the cost |
3146 | // of checking for valid UTF-8 anyway. |
3147 | let path: &Path = path.as_ref(); |
3148 | #[allow (clippy::incompatible_msrv)] |
3149 | Utf8PathBuf::try_from(std::path::absolute(path)?).map_err(|error: FromPathBufError| error.into_io_error()) |
3150 | } |
3151 | |
3152 | // invariant: OsStr must be guaranteed to be utf8 data |
3153 | #[inline ] |
3154 | unsafe fn str_assume_utf8(string: &OsStr) -> &str { |
3155 | #[cfg (os_str_bytes)] |
3156 | { |
3157 | // SAFETY: OsStr is guaranteed to be utf8 data from the invariant |
3158 | unsafe { |
3159 | std::str::from_utf8_unchecked( |
3160 | #[allow (clippy::incompatible_msrv)] |
3161 | string.as_encoded_bytes(), |
3162 | ) |
3163 | } |
3164 | } |
3165 | #[cfg (not(os_str_bytes))] |
3166 | { |
3167 | // Adapted from the source code for Option::unwrap_unchecked. |
3168 | match string.to_str() { |
3169 | Some(val) => val, |
3170 | None => std::hint::unreachable_unchecked(), |
3171 | } |
3172 | } |
3173 | } |
3174 | |