typed_path/common/utf8/path.rs
1use alloc::borrow::{Cow, ToOwned};
2use alloc::rc::Rc;
3#[cfg(target_has_atomic = "ptr")]
4use alloc::sync::Arc;
5use core::hash::{Hash, Hasher};
6use core::marker::PhantomData;
7use core::str::Utf8Error;
8use core::{cmp, fmt};
9
10#[cfg(feature = "serde")]
11use serde_core::{
12 de,
13 de::{Unexpected, Visitor},
14 Deserialize, Deserializer, Serialize, Serializer,
15};
16
17use crate::no_std_compat::*;
18use crate::{
19 CheckedPathError, Encoding, Path, StripPrefixError, Utf8Ancestors, Utf8Component,
20 Utf8Components, Utf8Encoding, Utf8Iter, Utf8PathBuf,
21};
22
23/// A slice of a path (akin to [`str`]).
24///
25/// This type supports a number of operations for inspecting a path, including
26/// breaking the path into its components (separated by `/` on Unix and by either
27/// `/` or `\` on Windows), extracting the file name, determining whether the path
28/// is absolute, and so on.
29///
30/// This is an *unsized* type, meaning that it must always be used behind a
31/// pointer like `&` or [`Box`]. For an owned version of this type,
32/// see [`Utf8PathBuf`].
33///
34/// # Examples
35///
36/// ```
37/// use typed_path::{Utf8Path, Utf8UnixEncoding};
38///
39/// // NOTE: A path cannot be created on its own without a defined encoding,
40/// // but all encodings work on all operating systems, providing the
41/// // ability to parse and operate on paths independently of the
42/// // compiled platform
43/// let path = Utf8Path::<Utf8UnixEncoding>::new("./foo/bar.txt");
44///
45/// let parent = path.parent();
46/// assert_eq!(parent, Some(Utf8Path::new("./foo")));
47///
48/// let file_stem = path.file_stem();
49/// assert_eq!(file_stem, Some("bar"));
50///
51/// let extension = path.extension();
52/// assert_eq!(extension, Some("txt"));
53/// ```
54///
55/// In addition to explicitly using [`Utf8Encoding`]s, you can also
56/// leverage aliases available from the crate to work with paths:
57///
58/// ```
59/// use typed_path::{Utf8UnixPath, Utf8WindowsPath};
60///
61/// // Same as Utf8Path<Utf8UnixEncoding>
62/// let path = Utf8UnixPath::new("/foo/bar.txt");
63///
64/// // Same as Utf8Path<Utf8WindowsEncoding>
65/// let path = Utf8WindowsPath::new(r"C:\foo\bar.txt");
66/// ```
67///
68/// To mirror the design of Rust's standard library, you can access
69/// the path associated with the compiled rust platform using [`Utf8NativePath`],
70/// which itself is an alias to one of the other choices:
71///
72/// ```
73/// use typed_path::Utf8NativePath;
74///
75/// // On Unix, this would be Utf8UnixPath aka Utf8Path<Utf8UnixEncoding>
76/// // On Windows, this would be Utf8WindowsPath aka Utf8Path<Utf8WindowsEncoding>
77/// let path = Utf8NativePath::new("/foo/bar.txt");
78/// ```
79///
80/// [`Utf8NativePath`]: crate::Utf8NativePath
81#[repr(transparent)]
82pub struct Utf8Path<T>
83where
84 T: Utf8Encoding,
85{
86 /// Encoding associated with path buf
87 _encoding: PhantomData<T>,
88
89 /// Path as an unparsed str slice
90 pub(crate) inner: str,
91}
92
93impl<T> Utf8Path<T>
94where
95 T: Utf8Encoding,
96{
97 /// Directly wraps a str slice as a `Utf8Path` slice.
98 ///
99 /// This is a cost-free conversion.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
105 ///
106 /// // NOTE: A path cannot be created on its own without a defined encoding
107 /// Utf8Path::<Utf8UnixEncoding>::new("foo.txt");
108 /// ```
109 ///
110 /// You can create `Utf8Path`s from `String`s, or even other `Utf8Path`s:
111 ///
112 /// ```
113 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
114 ///
115 /// // NOTE: A path cannot be created on its own without a defined encoding
116 /// let string = String::from("foo.txt");
117 /// let from_string = Utf8Path::<Utf8UnixEncoding>::new(&string);
118 /// let from_path = Utf8Path::new(&from_string);
119 /// assert_eq!(from_string, from_path);
120 /// ```
121 ///
122 /// There are also handy aliases to the `Utf8Path` with [`Utf8Encoding`]:
123 ///
124 /// ```
125 /// use typed_path::Utf8UnixPath;
126 ///
127 /// let string = String::from("foo.txt");
128 /// let from_string = Utf8UnixPath::new(&string);
129 /// let from_path = Utf8UnixPath::new(&from_string);
130 /// assert_eq!(from_string, from_path);
131 /// ```
132 #[inline]
133 pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
134 unsafe { &*(s.as_ref() as *const str as *const Self) }
135 }
136
137 /// Yields the underlying [`str`] slice.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
143 ///
144 /// // NOTE: A path cannot be created on its own without a defined encoding
145 /// let s = Utf8Path::<Utf8UnixEncoding>::new("foo.txt").as_str();
146 /// assert_eq!(s, "foo.txt");
147 /// ```
148 pub fn as_str(&self) -> &str {
149 &self.inner
150 }
151
152 /// Converts a `Utf8Path` to an owned [`Utf8PathBuf`].
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
158 ///
159 /// // NOTE: A path cannot be created on its own without a defined encoding
160 /// let path_buf = Utf8Path::<Utf8UnixEncoding>::new("foo.txt").to_path_buf();
161 /// assert_eq!(path_buf, Utf8PathBuf::from("foo.txt"));
162 /// ```
163 pub fn to_path_buf(&self) -> Utf8PathBuf<T> {
164 Utf8PathBuf {
165 _encoding: PhantomData,
166 inner: self.inner.to_owned(),
167 }
168 }
169
170 /// Returns `true` if the `Utf8Path` is absolute, i.e., if it is independent of
171 /// the current directory.
172 ///
173 /// * On Unix ([`Utf8UnixPath`]]), a path is absolute if it starts with the root, so
174 /// `is_absolute` and [`has_root`] are equivalent.
175 ///
176 /// * On Windows ([`Utf8WindowsPath`]), a path is absolute if it has a prefix and starts with
177 /// the root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
178 ///
179 /// [`Utf8UnixPath`]: crate::Utf8UnixPath
180 /// [`Utf8WindowsPath`]: crate::Utf8WindowsPath
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
186 ///
187 /// // NOTE: A path cannot be created on its own without a defined encoding
188 /// assert!(!Utf8Path::<Utf8UnixEncoding>::new("foo.txt").is_absolute());
189 /// ```
190 ///
191 /// [`has_root`]: Utf8Path::has_root
192 pub fn is_absolute(&self) -> bool {
193 self.components().is_absolute()
194 }
195
196 /// Returns `true` if the `Utf8Path` is relative, i.e., not absolute.
197 ///
198 /// See [`is_absolute`]'s documentation for more details.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
204 ///
205 /// // NOTE: A path cannot be created on its own without a defined encoding
206 /// assert!(Utf8Path::<Utf8UnixEncoding>::new("foo.txt").is_relative());
207 /// ```
208 ///
209 /// [`is_absolute`]: Utf8Path::is_absolute
210 #[inline]
211 pub fn is_relative(&self) -> bool {
212 !self.is_absolute()
213 }
214
215 /// Returns `true` if the path is valid, meaning that all of its components are valid.
216 ///
217 /// See [`Utf8Component::is_valid`]'s documentation for more details.
218 ///
219 /// # Examples
220 ///
221 /// ```
222 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
223 ///
224 /// // NOTE: A path cannot be created on its own without a defined encoding
225 /// assert!(Utf8Path::<Utf8UnixEncoding>::new("foo.txt").is_valid());
226 /// assert!(!Utf8Path::<Utf8UnixEncoding>::new("foo\0.txt").is_valid());
227 /// ```
228 ///
229 /// [`Utf8Component::is_valid`]: crate::Utf8Component::is_valid
230 pub fn is_valid(&self) -> bool {
231 self.components().all(|c| c.is_valid())
232 }
233
234 /// Returns `true` if the `Utf8Path` has a root.
235 ///
236 /// * On Unix ([`Utf8UnixPath`]), a path has a root if it begins with `/`.
237 ///
238 /// * On Windows ([`Utf8WindowsPath`]), a path has a root if it:
239 /// * has no prefix and begins with a separator, e.g., `\windows`
240 /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows`
241 /// * has any non-disk prefix, e.g., `\\server\share`
242 ///
243 /// [`Utf8UnixPath`]: crate::Utf8UnixPath
244 /// [`Utf8WindowsPath`]: crate::Utf8WindowsPath
245 ///
246 /// # Examples
247 ///
248 /// ```
249 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
250 ///
251 /// // NOTE: A path cannot be created on its own without a defined encoding
252 /// assert!(Utf8Path::<Utf8UnixEncoding>::new("/etc/passwd").has_root());
253 /// ```
254 #[inline]
255 pub fn has_root(&self) -> bool {
256 self.components().has_root()
257 }
258
259 /// Returns the `Utf8Path` without its final component, if there is one.
260 ///
261 /// Returns [`None`] if the path terminates in a root or prefix.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
267 ///
268 /// // NOTE: A path cannot be created on its own without a defined encoding
269 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/foo/bar");
270 /// let parent = path.parent().unwrap();
271 /// assert_eq!(parent, Utf8Path::new("/foo"));
272 ///
273 /// let grand_parent = parent.parent().unwrap();
274 /// assert_eq!(grand_parent, Utf8Path::new("/"));
275 /// assert_eq!(grand_parent.parent(), None);
276 /// ```
277 pub fn parent(&self) -> Option<&Self> {
278 let mut comps = self.components();
279 let comp = comps.next_back();
280 comp.and_then(|p| {
281 if !p.is_root() {
282 Some(Self::new(comps.as_str()))
283 } else {
284 None
285 }
286 })
287 }
288
289 /// Produces an iterator over `Utf8Path` and its ancestors.
290 ///
291 /// The iterator will yield the `Utf8Path` that is returned if the [`parent`] method is used zero
292 /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`,
293 /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns
294 /// [`None`], the iterator will do likewise. The iterator will always yield at least one value,
295 /// namely `&self`.
296 ///
297 /// # Examples
298 ///
299 /// ```
300 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
301 ///
302 /// // NOTE: A path cannot be created on its own without a defined encoding
303 /// let mut ancestors = Utf8Path::<Utf8UnixEncoding>::new("/foo/bar").ancestors();
304 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo/bar")));
305 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/foo")));
306 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("/")));
307 /// assert_eq!(ancestors.next(), None);
308 ///
309 /// // NOTE: A path cannot be created on its own without a defined encoding
310 /// let mut ancestors = Utf8Path::<Utf8UnixEncoding>::new("../foo/bar").ancestors();
311 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo/bar")));
312 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("../foo")));
313 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("..")));
314 /// assert_eq!(ancestors.next(), Some(Utf8Path::new("")));
315 /// assert_eq!(ancestors.next(), None);
316 /// ```
317 ///
318 /// [`parent`]: Utf8Path::parent
319 #[inline]
320 pub fn ancestors(&self) -> Utf8Ancestors<'_, T> {
321 Utf8Ancestors { next: Some(self) }
322 }
323
324 /// Returns the final component of the `Utf8Path`, if there is one.
325 ///
326 /// If the path is a normal file, this is the file name. If it's the path of a directory, this
327 /// is the directory name.
328 ///
329 /// Returns [`None`] if the path terminates in `..`.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
335 ///
336 /// // NOTE: A path cannot be created on its own without a defined encoding
337 /// assert_eq!(Some("bin"), Utf8Path::<Utf8UnixEncoding>::new("/usr/bin/").file_name());
338 /// assert_eq!(Some("foo.txt"), Utf8Path::<Utf8UnixEncoding>::new("tmp/foo.txt").file_name());
339 /// assert_eq!(Some("foo.txt"), Utf8Path::<Utf8UnixEncoding>::new("foo.txt/.").file_name());
340 /// assert_eq!(Some("foo.txt"), Utf8Path::<Utf8UnixEncoding>::new("foo.txt/.//").file_name());
341 /// assert_eq!(None, Utf8Path::<Utf8UnixEncoding>::new("foo.txt/..").file_name());
342 /// assert_eq!(None, Utf8Path::<Utf8UnixEncoding>::new("/").file_name());
343 /// ```
344 pub fn file_name(&self) -> Option<&str> {
345 match self.components().next_back() {
346 Some(p) => {
347 if p.is_normal() {
348 Some(p.as_str())
349 } else {
350 None
351 }
352 }
353 None => None,
354 }
355 }
356
357 /// Returns a path that, when joined onto `base`, yields `self`.
358 ///
359 /// # Errors
360 ///
361 /// If `base` is not a prefix of `self` (i.e., [`starts_with`]
362 /// returns `false`), returns [`Err`].
363 ///
364 /// [`starts_with`]: Utf8Path::starts_with
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
370 ///
371 /// // NOTE: A path cannot be created on its own without a defined encoding
372 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/test/haha/foo.txt");
373 ///
374 /// assert_eq!(path.strip_prefix("/"), Ok(Utf8Path::new("test/haha/foo.txt")));
375 /// assert_eq!(path.strip_prefix("/test"), Ok(Utf8Path::new("haha/foo.txt")));
376 /// assert_eq!(path.strip_prefix("/test/"), Ok(Utf8Path::new("haha/foo.txt")));
377 /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Utf8Path::new("")));
378 /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Utf8Path::new("")));
379 ///
380 /// assert!(path.strip_prefix("test").is_err());
381 /// assert!(path.strip_prefix("/haha").is_err());
382 ///
383 /// let prefix = Utf8PathBuf::<Utf8UnixEncoding>::from("/test/");
384 /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8Path::new("haha/foo.txt")));
385 /// ```
386 pub fn strip_prefix<P>(&self, base: P) -> Result<&Utf8Path<T>, StripPrefixError>
387 where
388 P: AsRef<Utf8Path<T>>,
389 {
390 self._strip_prefix(base.as_ref())
391 }
392
393 fn _strip_prefix(&self, base: &Utf8Path<T>) -> Result<&Utf8Path<T>, StripPrefixError> {
394 match helpers::iter_after(self.components(), base.components()) {
395 Some(c) => Ok(Utf8Path::new(c.as_str())),
396 None => Err(StripPrefixError(())),
397 }
398 }
399
400 /// Determines whether `base` is a prefix of `self`.
401 ///
402 /// Only considers whole path components to match.
403 ///
404 /// # Examples
405 ///
406 /// ```
407 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
408 ///
409 /// // NOTE: A path cannot be created on its own without a defined encoding
410 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/etc/passwd");
411 ///
412 /// assert!(path.starts_with("/etc"));
413 /// assert!(path.starts_with("/etc/"));
414 /// assert!(path.starts_with("/etc/passwd"));
415 /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay
416 /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay
417 ///
418 /// assert!(!path.starts_with("/e"));
419 /// assert!(!path.starts_with("/etc/passwd.txt"));
420 ///
421 /// assert!(!Utf8Path::<Utf8UnixEncoding>::new("/etc/foo.rs").starts_with("/etc/foo"));
422 /// ```
423 pub fn starts_with<P>(&self, base: P) -> bool
424 where
425 P: AsRef<Utf8Path<T>>,
426 {
427 self._starts_with(base.as_ref())
428 }
429
430 fn _starts_with(&self, base: &Utf8Path<T>) -> bool {
431 helpers::iter_after(self.components(), base.components()).is_some()
432 }
433
434 /// Determines whether `child` is a suffix of `self`.
435 ///
436 /// Only considers whole path components to match.
437 ///
438 /// # Examples
439 ///
440 /// ```
441 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
442 ///
443 /// // NOTE: A path cannot be created on its own without a defined encoding
444 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/etc/resolv.conf");
445 ///
446 /// assert!(path.ends_with("resolv.conf"));
447 /// assert!(path.ends_with("etc/resolv.conf"));
448 /// assert!(path.ends_with("/etc/resolv.conf"));
449 ///
450 /// assert!(!path.ends_with("/resolv.conf"));
451 /// assert!(!path.ends_with("conf")); // use .extension() instead
452 /// ```
453 pub fn ends_with<P>(&self, child: P) -> bool
454 where
455 P: AsRef<Utf8Path<T>>,
456 {
457 self._ends_with(child.as_ref())
458 }
459
460 fn _ends_with(&self, child: &Utf8Path<T>) -> bool {
461 helpers::iter_after(self.components().rev(), child.components().rev()).is_some()
462 }
463
464 /// Extracts the stem (non-extension) portion of [`self.file_name`].
465 ///
466 /// [`self.file_name`]: Utf8Path::file_name
467 ///
468 /// The stem is:
469 ///
470 /// * [`None`], if there is no file name;
471 /// * The entire file name if there is no embedded `.`;
472 /// * The entire file name if the file name begins with `.` and has no other `.`s within;
473 /// * Otherwise, the portion of the file name before the final `.`
474 ///
475 /// # Examples
476 ///
477 /// ```
478 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
479 ///
480 /// // NOTE: A path cannot be created on its own without a defined encoding
481 /// assert_eq!("foo", Utf8Path::<Utf8UnixEncoding>::new("foo.rs").file_stem().unwrap());
482 /// assert_eq!("foo.tar", Utf8Path::<Utf8UnixEncoding>::new("foo.tar.gz").file_stem().unwrap());
483 /// ```
484 ///
485 pub fn file_stem(&self) -> Option<&str> {
486 self.file_name()
487 .map(helpers::rsplit_file_at_dot)
488 .and_then(|(before, after)| before.or(after))
489 }
490
491 /// Extracts the extension of [`self.file_name`], if possible.
492 ///
493 /// The extension is:
494 ///
495 /// * [`None`], if there is no file name;
496 /// * [`None`], if there is no embedded `.`;
497 /// * [`None`], if the file name begins with `.` and has no other `.`s within;
498 /// * Otherwise, the portion of the file name after the final `.`
499 ///
500 /// [`self.file_name`]: Utf8Path::file_name
501 ///
502 /// # Examples
503 ///
504 /// ```
505 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
506 ///
507 /// // NOTE: A path cannot be created on its own without a defined encoding
508 /// assert_eq!("rs", Utf8Path::<Utf8UnixEncoding>::new("foo.rs").extension().unwrap());
509 /// assert_eq!("gz", Utf8Path::<Utf8UnixEncoding>::new("foo.tar.gz").extension().unwrap());
510 /// ```
511 pub fn extension(&self) -> Option<&str> {
512 self.file_name()
513 .map(helpers::rsplit_file_at_dot)
514 .and_then(|(before, after)| before.and(after))
515 }
516
517 /// Returns an owned [`Utf8PathBuf`] by resolving `..` and `.` segments.
518 ///
519 /// When multiple, sequential path segment separation characters are found (e.g. `/` for Unix
520 /// and either `\` or `/` on Windows), they are replaced by a single instance of the
521 /// platform-specific path segment separator (`/` on Unix and `\` on Windows).
522 ///
523 /// # Examples
524 ///
525 /// ```
526 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
527 ///
528 /// // NOTE: A path cannot be created on its own without a defined encoding
529 /// assert_eq!(
530 /// Utf8Path::<Utf8UnixEncoding>::new("foo/bar//baz/./asdf/quux/..").normalize(),
531 /// Utf8PathBuf::from("foo/bar/baz/asdf"),
532 /// );
533 /// ```
534 ///
535 /// When starting with a root directory, any `..` segment whose parent is the root directory
536 /// will be filtered out:
537 ///
538 /// ```
539 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
540 ///
541 /// // NOTE: A path cannot be created on its own without a defined encoding
542 /// assert_eq!(
543 /// Utf8Path::<Utf8UnixEncoding>::new("/../foo").normalize(),
544 /// Utf8PathBuf::from("/foo"),
545 /// );
546 /// ```
547 ///
548 /// If any `..` is left unresolved as the path is relative and no parent is found, it is
549 /// discarded:
550 ///
551 /// ```
552 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding, Utf8WindowsEncoding};
553 ///
554 /// assert_eq!(
555 /// Utf8Path::<Utf8UnixEncoding>::new("../foo/..").normalize(),
556 /// Utf8PathBuf::from(""),
557 /// );
558 ///
559 /// //Windows prefixes also count this way, but the prefix remains
560 /// assert_eq!(
561 /// Utf8Path::<Utf8WindowsEncoding>::new(r"C:..\foo\..").normalize(),
562 /// Utf8PathBuf::from(r"C:"),
563 /// );
564 /// ```
565 pub fn normalize(&self) -> Utf8PathBuf<T> {
566 let mut components = Vec::new();
567 for component in self.components() {
568 if !component.is_current() && !component.is_parent() {
569 components.push(component);
570 } else if component.is_parent() {
571 if let Some(last) = components.last() {
572 if last.is_normal() {
573 components.pop();
574 }
575 }
576 }
577 }
578
579 let mut path = Utf8PathBuf::<T>::new();
580
581 for component in components {
582 path.push(component.as_str());
583 }
584
585 path
586 }
587
588 /// Converts a path to an absolute form by [`normalizing`] the path, returning a
589 /// [`Utf8PathBuf`].
590 ///
591 /// In the case that the path is relative, the current working directory is prepended prior to
592 /// normalizing.
593 ///
594 /// [`normalizing`]: Utf8Path::normalize
595 ///
596 /// # Examples
597 ///
598 /// ```
599 /// use typed_path::{utils, Utf8Path, Utf8UnixEncoding};
600 ///
601 /// // With an absolute path, it is just normalized
602 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/a/b/../c/./d");
603 /// assert_eq!(path.absolutize().unwrap(), Utf8Path::new("/a/c/d"));
604 ///
605 /// // With a relative path, it is first joined with the current working directory
606 /// // and then normalized
607 /// let cwd = utils::utf8_current_dir().unwrap().with_encoding::<Utf8UnixEncoding>();
608 /// let path = cwd.join(Utf8Path::new("a/b/../c/./d"));
609 /// assert_eq!(path.absolutize().unwrap(), cwd.join(Utf8Path::new("a/c/d")));
610 /// ```
611 #[cfg(all(feature = "std", not(target_family = "wasm")))]
612 pub fn absolutize(&self) -> std::io::Result<Utf8PathBuf<T>> {
613 if self.is_absolute() {
614 Ok(self.normalize())
615 } else {
616 // Get the cwd as a platform path and convert to this path's encoding
617 let cwd = crate::utils::utf8_current_dir()?.with_encoding();
618
619 Ok(cwd.join(self).normalize())
620 }
621 }
622
623 /// Creates an owned [`Utf8PathBuf`] with `path` adjoined to `self`.
624 ///
625 /// See [`Utf8PathBuf::push`] for more details on what it means to adjoin a path.
626 ///
627 /// # Examples
628 ///
629 /// ```
630 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
631 ///
632 /// // NOTE: A path cannot be created on its own without a defined encoding
633 /// assert_eq!(
634 /// Utf8Path::<Utf8UnixEncoding>::new("/etc").join("passwd"),
635 /// Utf8PathBuf::from("/etc/passwd"),
636 /// );
637 /// ```
638 pub fn join<P: AsRef<Utf8Path<T>>>(&self, path: P) -> Utf8PathBuf<T> {
639 self._join(path.as_ref())
640 }
641
642 fn _join(&self, path: &Utf8Path<T>) -> Utf8PathBuf<T> {
643 let mut buf = self.to_path_buf();
644 buf.push(path);
645 buf
646 }
647
648 /// Creates an owned [`Utf8PathBuf`] with `path` adjoined to `self`, checking the `path` to
649 /// ensure it is safe to join. _When dealing with user-provided paths, this is the preferred
650 /// method._
651 ///
652 /// See [`Utf8PathBuf::push_checked`] for more details on what it means to adjoin a path
653 /// safely.
654 ///
655 /// # Examples
656 ///
657 /// ```
658 /// use typed_path::{CheckedPathError, Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
659 ///
660 /// // NOTE: A path cannot be created on its own without a defined encoding
661 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/etc");
662 ///
663 /// // A valid path can be joined onto the existing one
664 /// assert_eq!(path.join_checked("passwd"), Ok(Utf8PathBuf::from("/etc/passwd")));
665 ///
666 /// // An invalid path will result in an error
667 /// assert_eq!(path.join_checked("/sneaky/replacement"), Err(CheckedPathError::UnexpectedRoot));
668 /// ```
669 pub fn join_checked<P: AsRef<Utf8Path<T>>>(
670 &self,
671 path: P,
672 ) -> Result<Utf8PathBuf<T>, CheckedPathError> {
673 self._join_checked(path.as_ref())
674 }
675
676 fn _join_checked(&self, path: &Utf8Path<T>) -> Result<Utf8PathBuf<T>, CheckedPathError> {
677 let mut buf = self.to_path_buf();
678 buf.push_checked(path)?;
679 Ok(buf)
680 }
681
682 /// Creates an owned [`Utf8PathBuf`] like `self` but with the given file name.
683 ///
684 /// See [`Utf8PathBuf::set_file_name`] for more details.
685 ///
686 /// # Examples
687 ///
688 /// ```
689 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
690 ///
691 /// // NOTE: A path cannot be created on its own without a defined encoding
692 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/tmp/foo.txt");
693 /// assert_eq!(path.with_file_name("bar.txt"), Utf8PathBuf::from("/tmp/bar.txt"));
694 ///
695 /// // NOTE: A path cannot be created on its own without a defined encoding
696 /// let path = Utf8Path::<Utf8UnixEncoding>::new("/tmp");
697 /// assert_eq!(path.with_file_name("var"), Utf8PathBuf::from("/var"));
698 /// ```
699 pub fn with_file_name<S: AsRef<str>>(&self, file_name: S) -> Utf8PathBuf<T> {
700 self._with_file_name(file_name.as_ref())
701 }
702
703 fn _with_file_name(&self, file_name: &str) -> Utf8PathBuf<T> {
704 let mut buf = self.to_path_buf();
705 buf.set_file_name(file_name);
706 buf
707 }
708
709 /// Creates an owned [`Utf8PathBuf`] like `self` but with the given extension.
710 ///
711 /// See [`Utf8PathBuf::set_extension`] for more details.
712 ///
713 /// # Examples
714 ///
715 /// ```
716 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
717 ///
718 /// // NOTE: A path cannot be created on its own without a defined encoding
719 /// let path = Utf8Path::<Utf8UnixEncoding>::new("foo.rs");
720 /// assert_eq!(path.with_extension("txt"), Utf8PathBuf::from("foo.txt"));
721 ///
722 /// // NOTE: A path cannot be created on its own without a defined encoding
723 /// let path = Utf8Path::<Utf8UnixEncoding>::new("foo.tar.gz");
724 /// assert_eq!(path.with_extension(""), Utf8PathBuf::from("foo.tar"));
725 /// assert_eq!(path.with_extension("xz"), Utf8PathBuf::from("foo.tar.xz"));
726 /// assert_eq!(path.with_extension("").with_extension("txt"), Utf8PathBuf::from("foo.txt"));
727 /// ```
728 pub fn with_extension<S: AsRef<str>>(&self, extension: S) -> Utf8PathBuf<T> {
729 self._with_extension(extension.as_ref())
730 }
731
732 fn _with_extension(&self, extension: &str) -> Utf8PathBuf<T> {
733 let mut buf = self.to_path_buf();
734 buf.set_extension(extension);
735 buf
736 }
737
738 /// Produces an iterator over the [`Utf8Component`]s of the path.
739 ///
740 /// When parsing the path, there is a small amount of normalization:
741 ///
742 /// * Repeated separators are ignored, so `a/b` and `a//b` both have
743 /// `a` and `b` as components.
744 ///
745 /// * Occurrences of `.` are normalized away, except if they are at the
746 /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and
747 /// `a/b` all have `a` and `b` as components, but `./a/b` starts with
748 /// an additional [`CurDir`] component.
749 ///
750 /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
751 ///
752 /// Note that no other normalization takes place; in particular, `a/c`
753 /// and `a/b/../c` are distinct, to account for the possibility that `b`
754 /// is a symbolic link (so its parent isn't `a`).
755 ///
756 /// # Examples
757 ///
758 /// ```
759 /// use typed_path::{Utf8Path, Utf8UnixComponent, Utf8UnixEncoding};
760 ///
761 /// // NOTE: A path cannot be created on its own without a defined encoding
762 /// let mut components = Utf8Path::<Utf8UnixEncoding>::new("/tmp/foo.txt").components();
763 ///
764 /// assert_eq!(components.next(), Some(Utf8UnixComponent::RootDir));
765 /// assert_eq!(components.next(), Some(Utf8UnixComponent::Normal("tmp")));
766 /// assert_eq!(components.next(), Some(Utf8UnixComponent::Normal("foo.txt")));
767 /// assert_eq!(components.next(), None)
768 /// ```
769 ///
770 /// [`CurDir`]: crate::unix::UnixComponent::CurDir
771 pub fn components(&self) -> <T as Utf8Encoding>::Components<'_> {
772 T::components(&self.inner)
773 }
774
775 /// Produces an iterator over the path's components viewed as [`str`] slices.
776 ///
777 /// For more information about the particulars of how the path is separated
778 /// into components, see [`components`].
779 ///
780 /// [`components`]: Utf8Path::components
781 ///
782 /// # Examples
783 ///
784 /// ```
785 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
786 ///
787 /// // NOTE: A path cannot be created on its own without a defined encoding
788 /// let mut it = Utf8Path::<Utf8UnixEncoding>::new("/tmp/foo.txt").iter();
789 ///
790 /// assert_eq!(it.next(), Some(typed_path::constants::unix::SEPARATOR_STR));
791 /// assert_eq!(it.next(), Some("tmp"));
792 /// assert_eq!(it.next(), Some("foo.txt"));
793 /// assert_eq!(it.next(), None)
794 /// ```
795 #[inline]
796 pub fn iter(&self) -> Utf8Iter<'_, T> {
797 Utf8Iter::new(self.components())
798 }
799
800 /// Creates an owned [`Utf8PathBuf`] like `self` but with a different encoding.
801 ///
802 /// # Note
803 ///
804 /// As part of the process of converting between encodings, the path will need to be rebuilt.
805 /// This involves [`pushing`] each component, which may result in differences in the resulting
806 /// path such as resolving `.` and `..` early or other unexpected side effects.
807 ///
808 /// [`pushing`]: Utf8PathBuf::push
809 ///
810 /// # Examples
811 ///
812 /// ```
813 /// use typed_path::{Utf8Path, Utf8UnixEncoding, Utf8WindowsEncoding};
814 ///
815 /// // Convert from Unix to Windows
816 /// let unix_path = Utf8Path::<Utf8UnixEncoding>::new("/tmp/foo.txt");
817 /// let windows_path = unix_path.with_encoding::<Utf8WindowsEncoding>();
818 /// assert_eq!(windows_path, Utf8Path::<Utf8WindowsEncoding>::new(r"\tmp\foo.txt"));
819 ///
820 /// // Converting from Windows to Unix will drop any prefix
821 /// let windows_path = Utf8Path::<Utf8WindowsEncoding>::new(r"C:\tmp\foo.txt");
822 /// let unix_path = windows_path.with_encoding::<Utf8UnixEncoding>();
823 /// assert_eq!(unix_path, Utf8Path::<Utf8UnixEncoding>::new(r"/tmp/foo.txt"));
824 ///
825 /// // Converting to itself should retain everything
826 /// let path = Utf8Path::<Utf8WindowsEncoding>::new(r"C:\tmp\foo.txt");
827 /// assert_eq!(
828 /// path.with_encoding::<Utf8WindowsEncoding>(),
829 /// Utf8Path::<Utf8WindowsEncoding>::new(r"C:\tmp\foo.txt"),
830 /// );
831 /// ```
832 pub fn with_encoding<U>(&self) -> Utf8PathBuf<U>
833 where
834 U: Utf8Encoding,
835 {
836 // If we're the same, just return the path buf, which
837 // we do with a fancy trick to convert it
838 if T::label() == U::label() {
839 Utf8Path::new(self.as_str()).to_path_buf()
840 } else {
841 // Otherwise, we have to rebuild the path from the components
842 let mut path = Utf8PathBuf::new();
843
844 // For root, current, and parent we specially handle to convert
845 // to the appropriate type, otherwise we pass along as-is
846 for component in self.components() {
847 if component.is_root() {
848 path.push(<
849 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
850 as Utf8Component
851 >::root().as_str());
852 } else if component.is_current() {
853 path.push(<
854 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
855 as Utf8Component
856 >::current().as_str());
857 } else if component.is_parent() {
858 path.push(<
859 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
860 as Utf8Component
861 >::parent().as_str());
862 } else {
863 path.push(component.as_str());
864 }
865 }
866
867 path
868 }
869 }
870
871 /// Like [`with_encoding`], creates an owned [`Utf8PathBuf`] like `self` but with a different
872 /// encoding. Additionally, checks to ensure that the produced path will be valid.
873 ///
874 /// # Note
875 ///
876 /// As part of the process of converting between encodings, the path will need to be rebuilt.
877 /// This involves [`pushing and checking`] each component, which may result in differences in
878 /// the resulting path such as resolving `.` and `..` early or other unexpected side effects.
879 ///
880 /// [`pushing and checking`]: Utf8PathBuf::push_checked
881 /// [`with_encoding`]: Utf8Path::with_encoding
882 ///
883 /// # Examples
884 ///
885 /// ```
886 /// use typed_path::{CheckedPathError, Utf8Path, Utf8UnixEncoding, Utf8WindowsEncoding};
887 ///
888 /// // Convert from Unix to Windows
889 /// let unix_path = Utf8Path::<Utf8UnixEncoding>::new("/tmp/foo.txt");
890 /// let windows_path = unix_path.with_encoding_checked::<Utf8WindowsEncoding>().unwrap();
891 /// assert_eq!(windows_path, Utf8Path::<Utf8WindowsEncoding>::new(r"\tmp\foo.txt"));
892 ///
893 /// // Converting from Windows to Unix will drop any prefix
894 /// let windows_path = Utf8Path::<Utf8WindowsEncoding>::new(r"C:\tmp\foo.txt");
895 /// let unix_path = windows_path.with_encoding_checked::<Utf8UnixEncoding>().unwrap();
896 /// assert_eq!(unix_path, Utf8Path::<Utf8UnixEncoding>::new(r"/tmp/foo.txt"));
897 ///
898 /// // Converting from Unix to Windows with invalid filename characters like `|` should fail
899 /// let unix_path = Utf8Path::<Utf8UnixEncoding>::new("/|invalid|/foo.txt");
900 /// assert_eq!(
901 /// unix_path.with_encoding_checked::<Utf8WindowsEncoding>(),
902 /// Err(CheckedPathError::InvalidFilename),
903 /// );
904 ///
905 /// // Converting from Unix to Windows with unexpected prefix embedded in path should fail
906 /// let unix_path = Utf8Path::<Utf8UnixEncoding>::new("/path/c:/foo.txt");
907 /// assert_eq!(
908 /// unix_path.with_encoding_checked::<Utf8WindowsEncoding>(),
909 /// Err(CheckedPathError::UnexpectedPrefix),
910 /// );
911 /// ```
912 pub fn with_encoding_checked<U>(&self) -> Result<Utf8PathBuf<U>, CheckedPathError>
913 where
914 U: Utf8Encoding,
915 {
916 let mut path = Utf8PathBuf::new();
917
918 // For root, current, and parent we specially handle to convert to the appropriate type,
919 // otherwise we attempt to push using the checked variant, which will ensure that the
920 // destination encoding is respected
921 for component in self.components() {
922 if component.is_root() {
923 path.push(<
924 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
925 as Utf8Component
926 >::root().as_str());
927 } else if component.is_current() {
928 path.push(<
929 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
930 as Utf8Component
931 >::current().as_str());
932 } else if component.is_parent() {
933 path.push(<
934 <<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
935 as Utf8Component
936 >::parent().as_str());
937 } else {
938 path.push_checked(component.as_str())?;
939 }
940 }
941
942 Ok(path)
943 }
944
945 /// Converts a [`Box<Utf8Path>`](Box) into a
946 /// [`Utf8PathBuf`] without copying or allocating.
947 pub fn into_path_buf(self: Box<Utf8Path<T>>) -> Utf8PathBuf<T> {
948 let rw = Box::into_raw(self) as *mut str;
949 let inner = unsafe { Box::from_raw(rw) };
950 Utf8PathBuf {
951 _encoding: PhantomData,
952 inner: inner.into_string(),
953 }
954 }
955
956 /// Converts a non-UTF-8 [`Path`] to a UTF-8 [`Utf8PathBuf`] by checking that the path contains
957 /// valid UTF-8.
958 ///
959 /// # Errors
960 ///
961 /// Returns `Err` if the path is not UTF-8 with a description as to why the
962 /// provided component is not UTF-8.
963 ///
964 /// # Examples
965 ///
966 /// ```
967 /// use typed_path::{Path, Utf8Path, UnixEncoding, Utf8UnixEncoding};
968 ///
969 /// let path = Path::<UnixEncoding>::new(&[0xf0, 0x9f, 0x92, 0x96]);
970 /// let utf8_path = Utf8Path::<Utf8UnixEncoding>::from_bytes_path(&path).unwrap();
971 /// assert_eq!(utf8_path.as_str(), "💖");
972 /// ```
973 pub fn from_bytes_path<U>(path: &Path<U>) -> Result<&Self, Utf8Error>
974 where
975 U: Encoding,
976 {
977 Ok(Self::new(core::str::from_utf8(path.as_bytes())?))
978 }
979
980 /// Converts a non-UTF-8 [`Path`] to a UTF-8 [`Utf8Path`] without checking that the path
981 /// contains valid UTF-8.
982 ///
983 /// See the safe version, [`from_bytes_path`], for more information.
984 ///
985 /// [`from_bytes_path`]: Utf8Path::from_bytes_path
986 ///
987 /// # Safety
988 ///
989 /// The path passed in must be valid UTF-8.
990 ///
991 /// # Examples
992 ///
993 /// ```
994 /// use typed_path::{Path, Utf8Path, UnixEncoding, Utf8UnixEncoding};
995 ///
996 /// let path = Path::<UnixEncoding>::new(&[0xf0, 0x9f, 0x92, 0x96]);
997 /// let utf8_path = unsafe {
998 /// Utf8Path::<Utf8UnixEncoding>::from_bytes_path_unchecked(&path)
999 /// };
1000 /// assert_eq!(utf8_path.as_str(), "💖");
1001 /// ```
1002 pub unsafe fn from_bytes_path_unchecked<U>(path: &Path<U>) -> &Self
1003 where
1004 U: Encoding,
1005 {
1006 Self::new(core::str::from_utf8_unchecked(path.as_bytes()))
1007 }
1008
1009 /// Converts a UTF-8 [`Utf8Path`] to a non-UTF-8 [`Path`].
1010 ///
1011 /// # Examples
1012 ///
1013 /// ```
1014 /// use typed_path::{Path, Utf8Path, UnixEncoding, Utf8UnixEncoding};
1015 ///
1016 /// let utf8_path = Utf8Path::<Utf8UnixEncoding>::new("💖");
1017 /// let path = utf8_path.as_bytes_path::<UnixEncoding>();
1018 /// assert_eq!(path.as_bytes(), &[0xf0, 0x9f, 0x92, 0x96]);
1019 /// ```
1020 pub fn as_bytes_path<U>(&self) -> &Path<U>
1021 where
1022 U: Encoding,
1023 {
1024 Path::new(self.as_str())
1025 }
1026}
1027
1028impl<T> Clone for Box<Utf8Path<T>>
1029where
1030 T: Utf8Encoding,
1031{
1032 fn clone(&self) -> Self {
1033 self.to_path_buf().into_boxed_path()
1034 }
1035}
1036
1037impl<T> AsRef<[u8]> for Utf8Path<T>
1038where
1039 T: Utf8Encoding,
1040{
1041 #[inline]
1042 fn as_ref(&self) -> &[u8] {
1043 self.inner.as_bytes()
1044 }
1045}
1046
1047impl<T> AsRef<str> for Utf8Path<T>
1048where
1049 T: Utf8Encoding,
1050{
1051 #[inline]
1052 fn as_ref(&self) -> &str {
1053 &self.inner
1054 }
1055}
1056
1057impl<T> AsRef<Utf8Path<T>> for Utf8Path<T>
1058where
1059 T: Utf8Encoding,
1060{
1061 #[inline]
1062 fn as_ref(&self) -> &Utf8Path<T> {
1063 self
1064 }
1065}
1066
1067impl<T> AsRef<Utf8Path<T>> for str
1068where
1069 T: Utf8Encoding,
1070{
1071 #[inline]
1072 fn as_ref(&self) -> &Utf8Path<T> {
1073 Utf8Path::new(self)
1074 }
1075}
1076
1077impl<T> AsRef<Utf8Path<T>> for String
1078where
1079 T: Utf8Encoding,
1080{
1081 #[inline]
1082 fn as_ref(&self) -> &Utf8Path<T> {
1083 Utf8Path::new(self)
1084 }
1085}
1086
1087impl<T> fmt::Debug for Utf8Path<T>
1088where
1089 T: Utf8Encoding,
1090{
1091 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1092 f.debug_struct("Utf8Path")
1093 .field("_encoding", &T::label())
1094 .field("inner", &&self.inner)
1095 .finish()
1096 }
1097}
1098
1099impl<T> fmt::Display for Utf8Path<T>
1100where
1101 T: Utf8Encoding,
1102{
1103 /// Format path into a [`String`] using the underlying [`str`] representation.
1104 ///
1105 /// # Examples
1106 ///
1107 /// ```
1108 /// use typed_path::{Utf8Path, Utf8UnixEncoding};
1109 ///
1110 /// // NOTE: A path cannot be created on its own without a defined encoding
1111 /// let s = Utf8Path::<Utf8UnixEncoding>::new("foo.txt").to_string();
1112 /// assert_eq!(s, "foo.txt");
1113 /// ```
1114 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1115 fmt::Display::fmt(&self.inner, formatter)
1116 }
1117}
1118
1119impl<T> cmp::PartialEq for Utf8Path<T>
1120where
1121 T: Utf8Encoding,
1122{
1123 #[inline]
1124 fn eq(&self, other: &Utf8Path<T>) -> bool {
1125 self.components() == other.components()
1126 }
1127}
1128
1129impl<T> cmp::Eq for Utf8Path<T> where T: Utf8Encoding {}
1130
1131impl<T> Hash for Utf8Path<T>
1132where
1133 T: Utf8Encoding,
1134{
1135 fn hash<H: Hasher>(&self, h: &mut H) {
1136 T::hash(self.as_str(), h)
1137 }
1138}
1139
1140impl<T> cmp::PartialOrd for Utf8Path<T>
1141where
1142 T: Utf8Encoding,
1143{
1144 #[inline]
1145 fn partial_cmp(&self, other: &Utf8Path<T>) -> Option<cmp::Ordering> {
1146 Some(self.cmp(other))
1147 }
1148}
1149
1150impl<T> cmp::Ord for Utf8Path<T>
1151where
1152 T: Utf8Encoding,
1153{
1154 #[inline]
1155 fn cmp(&self, other: &Utf8Path<T>) -> cmp::Ordering {
1156 self.components().cmp(other.components())
1157 }
1158}
1159
1160impl<T> From<&Utf8Path<T>> for Box<Utf8Path<T>>
1161where
1162 T: Utf8Encoding,
1163{
1164 /// Creates a boxed [`Utf8Path`] from a reference.
1165 ///
1166 /// This will allocate and clone `path` to it.
1167 fn from(path: &Utf8Path<T>) -> Self {
1168 let boxed: Box<str> = path.inner.into();
1169 let rw = Box::into_raw(boxed) as *mut Utf8Path<T>;
1170 unsafe { Box::from_raw(rw) }
1171 }
1172}
1173
1174impl<T> From<Cow<'_, Utf8Path<T>>> for Box<Utf8Path<T>>
1175where
1176 T: Utf8Encoding,
1177{
1178 /// Creates a boxed [`Utf8Path`] from a clone-on-write pointer.
1179 ///
1180 /// Converting from a `Cow::Owned` does not clone or allocate.
1181 #[inline]
1182 fn from(cow: Cow<'_, Utf8Path<T>>) -> Box<Utf8Path<T>> {
1183 match cow {
1184 Cow::Borrowed(path) => Box::from(path),
1185 Cow::Owned(path) => Box::from(path),
1186 }
1187 }
1188}
1189
1190impl<T> From<Utf8PathBuf<T>> for Box<Utf8Path<T>>
1191where
1192 T: Utf8Encoding,
1193{
1194 /// Converts a [`Utf8PathBuf`] into a <code>[Box]<[Utf8Path]></code>.
1195 ///
1196 /// This conversion currently should not allocate memory,
1197 /// but this behavior is not guaranteed on all platforms or in all future versions.
1198 #[inline]
1199 fn from(p: Utf8PathBuf<T>) -> Box<Utf8Path<T>> {
1200 p.into_boxed_path()
1201 }
1202}
1203
1204impl<'a, T> From<&'a Utf8Path<T>> for Cow<'a, Utf8Path<T>>
1205where
1206 T: Utf8Encoding,
1207{
1208 /// Creates a clone-on-write pointer from a reference to
1209 /// [`Utf8Path`].
1210 ///
1211 /// This conversion does not clone or allocate.
1212 #[inline]
1213 fn from(s: &'a Utf8Path<T>) -> Self {
1214 Cow::Borrowed(s)
1215 }
1216}
1217
1218impl<T> From<Utf8PathBuf<T>> for Cow<'_, Utf8Path<T>>
1219where
1220 T: Utf8Encoding,
1221{
1222 /// Creates a clone-on-write pointer from an owned
1223 /// instance of [`Utf8PathBuf`].
1224 ///
1225 /// This conversion does not clone or allocate.
1226 #[inline]
1227 fn from(s: Utf8PathBuf<T>) -> Self {
1228 Cow::Owned(s)
1229 }
1230}
1231
1232impl<'a, T> From<&'a Utf8PathBuf<T>> for Cow<'a, Utf8Path<T>>
1233where
1234 T: Utf8Encoding,
1235{
1236 /// Creates a clone-on-write pointer from a reference to
1237 /// [`Utf8PathBuf`].
1238 ///
1239 /// This conversion does not clone or allocate.
1240 #[inline]
1241 fn from(p: &'a Utf8PathBuf<T>) -> Self {
1242 Cow::Borrowed(p.as_path())
1243 }
1244}
1245
1246#[cfg(target_has_atomic = "ptr")]
1247impl<T> From<Utf8PathBuf<T>> for Arc<Utf8Path<T>>
1248where
1249 T: Utf8Encoding,
1250{
1251 /// Converts a [`Utf8PathBuf`] into an <code>[Arc]<[Utf8Path]></code> by moving the [`Utf8PathBuf`] data
1252 /// into a new [`Arc`] buffer.
1253 #[inline]
1254 fn from(path_buf: Utf8PathBuf<T>) -> Self {
1255 let arc: Arc<str> = Arc::from(path_buf.into_string());
1256 unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Utf8Path<T>) }
1257 }
1258}
1259
1260#[cfg(target_has_atomic = "ptr")]
1261impl<T> From<&Utf8Path<T>> for Arc<Utf8Path<T>>
1262where
1263 T: Utf8Encoding,
1264{
1265 /// Converts a [`Utf8Path`] into an [`Arc`] by copying the [`Utf8Path`] data into a new [`Arc`] buffer.
1266 #[inline]
1267 fn from(path: &Utf8Path<T>) -> Self {
1268 let arc: Arc<str> = Arc::from(path.as_str().to_string());
1269 unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Utf8Path<T>) }
1270 }
1271}
1272
1273impl<T> From<Utf8PathBuf<T>> for Rc<Utf8Path<T>>
1274where
1275 T: Utf8Encoding,
1276{
1277 /// Converts a [`Utf8PathBuf`] into an <code>[Rc]<[Utf8Path]></code> by moving the [`Utf8PathBuf`] data into
1278 /// a new [`Rc`] buffer.
1279 #[inline]
1280 fn from(path_buf: Utf8PathBuf<T>) -> Self {
1281 let rc: Rc<str> = Rc::from(path_buf.into_string());
1282 unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Utf8Path<T>) }
1283 }
1284}
1285
1286impl<T> From<&Utf8Path<T>> for Rc<Utf8Path<T>>
1287where
1288 T: Utf8Encoding,
1289{
1290 /// Converts a [`Utf8Path`] into an [`Rc`] by copying the [`Utf8Path`] data into a new [`Rc`] buffer.
1291 #[inline]
1292 fn from(path: &Utf8Path<T>) -> Self {
1293 let rc: Rc<str> = Rc::from(path.as_str());
1294 unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Utf8Path<T>) }
1295 }
1296}
1297
1298impl<'a, T> IntoIterator for &'a Utf8Path<T>
1299where
1300 T: Utf8Encoding,
1301{
1302 type IntoIter = Utf8Iter<'a, T>;
1303 type Item = &'a str;
1304
1305 #[inline]
1306 fn into_iter(self) -> Self::IntoIter {
1307 self.iter()
1308 }
1309}
1310
1311impl<T> ToOwned for Utf8Path<T>
1312where
1313 T: Utf8Encoding,
1314{
1315 type Owned = Utf8PathBuf<T>;
1316
1317 #[inline]
1318 fn to_owned(&self) -> Self::Owned {
1319 self.to_path_buf()
1320 }
1321}
1322
1323macro_rules! impl_cmp {
1324 ($($lt:lifetime),* ; $lhs:ty, $rhs: ty) => {
1325 impl<$($lt,)* T> PartialEq<$rhs> for $lhs
1326 where
1327 T: Utf8Encoding,
1328 {
1329 #[inline]
1330 fn eq(&self, other: &$rhs) -> bool {
1331 <Utf8Path<T> as PartialEq>::eq(self, other)
1332 }
1333 }
1334
1335 impl<$($lt,)* T> PartialEq<$lhs> for $rhs
1336 where
1337 T: Utf8Encoding,
1338 {
1339 #[inline]
1340 fn eq(&self, other: &$lhs) -> bool {
1341 <Utf8Path<T> as PartialEq>::eq(self, other)
1342 }
1343 }
1344
1345 impl<$($lt,)* T> PartialOrd<$rhs> for $lhs
1346 where
1347 T: Utf8Encoding,
1348 {
1349 #[inline]
1350 fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
1351 <Utf8Path<T> as PartialOrd>::partial_cmp(self, other)
1352 }
1353 }
1354
1355 impl<$($lt,)* T> PartialOrd<$lhs> for $rhs
1356 where
1357 T: Utf8Encoding,
1358 {
1359 #[inline]
1360 fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
1361 <Utf8Path<T> as PartialOrd>::partial_cmp(self, other)
1362 }
1363 }
1364 };
1365}
1366
1367impl_cmp!(; Utf8PathBuf<T>, Utf8Path<T>);
1368impl_cmp!('a; Utf8PathBuf<T>, &'a Utf8Path<T>);
1369impl_cmp!('a; Cow<'a, Utf8Path<T>>, Utf8Path<T>);
1370impl_cmp!('a, 'b; Cow<'a, Utf8Path<T>>, &'b Utf8Path<T>);
1371impl_cmp!('a; Cow<'a, Utf8Path<T>>, Utf8PathBuf<T>);
1372
1373macro_rules! impl_cmp_bytes {
1374 ($($lt:lifetime),* ; $lhs:ty, $rhs: ty) => {
1375 impl<$($lt,)* T> PartialEq<$rhs> for $lhs
1376 where
1377 T: Utf8Encoding,
1378 {
1379 #[inline]
1380 fn eq(&self, other: &$rhs) -> bool {
1381 <Utf8Path<T> as PartialEq>::eq(self, other.as_ref())
1382 }
1383 }
1384
1385 impl<$($lt,)* T> PartialEq<$lhs> for $rhs
1386 where
1387 T: Utf8Encoding,
1388 {
1389 #[inline]
1390 fn eq(&self, other: &$lhs) -> bool {
1391 <Utf8Path<T> as PartialEq>::eq(self.as_ref(), other)
1392 }
1393 }
1394
1395 impl<$($lt,)* T> PartialOrd<$rhs> for $lhs
1396 where
1397 T: Utf8Encoding,
1398 {
1399 #[inline]
1400 fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
1401 <Utf8Path<T> as PartialOrd>::partial_cmp(self, other.as_ref())
1402 }
1403 }
1404
1405 impl<$($lt,)* T> PartialOrd<$lhs> for $rhs
1406 where
1407 T: Utf8Encoding,
1408 {
1409 #[inline]
1410 fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
1411 <Utf8Path<T> as PartialOrd>::partial_cmp(self.as_ref(), other)
1412 }
1413 }
1414 };
1415}
1416
1417impl_cmp_bytes!(; Utf8PathBuf<T>, str);
1418impl_cmp_bytes!('a; Utf8PathBuf<T>, &'a str);
1419impl_cmp_bytes!(; Utf8PathBuf<T>, String);
1420impl_cmp_bytes!(; Utf8Path<T>, str);
1421impl_cmp_bytes!('a; Utf8Path<T>, &'a str);
1422impl_cmp_bytes!(; Utf8Path<T>, String);
1423impl_cmp_bytes!('a; &'a Utf8Path<T>, str);
1424impl_cmp_bytes!('a; &'a Utf8Path<T>, String);
1425
1426mod helpers {
1427 use super::*;
1428
1429 pub fn rsplit_file_at_dot(file: &str) -> (Option<&str>, Option<&str>) {
1430 if file == ".." {
1431 return (Some(file), None);
1432 }
1433
1434 let mut iter = file.rsplitn(2, '.');
1435 let after = iter.next();
1436 let before = iter.next();
1437 if before == Some("") {
1438 (Some(file), None)
1439 } else {
1440 (before, after)
1441 }
1442 }
1443
1444 // Iterate through `iter` while it matches `prefix`; return `None` if `prefix`
1445 // is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving
1446 // `iter` after having exhausted `prefix`.
1447 pub fn iter_after<'a, 'b, T, U, I, J>(mut iter: I, mut prefix: J) -> Option<I>
1448 where
1449 T: Utf8Component<'a>,
1450 U: Utf8Component<'b>,
1451 I: Iterator<Item = T> + Clone,
1452 J: Iterator<Item = U>,
1453 {
1454 loop {
1455 let mut iter_next = iter.clone();
1456 match (iter_next.next(), prefix.next()) {
1457 // TODO: Because there is not a `Component` struct, there is no direct comparison
1458 // between T and U since they aren't the same type due to different
1459 // lifetimes. We get around this with an equality check by converting these
1460 // components to bytes, which should work for the Unix and Windows component
1461 // implementations, but is error-prone as any new implementation could
1462 // deviate in a way that breaks this subtlely. Instead, need to figure out
1463 // either how to bring back equality of x == y WITHOUT needing to have
1464 // T: PartialEq<U> because that causes lifetime problems for `strip_prefix`
1465 (Some(ref x), Some(ref y)) if x.as_str() == y.as_str() => (),
1466 (Some(_), Some(_)) => return None,
1467 (Some(_), None) => return Some(iter),
1468 (None, None) => return Some(iter),
1469 (None, Some(_)) => return None,
1470 }
1471 iter = iter_next;
1472 }
1473 }
1474}
1475
1476#[cfg(feature = "serde")]
1477struct Utf8PathVisitor<T>(PhantomData<T>)
1478where
1479 T: Utf8Encoding;
1480
1481#[cfg(feature = "serde")]
1482impl<'a, T> Visitor<'a> for Utf8PathVisitor<T>
1483where
1484 T: Utf8Encoding + 'a,
1485{
1486 type Value = &'a Utf8Path<T>;
1487
1488 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1489 formatter.write_str("a borrowed UTF-8 path")
1490 }
1491
1492 fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
1493 where
1494 E: de::Error,
1495 {
1496 Ok(Utf8Path::new(v))
1497 }
1498
1499 fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
1500 where
1501 E: de::Error,
1502 {
1503 core::str::from_utf8(v)
1504 .map(str::as_ref)
1505 .map_err(|_| de::Error::invalid_value(Unexpected::Bytes(v), &self))
1506 }
1507}
1508
1509#[cfg(feature = "serde")]
1510impl<T> Serialize for Utf8Path<T>
1511where
1512 T: Utf8Encoding,
1513{
1514 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1515 where
1516 S: Serializer,
1517 {
1518 self.as_str().serialize(serializer)
1519 }
1520}
1521
1522#[cfg(feature = "serde")]
1523impl<'de: 'a, 'a, T> Deserialize<'de> for &'a Utf8Path<T>
1524where
1525 T: Utf8Encoding + 'de,
1526{
1527 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1528 where
1529 D: Deserializer<'de>,
1530 {
1531 deserializer.deserialize_str(Utf8PathVisitor(PhantomData))
1532 }
1533}
1534
1535#[cfg(feature = "serde")]
1536impl<'de, T> Deserialize<'de> for Box<Utf8Path<T>>
1537where
1538 T: Utf8Encoding,
1539{
1540 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1541 where
1542 D: Deserializer<'de>,
1543 {
1544 Deserialize::deserialize(deserializer).map(Utf8PathBuf::into_boxed_path)
1545 }
1546}
1547
1548#[cfg(any(
1549 unix,
1550 all(target_vendor = "fortanix", target_env = "sgx"),
1551 target_os = "solid_asp3",
1552 target_os = "hermit",
1553 all(
1554 target_os = "wasi",
1555 any(not(target_env = "p2"), feature = "nightly-wasip2")
1556 )
1557))]
1558#[cfg(feature = "std")]
1559mod std_conversions {
1560 use std::ffi::{OsStr, OsString};
1561 #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
1562 use std::os::fortanix_sgx as os;
1563 #[cfg(target_os = "solid_asp3")]
1564 use std::os::solid as os;
1565 #[cfg(any(target_os = "hermit", unix))]
1566 use std::os::unix as os;
1567 #[cfg(target_os = "wasi")]
1568 use std::os::wasi as os;
1569
1570 use os::ffi::OsStrExt;
1571
1572 use super::*;
1573 use crate::common::TryAsRef;
1574
1575 impl<T> TryAsRef<Utf8Path<T>> for OsStr
1576 where
1577 T: Utf8Encoding,
1578 {
1579 #[inline]
1580 fn try_as_ref(&self) -> Option<&Utf8Path<T>> {
1581 std::str::from_utf8(self.as_bytes()).ok().map(Utf8Path::new)
1582 }
1583 }
1584
1585 impl<T> TryAsRef<Utf8Path<T>> for OsString
1586 where
1587 T: Utf8Encoding,
1588 {
1589 #[inline]
1590 fn try_as_ref(&self) -> Option<&Utf8Path<T>> {
1591 std::str::from_utf8(self.as_bytes()).ok().map(Utf8Path::new)
1592 }
1593 }
1594
1595 impl<T> AsRef<OsStr> for Utf8Path<T>
1596 where
1597 T: Utf8Encoding,
1598 {
1599 #[inline]
1600 fn as_ref(&self) -> &OsStr {
1601 OsStrExt::from_bytes(self.as_str().as_bytes())
1602 }
1603 }
1604}
1605
1606#[cfg(all(test, feature = "serde"))]
1607mod tests {
1608 use crate::Utf8WindowsPath;
1609
1610 #[test]
1611 fn deserialize_invalid_utf8() {
1612 let bytes = vec![b'"', b'f', b'o', b'o', b'\\', 0xff, b'"'];
1613
1614 assert!(serde_json::from_slice::<&Utf8WindowsPath>(&bytes).is_err());
1615 }
1616}