typed_path/typed/utf8/
pathbuf.rs

1use alloc::collections::TryReserveError;
2use core::convert::TryFrom;
3use core::fmt;
4
5use crate::common::{CheckedPathError, StripPrefixError};
6use crate::no_std_compat::*;
7use crate::typed::{
8    PathType, Utf8TypedAncestors, Utf8TypedComponents, Utf8TypedIter, Utf8TypedPath,
9};
10use crate::unix::{Utf8UnixPath, Utf8UnixPathBuf};
11use crate::windows::{Utf8WindowsPath, Utf8WindowsPathBuf};
12
13/// Represents a pathbuf with a known type that can be one of:
14///
15/// * [`Utf8UnixPathBuf`]
16/// * [`Utf8WindowsPathBuf`]
17#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub enum Utf8TypedPathBuf {
19    Unix(Utf8UnixPathBuf),
20    Windows(Utf8WindowsPathBuf),
21}
22
23impl Utf8TypedPathBuf {
24    /// Returns true if this path represents a Unix path.
25    #[inline]
26    pub fn is_unix(&self) -> bool {
27        matches!(self, Self::Unix(_))
28    }
29
30    /// Returns true if this path represents a Windows path.
31    #[inline]
32    pub fn is_windows(&self) -> bool {
33        matches!(self, Self::Windows(_))
34    }
35
36    /// Converts this [`Utf8TypedPathBuf`] into the Unix variant.
37    pub fn with_unix_encoding(&self) -> Utf8TypedPathBuf {
38        match self {
39            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding()),
40            _ => self.clone(),
41        }
42    }
43
44    /// Converts this [`Utf8TypedPathBuf`] into the Unix variant, ensuring it is valid as a Unix
45    /// path.
46    pub fn with_unix_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
47        Ok(match self {
48            Self::Unix(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
49            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
50        })
51    }
52
53    /// Converts this [`Utf8TypedPathBuf`] into the Windows variant.
54    pub fn with_windows_encoding(&self) -> Utf8TypedPathBuf {
55        match self {
56            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding()),
57            _ => self.clone(),
58        }
59    }
60
61    /// Converts this [`Utf8TypedPathBuf`] into the Windows variant, ensuring it is valid as a
62    /// Windows path.
63    pub fn with_windows_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
64        Ok(match self {
65            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
66            Self::Windows(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
67        })
68    }
69
70    /// Allocates an empty [`Utf8TypedPathBuf`] for the specified path type.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use typed_path::{PathType, Utf8TypedPathBuf};
76    /// let _unix_path = Utf8TypedPathBuf::new(PathType::Unix);
77    /// let _windows_path = Utf8TypedPathBuf::new(PathType::Windows);
78    /// ```
79    #[inline]
80    pub fn new(r#type: PathType) -> Self {
81        match r#type {
82            PathType::Unix => Self::unix(),
83            PathType::Windows => Self::windows(),
84        }
85    }
86
87    /// Allocates an empty [`Utf8TypedPathBuf`] as a Unix path.
88    #[inline]
89    pub fn unix() -> Self {
90        Self::Unix(Utf8UnixPathBuf::new())
91    }
92
93    /// Allocates an empty [`Utf8TypedPathBuf`] as a Windows path.
94    #[inline]
95    pub fn windows() -> Self {
96        Self::Windows(Utf8WindowsPathBuf::new())
97    }
98
99    /// Creates a new [`Utf8TypedPathBuf`] from the bytes representing a Unix path.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use typed_path::Utf8TypedPathBuf;
105    /// let path = Utf8TypedPathBuf::from_unix("/tmp");
106    /// ```
107    pub fn from_unix(s: impl AsRef<str>) -> Self {
108        Self::Unix(Utf8UnixPathBuf::from(s.as_ref()))
109    }
110
111    /// Creates a new [`Utf8TypedPathBuf`] from the bytes representing a Windows path.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use typed_path::Utf8TypedPathBuf;
117    /// let path = Utf8TypedPathBuf::from_windows(r"C:\tmp");
118    /// ```
119    pub fn from_windows(s: impl AsRef<str>) -> Self {
120        Self::Windows(Utf8WindowsPathBuf::from(s.as_ref()))
121    }
122
123    /// Converts into a [`Utf8TypedPath`].
124    pub fn to_path(&self) -> Utf8TypedPath<'_> {
125        match self {
126            Self::Unix(path) => Utf8TypedPath::Unix(path.as_path()),
127            Self::Windows(path) => Utf8TypedPath::Windows(path.as_path()),
128        }
129    }
130
131    /// Extends `self` with `path`.
132    ///
133    /// If `path` is absolute, it replaces the current path.
134    ///
135    /// With [`Utf8WindowsPathBuf`]:
136    ///
137    /// * if `path` has a root but no prefix (e.g., `\windows`), it
138    ///   replaces everything except for the prefix (if any) of `self`.
139    /// * if `path` has a prefix but no root, it replaces `self`.
140    /// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`)
141    ///   and `path` is not empty, the new path is normalized: all references
142    ///   to `.` and `..` are removed.
143    ///
144    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
145    ///
146    /// # Difference from PathBuf
147    ///
148    /// Unlike [`Utf8PathBuf::push`], this implementation only supports types that implement
149    /// `AsRef<str>` instead of `AsRef<Path>`.
150    ///
151    /// [`Utf8PathBuf::push`]: crate::Utf8PathBuf::push
152    ///
153    /// # Examples
154    ///
155    /// Pushing a relative path extends the existing path:
156    ///
157    /// ```
158    /// use typed_path::Utf8TypedPathBuf;
159    ///
160    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
161    /// path.push("file.bk");
162    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp/file.bk"));
163    /// ```
164    ///
165    /// Pushing an absolute path replaces the existing path:
166    ///
167    /// ```
168    /// use typed_path::Utf8TypedPathBuf;
169    ///
170    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
171    /// path.push("/etc");
172    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/etc"));
173    /// ```
174    pub fn push(&mut self, path: impl AsRef<str>) {
175        match self {
176            Self::Unix(a) => a.push(Utf8UnixPath::new(&path)),
177            Self::Windows(a) => a.push(Utf8WindowsPath::new(&path)),
178        }
179    }
180
181    /// Like [`Utf8TypedPathBuf::push`], extends `self` with `path`, but also checks to ensure that
182    /// `path` abides by a set of rules.
183    ///
184    /// # Rules
185    ///
186    /// 1. `path` cannot contain a prefix component.
187    /// 2. `path` cannot contain a root component.
188    /// 3. `path` cannot contain invalid filename bytes.
189    /// 4. `path` cannot contain parent components such that the current path would be escaped.
190    ///
191    /// # Difference from PathBuf
192    ///
193    /// Unlike [`PathBuf::push_checked`], this implementation only supports types that implement
194    /// `AsRef<str>` instead of `AsRef<Path>`.
195    ///
196    /// [`PathBuf::push_checked`]: crate::PathBuf::push_checked
197    ///
198    /// # Examples
199    ///
200    /// Pushing a relative path extends the existing path:
201    ///
202    /// ```
203    /// use typed_path::Utf8TypedPathBuf;
204    ///
205    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
206    /// assert!(path.push_checked("file.bk").is_ok());
207    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp/file.bk"));
208    /// ```
209    ///
210    /// Pushing a relative path that contains unresolved parent directory references fails
211    /// with an error:
212    ///
213    /// ```
214    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
215    ///
216    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
217    ///
218    /// // Pushing a relative path that contains parent directory references that cannot be
219    /// // resolved within the path is considered an error as this is considered a path
220    /// // traversal attack!
221    /// assert_eq!(path.push_checked(".."), Err(CheckedPathError::PathTraversalAttack));
222    /// assert_eq!(path, Utf8TypedPathBuf::from("/tmp"));
223    /// ```
224    ///
225    /// Pushing an absolute path fails with an error:
226    ///
227    /// ```
228    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
229    ///
230    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
231    ///
232    /// // Pushing an absolute path will fail with an error
233    /// assert_eq!(path.push_checked("/etc"), Err(CheckedPathError::UnexpectedRoot));
234    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp"));
235    /// ```
236    pub fn push_checked(&mut self, path: impl AsRef<str>) -> Result<(), CheckedPathError> {
237        match self {
238            Self::Unix(a) => a.push_checked(Utf8UnixPath::new(&path)),
239            Self::Windows(a) => a.push_checked(Utf8WindowsPath::new(&path)),
240        }
241    }
242
243    /// Truncates `self` to [`self.parent`].
244    ///
245    /// Returns `false` and does nothing if [`self.parent`] is [`None`].
246    /// Otherwise, returns `true`.
247    ///
248    /// [`self.parent`]: Utf8TypedPath::parent
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
254    ///
255    /// let mut p = Utf8TypedPathBuf::from_unix("/spirited/away.rs");
256    ///
257    /// p.pop();
258    /// assert_eq!(Utf8TypedPath::derive("/spirited"), p);
259    /// p.pop();
260    /// assert_eq!(Utf8TypedPath::derive("/"), p);
261    /// ```
262    pub fn pop(&mut self) -> bool {
263        impl_typed_fn!(self, pop)
264    }
265
266    /// Updates [`self.file_name`] to `file_name`.
267    ///
268    /// If [`self.file_name`] was [`None`], this is equivalent to pushing
269    /// `file_name`.
270    ///
271    /// Otherwise it is equivalent to calling [`pop`] and then pushing
272    /// `file_name`. The new path will be a sibling of the original path.
273    /// (That is, it will have the same parent.)
274    ///
275    /// [`self.file_name`]: Utf8TypedPath::file_name
276    /// [`pop`]: Utf8TypedPathBuf::pop
277    ///
278    /// # Examples
279    ///
280    /// ```
281    /// use typed_path::Utf8TypedPathBuf;
282    ///
283    /// let mut buf = Utf8TypedPathBuf::from_unix("/");
284    /// assert!(buf.file_name() == None);
285    /// buf.set_file_name("bar");
286    /// assert!(buf == Utf8TypedPathBuf::from_unix("/bar"));
287    /// assert!(buf.file_name().is_some());
288    /// buf.set_file_name("baz.txt");
289    /// assert!(buf == Utf8TypedPathBuf::from_unix("/baz.txt"));
290    /// ```
291    pub fn set_file_name<S: AsRef<str>>(&mut self, file_name: S) {
292        impl_typed_fn!(self, set_file_name, file_name)
293    }
294
295    /// Updates [`self.extension`] to `extension`.
296    ///
297    /// Returns `false` and does nothing if [`self.file_name`] is [`None`],
298    /// returns `true` and updates the extension otherwise.
299    ///
300    /// If [`self.extension`] is [`None`], the extension is added; otherwise
301    /// it is replaced.
302    ///
303    /// [`self.file_name`]: Utf8TypedPath::file_name
304    /// [`self.extension`]: Utf8TypedPath::extension
305    ///
306    /// # Examples
307    ///
308    /// ```
309    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
310    ///
311    /// let mut p = Utf8TypedPathBuf::from_unix("/feel/the");
312    ///
313    /// p.set_extension("force");
314    /// assert_eq!(Utf8TypedPath::derive("/feel/the.force"), p.to_path());
315    ///
316    /// p.set_extension("dark_side");
317    /// assert_eq!(Utf8TypedPath::derive("/feel/the.dark_side"), p.to_path());
318    /// ```
319    pub fn set_extension<S: AsRef<str>>(&mut self, extension: S) -> bool {
320        impl_typed_fn!(self, set_extension, extension)
321    }
322
323    /// Consumes the [`Utf8TypedPathBuf`], yielding its internal [`String`] storage.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use typed_path::Utf8TypedPathBuf;
329    ///
330    /// let p = Utf8TypedPathBuf::from_unix("/the/head");
331    /// let string = p.into_string();
332    /// assert_eq!(string, "/the/head");
333    /// ```
334    #[inline]
335    pub fn into_string(self) -> String {
336        impl_typed_fn!(self, into_string)
337    }
338
339    /// Invokes [`capacity`] on the underlying instance of [`Vec`].
340    ///
341    /// [`capacity`]: Vec::capacity
342    #[inline]
343    pub fn capacity(&self) -> usize {
344        impl_typed_fn!(self, capacity)
345    }
346
347    /// Invokes [`clear`] on the underlying instance of [`Vec`].
348    ///
349    /// [`clear`]: Vec::clear
350    #[inline]
351    pub fn clear(&mut self) {
352        impl_typed_fn!(self, clear)
353    }
354
355    /// Invokes [`reserve`] on the underlying instance of [`Vec`].
356    ///
357    /// [`reserve`]: Vec::reserve
358    #[inline]
359    pub fn reserve(&mut self, additional: usize) {
360        impl_typed_fn!(self, reserve, additional)
361    }
362
363    /// Invokes [`try_reserve`] on the underlying instance of [`Vec`].
364    ///
365    /// [`try_reserve`]: Vec::try_reserve
366    #[inline]
367    pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
368        impl_typed_fn!(self, try_reserve, additional)
369    }
370
371    /// Invokes [`reserve_exact`] on the underlying instance of [`Vec`].
372    ///
373    /// [`reserve_exact`]: Vec::reserve_exact
374    #[inline]
375    pub fn reserve_exact(&mut self, additional: usize) {
376        impl_typed_fn!(self, reserve_exact, additional)
377    }
378
379    /// Invokes [`try_reserve_exact`] on the underlying instance of [`Vec`].
380    ///
381    /// [`try_reserve_exact`]: Vec::try_reserve_exact
382    #[inline]
383    pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
384        impl_typed_fn!(self, try_reserve_exact, additional)
385    }
386
387    /// Invokes [`shrink_to_fit`] on the underlying instance of [`Vec`].
388    ///
389    /// [`shrink_to_fit`]: Vec::shrink_to_fit
390    #[inline]
391    pub fn shrink_to_fit(&mut self) {
392        impl_typed_fn!(self, shrink_to_fit)
393    }
394
395    /// Invokes [`shrink_to`] on the underlying instance of [`Vec`].
396    ///
397    /// [`shrink_to`]: Vec::shrink_to
398    #[inline]
399    pub fn shrink_to(&mut self, min_capacity: usize) {
400        impl_typed_fn!(self, shrink_to, min_capacity)
401    }
402}
403
404/// Reimplementation of [`Utf8TypedPath`] methods as we cannot implement [`std::ops::Deref`]
405/// directly.
406impl Utf8TypedPathBuf {
407    /// Yields the underlying [`str`] slice.
408    ///
409    /// # Examples
410    ///
411    /// ```
412    /// use typed_path::Utf8TypedPathBuf;
413    ///
414    /// let string = Utf8TypedPathBuf::from("foo.txt").as_str().to_string();
415    /// assert_eq!(string, "foo.txt");
416    /// ```
417    pub fn as_str(&self) -> &str {
418        impl_typed_fn!(self, as_str)
419    }
420
421    /// Returns `true` if the [`Utf8TypedPathBuf`] is absolute, i.e., if it is independent of
422    /// the current directory.
423    ///
424    /// * On Unix ([`Utf8UnixPathBuf`]]), a path is absolute if it starts with the root, so
425    ///   `is_absolute` and [`has_root`] are equivalent.
426    ///
427    /// * On Windows ([`Utf8WindowsPathBuf`]), a path is absolute if it has a prefix and starts with
428    ///   the root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
429    ///
430    /// [`Utf8UnixPathBuf`]: crate::Utf8UnixPathBuf
431    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
432    ///
433    /// # Examples
434    ///
435    /// ```
436    /// use typed_path::Utf8TypedPathBuf;
437    ///
438    /// assert!(!Utf8TypedPathBuf::from("foo.txt").is_absolute());
439    /// ```
440    ///
441    /// [`has_root`]: Utf8TypedPathBuf::has_root
442    pub fn is_absolute(&self) -> bool {
443        impl_typed_fn!(self, is_absolute)
444    }
445
446    /// Returns `true` if the [`Utf8TypedPathBuf`] is relative, i.e., not absolute.
447    ///
448    /// See [`is_absolute`]'s documentation for more details.
449    ///
450    /// # Examples
451    ///
452    /// ```
453    /// use typed_path::Utf8TypedPathBuf;
454    ///
455    /// assert!(Utf8TypedPathBuf::from("foo.txt").is_relative());
456    /// ```
457    ///
458    /// [`is_absolute`]: Utf8TypedPathBuf::is_absolute
459    #[inline]
460    pub fn is_relative(&self) -> bool {
461        impl_typed_fn!(self, is_relative)
462    }
463
464    /// Returns `true` if the [`Utf8TypedPathBuf`] has a root.
465    ///
466    /// * On Unix ([`Utf8UnixPathBuf`]), a path has a root if it begins with `/`.
467    ///
468    /// * On Windows ([`Utf8WindowsPathBuf`]), a path has a root if it:
469    ///     * has no prefix and begins with a separator, e.g., `\windows`
470    ///     * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows`
471    ///     * has any non-disk prefix, e.g., `\\server\share`
472    ///
473    /// [`Utf8UnixPathBuf`]: crate::Utf8UnixPathBuf
474    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
475    ///
476    /// # Examples
477    ///
478    /// ```
479    /// use typed_path::Utf8TypedPathBuf;
480    ///
481    /// assert!(Utf8TypedPathBuf::from("/etc/passwd").has_root());
482    /// ```
483    #[inline]
484    pub fn has_root(&self) -> bool {
485        impl_typed_fn!(self, has_root)
486    }
487
488    /// Returns a reference to the path without its final component, if there is one.
489    ///
490    /// Returns [`None`] if the path terminates in a root or prefix.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use typed_path::Utf8TypedPathBuf;
496    ///
497    /// let path = Utf8TypedPathBuf::from("/foo/bar");
498    /// let parent = path.parent().unwrap();
499    /// assert_eq!(parent, Utf8TypedPathBuf::from("/foo"));
500    ///
501    /// let grand_parent = parent.parent().unwrap();
502    /// assert_eq!(grand_parent, Utf8TypedPathBuf::from("/"));
503    /// assert_eq!(grand_parent.parent(), None);
504    /// ```
505    pub fn parent(&self) -> Option<Utf8TypedPath<'_>> {
506        self.to_path().parent()
507    }
508
509    /// Produces an iterator over [`Utf8TypedPathBuf`] and its ancestors.
510    ///
511    /// The iterator will yield the [`Utf8TypedPathBuf`] that is returned if the [`parent`] method
512    /// is used zero or more times. That means, the iterator will yield `&self`,
513    /// `&self.parent().unwrap()`, `&self.parent().unwrap().parent().unwrap()` and so on. If the
514    /// [`parent`] method returns [`None`], the iterator will do likewise. The iterator will always
515    /// yield at least one value, namely `&self`.
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
521    ///
522    /// let path = Utf8TypedPathBuf::from("/foo/bar");
523    /// let mut ancestors = path.ancestors();
524    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo/bar")));
525    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo")));
526    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/")));
527    /// assert_eq!(ancestors.next(), None);
528    ///
529    /// let path = Utf8TypedPathBuf::from("../foo/bar");
530    /// let mut ancestors = path.ancestors();
531    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo/bar")));
532    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo")));
533    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("..")));
534    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("")));
535    /// assert_eq!(ancestors.next(), None);
536    /// ```
537    ///
538    /// [`parent`]: Utf8TypedPathBuf::parent
539    #[inline]
540    pub fn ancestors(&self) -> Utf8TypedAncestors<'_> {
541        self.to_path().ancestors()
542    }
543
544    /// Returns the final component of the [`Utf8TypedPathBuf`], if there is one.
545    ///
546    /// If the path is a normal file, this is the file name. If it's the path of a directory, this
547    /// is the directory name.
548    ///
549    /// Returns [`None`] if the path terminates in `..`.
550    ///
551    /// # Examples
552    ///
553    /// ```
554    /// use typed_path::Utf8TypedPathBuf;
555    ///
556    /// assert_eq!(Some("bin"), Utf8TypedPathBuf::from("/usr/bin/").file_name());
557    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("tmp/foo.txt").file_name());
558    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("foo.txt/.").file_name());
559    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("foo.txt/.//").file_name());
560    /// assert_eq!(None, Utf8TypedPathBuf::from("foo.txt/..").file_name());
561    /// assert_eq!(None, Utf8TypedPathBuf::from("/").file_name());
562    /// ```
563    pub fn file_name(&self) -> Option<&str> {
564        impl_typed_fn!(self, file_name)
565    }
566
567    /// Returns a path that, when joined onto `base`, yields `self`.
568    ///
569    /// # Errors
570    ///
571    /// If `base` is not a prefix of `self` (i.e., [`starts_with`]
572    /// returns `false`), returns [`Err`].
573    ///
574    /// [`starts_with`]: Utf8TypedPathBuf::starts_with
575    ///
576    /// # Examples
577    ///
578    /// ```
579    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
580    ///
581    /// let path = Utf8TypedPathBuf::from("/test/haha/foo.txt");
582    ///
583    /// assert_eq!(path.strip_prefix("/"), Ok(Utf8TypedPath::derive("test/haha/foo.txt")));
584    /// assert_eq!(path.strip_prefix("/test"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
585    /// assert_eq!(path.strip_prefix("/test/"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
586    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Utf8TypedPath::derive("")));
587    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Utf8TypedPath::derive("")));
588    ///
589    /// assert!(path.strip_prefix("test").is_err());
590    /// assert!(path.strip_prefix("/haha").is_err());
591    ///
592    /// let prefix = Utf8TypedPathBuf::from("/test/");
593    /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8TypedPath::derive("haha/foo.txt")));
594    /// ```
595    pub fn strip_prefix(
596        &self,
597        base: impl AsRef<str>,
598    ) -> Result<Utf8TypedPath<'_>, StripPrefixError> {
599        match self {
600            Self::Unix(p) => p
601                .strip_prefix(Utf8UnixPath::new(&base))
602                .map(Utf8TypedPath::Unix),
603            Self::Windows(p) => p
604                .strip_prefix(Utf8WindowsPath::new(&base))
605                .map(Utf8TypedPath::Windows),
606        }
607    }
608
609    /// Determines whether `base` is a prefix of `self`.
610    ///
611    /// Only considers whole path components to match.
612    ///
613    /// # Difference from Path
614    ///
615    /// Unlike [`Utf8Path::starts_with`], this implementation only supports types that implement
616    /// `AsRef<str>` instead of `AsRef<Path>`.
617    ///
618    /// [`Utf8Path::starts_with`]: crate::Utf8Path::starts_with
619    ///
620    /// # Examples
621    ///
622    /// ```
623    /// use typed_path::Utf8TypedPathBuf;
624    ///
625    /// let path = Utf8TypedPathBuf::from("/etc/passwd");
626    ///
627    /// assert!(path.starts_with("/etc"));
628    /// assert!(path.starts_with("/etc/"));
629    /// assert!(path.starts_with("/etc/passwd"));
630    /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay
631    /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay
632    ///
633    /// assert!(!path.starts_with("/e"));
634    /// assert!(!path.starts_with("/etc/passwd.txt"));
635    ///
636    /// assert!(!Utf8TypedPathBuf::from("/etc/foo.rs").starts_with("/etc/foo"));
637    /// ```
638    pub fn starts_with(&self, base: impl AsRef<str>) -> bool {
639        self.to_path().starts_with(base)
640    }
641
642    /// Determines whether `child` is a suffix of `self`.
643    ///
644    /// Only considers whole path components to match.
645    ///
646    /// # Difference from Path
647    ///
648    /// Unlike [`Utf8Path::ends_with`], this implementation only supports types that implement
649    /// `AsRef<str>` instead of `AsRef<Path>`.
650    ///
651    /// [`Utf8Path::ends_with`]: crate::Utf8Path::ends_with
652    ///
653    /// # Examples
654    ///
655    /// ```
656    /// use typed_path::Utf8TypedPathBuf;
657    ///
658    /// let path = Utf8TypedPathBuf::from("/etc/resolv.conf");
659    ///
660    /// assert!(path.ends_with("resolv.conf"));
661    /// assert!(path.ends_with("etc/resolv.conf"));
662    /// assert!(path.ends_with("/etc/resolv.conf"));
663    ///
664    /// assert!(!path.ends_with("/resolv.conf"));
665    /// assert!(!path.ends_with("conf")); // use .extension() instead
666    /// ```
667    pub fn ends_with(&self, child: impl AsRef<str>) -> bool {
668        self.to_path().ends_with(child)
669    }
670
671    /// Extracts the stem (non-extension) portion of [`self.file_name`].
672    ///
673    /// [`self.file_name`]: Utf8TypedPathBuf::file_name
674    ///
675    /// The stem is:
676    ///
677    /// * [`None`], if there is no file name;
678    /// * The entire file name if there is no embedded `.`;
679    /// * The entire file name if the file name begins with `.` and has no other `.`s within;
680    /// * Otherwise, the portion of the file name before the final `.`
681    ///
682    /// # Examples
683    ///
684    /// ```
685    /// use typed_path::Utf8TypedPathBuf;
686    ///
687    /// assert_eq!("foo", Utf8TypedPathBuf::from("foo.rs").file_stem().unwrap());
688    /// assert_eq!("foo.tar", Utf8TypedPathBuf::from("foo.tar.gz").file_stem().unwrap());
689    /// ```
690    ///
691    pub fn file_stem(&self) -> Option<&str> {
692        impl_typed_fn!(self, file_stem)
693    }
694
695    /// Extracts the extension of [`self.file_name`], if possible.
696    ///
697    /// The extension is:
698    ///
699    /// * [`None`], if there is no file name;
700    /// * [`None`], if there is no embedded `.`;
701    /// * [`None`], if the file name begins with `.` and has no other `.`s within;
702    /// * Otherwise, the portion of the file name after the final `.`
703    ///
704    /// [`self.file_name`]: Utf8TypedPathBuf::file_name
705    ///
706    /// # Examples
707    ///
708    /// ```
709    /// use typed_path::Utf8TypedPathBuf;
710    ///
711    /// assert_eq!("rs", Utf8TypedPathBuf::from("foo.rs").extension().unwrap());
712    /// assert_eq!("gz", Utf8TypedPathBuf::from("foo.tar.gz").extension().unwrap());
713    /// ```
714    pub fn extension(&self) -> Option<&str> {
715        impl_typed_fn!(self, extension)
716    }
717
718    /// Returns an owned [`Utf8TypedPathBuf`] by resolving `..` and `.` segments.
719    ///
720    /// When multiple, sequential path segment separation characters are found (e.g. `/` for Unix
721    /// and either `\` or `/` on Windows), they are replaced by a single instance of the
722    /// platform-specific path segment separator (`/` on Unix and `\` on Windows).
723    ///
724    /// # Examples
725    ///
726    /// ```
727    /// use typed_path::Utf8TypedPathBuf;
728    ///
729    /// assert_eq!(
730    ///     Utf8TypedPathBuf::from("foo/bar//baz/./asdf/quux/..").normalize(),
731    ///     Utf8TypedPathBuf::from("foo/bar/baz/asdf"),
732    /// );
733    /// ```
734    ///
735    /// When starting with a root directory, any `..` segment whose parent is the root directory
736    /// will be filtered out:
737    ///
738    /// ```
739    /// use typed_path::Utf8TypedPathBuf;
740    ///
741    /// assert_eq!(
742    ///     Utf8TypedPathBuf::from("/../foo").normalize(),
743    ///     Utf8TypedPathBuf::from("/foo"),
744    /// );
745    /// ```
746    ///
747    /// If any `..` is left unresolved as the path is relative and no parent is found, it is
748    /// discarded:
749    ///
750    /// ```
751    /// use typed_path::Utf8TypedPathBuf;
752    ///
753    /// assert_eq!(
754    ///     Utf8TypedPathBuf::from("../foo/..").normalize(),
755    ///     Utf8TypedPathBuf::from(""),
756    /// );
757    ///
758    /// // Windows prefixes also count this way, but the prefix remains
759    /// assert_eq!(
760    ///     Utf8TypedPathBuf::from(r"C:..\foo\..").normalize(),
761    ///     Utf8TypedPathBuf::from(r"C:"),
762    /// );
763    /// ```
764    pub fn normalize(&self) -> Utf8TypedPathBuf {
765        self.to_path().normalize()
766    }
767
768    /// Converts a path to an absolute form by [`normalizing`] the path, returning a
769    /// [`Utf8TypedPathBuf`].
770    ///
771    /// In the case that the path is relative, the current working directory is prepended prior to
772    /// normalizing.
773    ///
774    /// [`normalizing`]: Utf8TypedPathBuf::normalize
775    ///
776    /// # Examples
777    ///
778    /// ```
779    /// use typed_path::{utils, Utf8TypedPathBuf, Utf8UnixEncoding};
780    ///
781    /// // With an absolute path, it is just normalized
782    /// let path = Utf8TypedPathBuf::from("/a/b/../c/./d");
783    /// assert_eq!(path.absolutize().unwrap(), Utf8TypedPathBuf::from("/a/c/d"));
784    ///
785    /// // With a relative path, it is first joined with the current working directory
786    /// // and then normalized
787    /// let cwd = utils::utf8_current_dir().unwrap()
788    ///     .with_encoding::<Utf8UnixEncoding>().to_typed_path_buf();
789    ///
790    /// let path = cwd.join("a/b/../c/./d");
791    /// assert_eq!(path.absolutize().unwrap(), cwd.join("a/c/d"));
792    /// ```
793    #[cfg(all(feature = "std", not(target_family = "wasm")))]
794    pub fn absolutize(&self) -> std::io::Result<Utf8TypedPathBuf> {
795        self.to_path().absolutize()
796    }
797
798    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`.
799    ///
800    /// See [`Utf8TypedPathBuf::push`] for more details on what it means to adjoin a path.
801    ///
802    /// # Difference from Path
803    ///
804    /// Unlike [`Utf8Path::join`], this implementation only supports types that implement
805    /// `AsRef<str>` instead of `AsRef<Path>`.
806    ///
807    /// [`Utf8Path::join`]: crate::Utf8Path::join
808    ///
809    /// # Examples
810    ///
811    /// ```
812    /// use typed_path::Utf8TypedPathBuf;
813    ///
814    /// assert_eq!(
815    ///     Utf8TypedPathBuf::from("/etc").join("passwd"),
816    ///     Utf8TypedPathBuf::from("/etc/passwd"),
817    /// );
818    /// ```
819    pub fn join(&self, path: impl AsRef<str>) -> Utf8TypedPathBuf {
820        self.to_path().join(path)
821    }
822
823    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`, checking the `path`
824    /// to ensure it is safe to join. _When dealing with user-provided paths, this is the preferred
825    /// method._
826    ///
827    /// See [`Utf8TypedPathBuf::push_checked`] for more details on what it means to adjoin a path
828    /// safely.
829    ///
830    /// # Difference from Path
831    ///
832    /// Unlike [`Utf8Path::join_checked`], this implementation only supports types that implement
833    /// `AsRef<str>` instead of `AsRef<Path>`.
834    ///
835    /// [`Utf8Path::join_checked`]: crate::Utf8Path::join_checked
836    ///
837    /// # Examples
838    ///
839    /// ```
840    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
841    ///
842    /// // Valid path will join successfully
843    /// assert_eq!(
844    ///     Utf8TypedPathBuf::from("/etc").join_checked("passwd"),
845    ///     Ok(Utf8TypedPathBuf::from("/etc/passwd")),
846    /// );
847    ///
848    /// // Invalid path will fail to join
849    /// assert_eq!(
850    ///     Utf8TypedPathBuf::from("/etc").join_checked("/sneaky/path"),
851    ///     Err(CheckedPathError::UnexpectedRoot),
852    /// );
853    /// ```
854    pub fn join_checked(
855        &self,
856        path: impl AsRef<str>,
857    ) -> Result<Utf8TypedPathBuf, CheckedPathError> {
858        self.to_path().join_checked(path)
859    }
860
861    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given file name.
862    ///
863    /// See [`Utf8TypedPathBuf::set_file_name`] for more details.
864    ///
865    /// # Examples
866    ///
867    /// ```
868    /// use typed_path::Utf8TypedPathBuf;
869    ///
870    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
871    /// assert_eq!(path.with_file_name("bar.txt"), Utf8TypedPathBuf::from("/tmp/bar.txt"));
872    ///
873    /// let path = Utf8TypedPathBuf::from("/tmp");
874    /// assert_eq!(path.with_file_name("var"), Utf8TypedPathBuf::from("/var"));
875    /// ```
876    pub fn with_file_name<S: AsRef<str>>(&self, file_name: S) -> Utf8TypedPathBuf {
877        self.to_path().with_file_name(file_name)
878    }
879
880    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given extension.
881    ///
882    /// See [`Utf8TypedPathBuf::set_extension`] for more details.
883    ///
884    /// # Examples
885    ///
886    /// ```
887    /// use typed_path::Utf8TypedPathBuf;
888    ///
889    /// let path = Utf8TypedPathBuf::from("foo.rs");
890    /// assert_eq!(path.with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
891    ///
892    /// let path = Utf8TypedPathBuf::from("foo.tar.gz");
893    /// assert_eq!(path.with_extension(""), Utf8TypedPathBuf::from("foo.tar"));
894    /// assert_eq!(path.with_extension("xz"), Utf8TypedPathBuf::from("foo.tar.xz"));
895    /// assert_eq!(path.with_extension("").with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
896    /// ```
897    pub fn with_extension<S: AsRef<str>>(&self, extension: S) -> Utf8TypedPathBuf {
898        self.to_path().with_extension(extension)
899    }
900
901    /// Produces an iterator over the [`Utf8TypedComponent`]s of the path.
902    ///
903    /// When parsing the path, there is a small amount of normalization:
904    ///
905    /// * Repeated separators are ignored, so `a/b` and `a//b` both have
906    ///   `a` and `b` as components.
907    ///
908    /// * Occurrences of `.` are normalized away, except if they are at the
909    ///   beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and
910    ///   `a/b` all have `a` and `b` as components, but `./a/b` starts with
911    ///   an additional CurDir component.
912    ///
913    /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
914    ///
915    /// Note that no other normalization takes place; in particular, `a/c`
916    /// and `a/b/../c` are distinct, to account for the possibility that `b`
917    /// is a symbolic link (so its parent isn't `a`).
918    ///
919    /// # Examples
920    ///
921    /// ```
922    /// use typed_path::{Utf8TypedPathBuf, Utf8TypedComponent};
923    ///
924    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
925    /// let mut components = path.components();
926    ///
927    /// assert!(components.next().unwrap().is_root());
928    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("tmp"));
929    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("foo.txt"));
930    /// assert_eq!(components.next(), None)
931    /// ```
932    ///
933    /// [`Utf8TypedComponent`]: crate::Utf8TypedComponent
934    pub fn components(&self) -> Utf8TypedComponents<'_> {
935        self.to_path().components()
936    }
937
938    /// Produces an iterator over the path's components viewed as [`str`] slices.
939    ///
940    /// For more information about the particulars of how the path is separated
941    /// into components, see [`components`].
942    ///
943    /// [`components`]: Utf8TypedPath::components
944    ///
945    /// # Examples
946    ///
947    /// ```
948    /// use typed_path::Utf8TypedPathBuf;
949    ///
950    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
951    /// let mut it = path.iter();
952    ///
953    /// assert_eq!(it.next(), Some(typed_path::constants::unix::SEPARATOR_STR));
954    /// assert_eq!(it.next(), Some("tmp"));
955    /// assert_eq!(it.next(), Some("foo.txt"));
956    /// assert_eq!(it.next(), None)
957    /// ```
958    #[inline]
959    pub fn iter(&self) -> Utf8TypedIter<'_> {
960        self.to_path().iter()
961    }
962}
963
964impl fmt::Display for Utf8TypedPathBuf {
965    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
966        match self {
967            Self::Unix(path) => fmt::Display::fmt(path, f),
968            Self::Windows(path) => fmt::Display::fmt(path, f),
969        }
970    }
971}
972
973impl AsRef<[u8]> for Utf8TypedPathBuf {
974    #[inline]
975    fn as_ref(&self) -> &[u8] {
976        self.as_str().as_bytes()
977    }
978}
979
980impl AsRef<str> for Utf8TypedPathBuf {
981    #[inline]
982    fn as_ref(&self) -> &str {
983        self.as_str()
984    }
985}
986
987impl<'a> From<&'a str> for Utf8TypedPathBuf {
988    /// Creates a new typed pathbuf from a byte slice by determining if the path represents a
989    /// Windows or Unix path. This is accomplished by first trying to parse as a Windows path. If
990    /// the resulting path contains a prefix such as `C:` or begins with a `\`, it is assumed to be
991    /// a [`Utf8WindowsPathBuf`]; otherwise, the slice will be represented as a [`Utf8UnixPathBuf`].
992    ///
993    /// # Examples
994    ///
995    /// ```
996    /// use typed_path::Utf8TypedPathBuf;
997    ///
998    /// assert!(Utf8TypedPathBuf::from(r#"C:\some\path\to\file.txt"#).is_windows());
999    /// assert!(Utf8TypedPathBuf::from(r#"\some\path\to\file.txt"#).is_windows());
1000    /// assert!(Utf8TypedPathBuf::from(r#"/some/path/to/file.txt"#).is_unix());
1001    ///
1002    /// // NOTE: If we don't start with a backslash, it's too difficult to
1003    /// //       determine and we therefore just assume a Unix/POSIX path.
1004    /// assert!(Utf8TypedPathBuf::from(r#"some\path\to\file.txt"#).is_unix());
1005    /// assert!(Utf8TypedPathBuf::from("file.txt").is_unix());
1006    /// assert!(Utf8TypedPathBuf::from("").is_unix());
1007    /// ```
1008    #[inline]
1009    fn from(s: &'a str) -> Self {
1010        Utf8TypedPath::derive(s).to_path_buf()
1011    }
1012}
1013
1014impl From<String> for Utf8TypedPathBuf {
1015    #[inline]
1016    fn from(s: String) -> Self {
1017        // NOTE: We use the typed path to check the underlying format, and then
1018        //       create it manually to avoid a clone of the vec itself
1019        match Utf8TypedPath::derive(s.as_str()) {
1020            Utf8TypedPath::Unix(_) => Utf8TypedPathBuf::Unix(Utf8UnixPathBuf::from(s)),
1021            Utf8TypedPath::Windows(_) => Utf8TypedPathBuf::Windows(Utf8WindowsPathBuf::from(s)),
1022        }
1023    }
1024}
1025
1026impl TryFrom<Utf8TypedPathBuf> for Utf8UnixPathBuf {
1027    type Error = Utf8TypedPathBuf;
1028
1029    fn try_from(path: Utf8TypedPathBuf) -> Result<Self, Self::Error> {
1030        match path {
1031            Utf8TypedPathBuf::Unix(path) => Ok(path),
1032            path => Err(path),
1033        }
1034    }
1035}
1036
1037impl TryFrom<Utf8TypedPathBuf> for Utf8WindowsPathBuf {
1038    type Error = Utf8TypedPathBuf;
1039
1040    fn try_from(path: Utf8TypedPathBuf) -> Result<Self, Self::Error> {
1041        match path {
1042            Utf8TypedPathBuf::Windows(path) => Ok(path),
1043            path => Err(path),
1044        }
1045    }
1046}
1047
1048impl PartialEq<Utf8TypedPath<'_>> for Utf8TypedPathBuf {
1049    fn eq(&self, path: &Utf8TypedPath<'_>) -> bool {
1050        path.eq(&self.to_path())
1051    }
1052}
1053
1054impl PartialEq<str> for Utf8TypedPathBuf {
1055    fn eq(&self, path: &str) -> bool {
1056        self.as_str() == path
1057    }
1058}
1059
1060impl PartialEq<Utf8TypedPathBuf> for str {
1061    fn eq(&self, path: &Utf8TypedPathBuf) -> bool {
1062        self == path.as_str()
1063    }
1064}
1065
1066impl<'a> PartialEq<&'a str> for Utf8TypedPathBuf {
1067    fn eq(&self, path: &&'a str) -> bool {
1068        self.as_str() == *path
1069    }
1070}
1071
1072impl PartialEq<Utf8TypedPathBuf> for &str {
1073    fn eq(&self, path: &Utf8TypedPathBuf) -> bool {
1074        *self == path.as_str()
1075    }
1076}