typed_path/typed/utf8/
path.rs

1use core::fmt;
2
3use crate::common::{CheckedPathError, StripPrefixError, TryAsRef};
4use crate::typed::{
5    PathType, Utf8TypedAncestors, Utf8TypedComponents, Utf8TypedIter, Utf8TypedPathBuf,
6};
7use crate::unix::Utf8UnixPath;
8use crate::windows::Utf8WindowsPath;
9
10/// Represents a path with a known type that can be one of:
11///
12/// * [`Utf8UnixPath`]
13/// * [`Utf8WindowsPath`]
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub enum Utf8TypedPath<'a> {
16    Unix(&'a Utf8UnixPath),
17    Windows(&'a Utf8WindowsPath),
18}
19
20impl<'a> Utf8TypedPath<'a> {
21    /// Creates a new path with the given type as its encoding.
22    pub fn new<S: AsRef<str> + ?Sized>(s: &'a S, r#type: PathType) -> Self {
23        match r#type {
24            PathType::Unix => Self::unix(s),
25            PathType::Windows => Self::windows(s),
26        }
27    }
28
29    /// Creates a new typed Unix path.
30    #[inline]
31    pub fn unix<S: AsRef<str> + ?Sized>(s: &'a S) -> Self {
32        Self::Unix(Utf8UnixPath::new(s))
33    }
34
35    /// Creates a new typed Windows path.
36    #[inline]
37    pub fn windows<S: AsRef<str> + ?Sized>(s: &'a S) -> Self {
38        Self::Windows(Utf8WindowsPath::new(s))
39    }
40
41    /// Creates a new typed path from a byte slice by determining if the path represents a Windows
42    /// or Unix path. This is accomplished by first trying to parse as a Windows path. If the
43    /// resulting path contains a prefix such as `C:` or begins with a `\`, it is assumed to be a
44    /// [`Utf8WindowsPath`]; otherwise, the slice will be represented as a [`Utf8UnixPath`].
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use typed_path::Utf8TypedPath;
50    ///
51    /// assert!(Utf8TypedPath::derive(r#"C:\some\path\to\file.txt"#).is_windows());
52    /// assert!(Utf8TypedPath::derive(r#"\some\path\to\file.txt"#).is_windows());
53    /// assert!(Utf8TypedPath::derive(r#"/some/path/to/file.txt"#).is_unix());
54    ///
55    /// // NOTE: If we don't start with a backslash, it's too difficult to
56    /// //       determine and we therefore just assume a Unix/POSIX path.
57    /// assert!(Utf8TypedPath::derive(r#"some\path\to\file.txt"#).is_unix());
58    /// assert!(Utf8TypedPath::derive("file.txt").is_unix());
59    /// assert!(Utf8TypedPath::derive("").is_unix());
60    /// ```
61    pub fn derive<S: AsRef<str> + ?Sized>(s: &'a S) -> Self {
62        let winpath = Utf8WindowsPath::new(s);
63        if s.as_ref().starts_with('\\') || winpath.components().has_prefix() {
64            Self::Windows(winpath)
65        } else {
66            Self::Unix(Utf8UnixPath::new(s))
67        }
68    }
69
70    /// Yields the underlying [`str`] slice.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use typed_path::Utf8TypedPath;
76    ///
77    /// let string = Utf8TypedPath::derive("foo.txt").as_str().to_string();
78    /// assert_eq!(string, "foo.txt");
79    /// ```
80    pub fn as_str(&self) -> &str {
81        impl_typed_fn!(self, as_str)
82    }
83
84    /// Converts a [`Utf8TypedPath`] into a [`Utf8TypedPathBuf`].
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
90    ///
91    /// let path_buf = Utf8TypedPath::derive("foo.txt").to_path_buf();
92    /// assert_eq!(path_buf, Utf8TypedPathBuf::from("foo.txt"));
93    /// ```
94    pub fn to_path_buf(&self) -> Utf8TypedPathBuf {
95        match self {
96            Self::Unix(path) => Utf8TypedPathBuf::Unix(path.to_path_buf()),
97            Self::Windows(path) => Utf8TypedPathBuf::Windows(path.to_path_buf()),
98        }
99    }
100
101    /// Returns `true` if the [`Utf8TypedPath`] is absolute, i.e., if it is independent of
102    /// the current directory.
103    ///
104    /// * On Unix ([`UnixPath`]]), a path is absolute if it starts with the root, so
105    ///   `is_absolute` and [`has_root`] are equivalent.
106    ///
107    /// * On Windows ([`WindowsPath`]), a path is absolute if it has a prefix and starts with the
108    ///   root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
109    ///
110    /// [`UnixPath`]: crate::UnixPath
111    /// [`WindowsPath`]: crate::WindowsPath
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use typed_path::Utf8TypedPath;
117    ///
118    /// assert!(!Utf8TypedPath::derive("foo.txt").is_absolute());
119    /// ```
120    ///
121    /// [`has_root`]: Utf8TypedPath::has_root
122    pub fn is_absolute(&self) -> bool {
123        impl_typed_fn!(self, is_absolute)
124    }
125
126    /// Returns `true` if the [`Utf8TypedPath`] is relative, i.e., not absolute.
127    ///
128    /// See [`is_absolute`]'s documentation for more details.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use typed_path::Utf8TypedPath;
134    ///
135    /// assert!(Utf8TypedPath::derive("foo.txt").is_relative());
136    /// ```
137    ///
138    /// [`is_absolute`]: Utf8TypedPath::is_absolute
139    #[inline]
140    pub fn is_relative(&self) -> bool {
141        impl_typed_fn!(self, is_relative)
142    }
143
144    /// Returns `true` if the [`Utf8TypedPath`] has a root.
145    ///
146    /// * On Unix ([`Utf8UnixPath`]), a path has a root if it begins with `/`.
147    ///
148    /// * On Windows ([`Utf8WindowsPath`]), a path has a root if it:
149    ///     * has no prefix and begins with a separator, e.g., `\windows`
150    ///     * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows`
151    ///     * has any non-disk prefix, e.g., `\\server\share`
152    ///
153    /// [`Utf8UnixPath`]: crate::Utf8UnixPath
154    /// [`Utf8WindowsPath`]: crate::Utf8WindowsPath
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use typed_path::Utf8TypedPath;
160    ///
161    /// assert!(Utf8TypedPath::derive("/etc/passwd").has_root());
162    /// ```
163    #[inline]
164    pub fn has_root(&self) -> bool {
165        impl_typed_fn!(self, has_root)
166    }
167
168    /// Returns the [`Utf8TypedPath`] without its final component, if there is one.
169    ///
170    /// Returns [`None`] if the path terminates in a root or prefix.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use typed_path::Utf8TypedPath;
176    ///
177    /// let path = Utf8TypedPath::derive("/foo/bar");
178    /// let parent = path.parent().unwrap();
179    /// assert_eq!(parent, Utf8TypedPath::derive("/foo"));
180    ///
181    /// let grand_parent = parent.parent().unwrap();
182    /// assert_eq!(grand_parent, Utf8TypedPath::derive("/"));
183    /// assert_eq!(grand_parent.parent(), None);
184    /// ```
185    pub fn parent(&self) -> Option<Self> {
186        match self {
187            Self::Unix(path) => path.parent().map(Self::Unix),
188            Self::Windows(path) => path.parent().map(Self::Windows),
189        }
190    }
191
192    /// Produces an iterator over [`Utf8TypedPath`] and its ancestors.
193    ///
194    /// The iterator will yield the [`Utf8TypedPath`] that is returned if the [`parent`] method is used
195    /// zero or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`,
196    /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns
197    /// [`None`], the iterator will do likewise. The iterator will always yield at least one value,
198    /// namely `&self`.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use typed_path::Utf8TypedPath;
204    ///
205    /// let mut ancestors = Utf8TypedPath::derive("/foo/bar").ancestors();
206    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo/bar")));
207    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo")));
208    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/")));
209    /// assert_eq!(ancestors.next(), None);
210    ///
211    /// let mut ancestors = Utf8TypedPath::derive("../foo/bar").ancestors();
212    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo/bar")));
213    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo")));
214    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("..")));
215    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("")));
216    /// assert_eq!(ancestors.next(), None);
217    /// ```
218    ///
219    /// [`parent`]: Utf8TypedPath::parent
220    #[inline]
221    pub fn ancestors(&self) -> Utf8TypedAncestors<'a> {
222        match self {
223            Self::Unix(p) => Utf8TypedAncestors::Unix(p.ancestors()),
224            Self::Windows(p) => Utf8TypedAncestors::Windows(p.ancestors()),
225        }
226    }
227
228    /// Returns the final component of the [`Utf8TypedPath`], if there is one.
229    ///
230    /// If the path is a normal file, this is the file name. If it's the path of a directory, this
231    /// is the directory name.
232    ///
233    /// Returns [`None`] if the path terminates in `..`.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use typed_path::Utf8TypedPath;
239    ///
240    /// assert_eq!(Some("bin"), Utf8TypedPath::derive("/usr/bin/").file_name());
241    /// assert_eq!(Some("foo.txt"), Utf8TypedPath::derive("tmp/foo.txt").file_name());
242    /// assert_eq!(Some("foo.txt"), Utf8TypedPath::derive("foo.txt/.").file_name());
243    /// assert_eq!(Some("foo.txt"), Utf8TypedPath::derive("foo.txt/.//").file_name());
244    /// assert_eq!(None, Utf8TypedPath::derive("foo.txt/..").file_name());
245    /// assert_eq!(None, Utf8TypedPath::derive("/").file_name());
246    /// ```
247    pub fn file_name(&self) -> Option<&str> {
248        impl_typed_fn!(self, file_name)
249    }
250
251    /// Returns a path that, when joined onto `base`, yields `self`.
252    ///
253    /// # Difference from Path
254    ///
255    /// Unlike [`Path::strip_prefix`], this implementation only supports types that implement
256    /// `AsRef<str>` instead of `AsRef<Path>`.
257    ///
258    /// [`Path::strip_prefix`]: crate::Path::strip_prefix
259    ///
260    /// # Errors
261    ///
262    /// If `base` is not a prefix of `self` (i.e., [`starts_with`]
263    /// returns `false`), returns [`Err`].
264    ///
265    /// [`starts_with`]: Utf8TypedPath::starts_with
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
271    ///
272    /// let path = Utf8TypedPath::derive("/test/haha/foo.txt");
273    ///
274    /// assert_eq!(path.strip_prefix("/"), Ok(Utf8TypedPath::derive("test/haha/foo.txt")));
275    /// assert_eq!(path.strip_prefix("/test"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
276    /// assert_eq!(path.strip_prefix("/test/"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
277    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Utf8TypedPath::derive("")));
278    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Utf8TypedPath::derive("")));
279    ///
280    /// assert!(path.strip_prefix("test").is_err());
281    /// assert!(path.strip_prefix("/haha").is_err());
282    ///
283    /// let prefix = Utf8TypedPathBuf::from("/test/");
284    /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8TypedPath::derive("haha/foo.txt")));
285    /// ```
286    pub fn strip_prefix(
287        &self,
288        base: impl AsRef<str>,
289    ) -> Result<Utf8TypedPath<'_>, StripPrefixError> {
290        match self {
291            Self::Unix(p) => p
292                .strip_prefix(Utf8UnixPath::new(&base))
293                .map(Utf8TypedPath::Unix),
294            Self::Windows(p) => p
295                .strip_prefix(Utf8WindowsPath::new(&base))
296                .map(Utf8TypedPath::Windows),
297        }
298    }
299
300    /// Determines whether `base` is a prefix of `self`.
301    ///
302    /// Only considers whole path components to match.
303    ///
304    /// # Difference from Path
305    ///
306    /// Unlike [`Path::starts_with`], this implementation only supports types that implement
307    /// `AsRef<str>` instead of `AsRef<Path>`.
308    ///
309    /// [`Path::starts_with`]: crate::Path::starts_with
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use typed_path::Utf8TypedPath;
315    ///
316    /// let path = Utf8TypedPath::derive("/etc/passwd");
317    ///
318    /// assert!(path.starts_with("/etc"));
319    /// assert!(path.starts_with("/etc/"));
320    /// assert!(path.starts_with("/etc/passwd"));
321    /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay
322    /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay
323    ///
324    /// assert!(!path.starts_with("/e"));
325    /// assert!(!path.starts_with("/etc/passwd.txt"));
326    ///
327    /// assert!(!Utf8TypedPath::derive("/etc/foo.rs").starts_with("/etc/foo"));
328    /// ```
329    pub fn starts_with(&self, base: impl AsRef<str>) -> bool {
330        match self {
331            Self::Unix(p) => p.starts_with(Utf8UnixPath::new(&base)),
332            Self::Windows(p) => p.starts_with(Utf8WindowsPath::new(&base)),
333        }
334    }
335
336    /// Determines whether `child` is a suffix of `self`.
337    ///
338    /// Only considers whole path components to match.
339    ///
340    /// # Difference from Path
341    ///
342    /// Unlike [`Path::ends_with`], this implementation only supports types that implement
343    /// `AsRef<str>` instead of `AsRef<Path>`.
344    ///
345    /// [`Path::ends_with`]: crate::Path::ends_with
346    ///
347    /// # Examples
348    ///
349    /// ```
350    /// use typed_path::Utf8TypedPath;
351    ///
352    /// let path = Utf8TypedPath::derive("/etc/resolv.conf");
353    ///
354    /// assert!(path.ends_with("resolv.conf"));
355    /// assert!(path.ends_with("etc/resolv.conf"));
356    /// assert!(path.ends_with("/etc/resolv.conf"));
357    ///
358    /// assert!(!path.ends_with("/resolv.conf"));
359    /// assert!(!path.ends_with("conf")); // use .extension() instead
360    /// ```
361    pub fn ends_with(&self, child: impl AsRef<str>) -> bool {
362        match self {
363            Self::Unix(p) => p.ends_with(Utf8UnixPath::new(&child)),
364            Self::Windows(p) => p.ends_with(Utf8WindowsPath::new(&child)),
365        }
366    }
367
368    /// Extracts the stem (non-extension) portion of [`self.file_name`].
369    ///
370    /// [`self.file_name`]: Utf8TypedPath::file_name
371    ///
372    /// The stem is:
373    ///
374    /// * [`None`], if there is no file name;
375    /// * The entire file name if there is no embedded `.`;
376    /// * The entire file name if the file name begins with `.` and has no other `.`s within;
377    /// * Otherwise, the portion of the file name before the final `.`
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use typed_path::Utf8TypedPath;
383    ///
384    /// assert_eq!("foo", Utf8TypedPath::derive("foo.rs").file_stem().unwrap());
385    /// assert_eq!("foo.tar", Utf8TypedPath::derive("foo.tar.gz").file_stem().unwrap());
386    /// ```
387    ///
388    pub fn file_stem(&self) -> Option<&str> {
389        impl_typed_fn!(self, file_stem)
390    }
391
392    /// Extracts the extension of [`self.file_name`], if possible.
393    ///
394    /// The extension is:
395    ///
396    /// * [`None`], if there is no file name;
397    /// * [`None`], if there is no embedded `.`;
398    /// * [`None`], if the file name begins with `.` and has no other `.`s within;
399    /// * Otherwise, the portion of the file name after the final `.`
400    ///
401    /// [`self.file_name`]: Utf8TypedPath::file_name
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// use typed_path::Utf8TypedPath;
407    ///
408    /// assert_eq!("rs", Utf8TypedPath::derive("foo.rs").extension().unwrap());
409    /// assert_eq!("gz", Utf8TypedPath::derive("foo.tar.gz").extension().unwrap());
410    /// ```
411    pub fn extension(&self) -> Option<&str> {
412        impl_typed_fn!(self, extension)
413    }
414
415    /// Returns an owned [`Utf8TypedPathBuf`] by resolving `..` and `.` segments.
416    ///
417    /// When multiple, sequential path segment separation characters are found (e.g. `/` for Unix
418    /// and either `\` or `/` on Windows), they are replaced by a single instance of the
419    /// platform-specific path segment separator (`/` on Unix and `\` on Windows).
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
425    ///
426    /// assert_eq!(
427    ///     Utf8TypedPath::derive("foo/bar//baz/./asdf/quux/..").normalize(),
428    ///     Utf8TypedPathBuf::from("foo/bar/baz/asdf"),
429    /// );
430    /// ```
431    ///
432    /// When starting with a root directory, any `..` segment whose parent is the root directory
433    /// will be filtered out:
434    ///
435    /// ```
436    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
437    ///
438    /// // NOTE: A path cannot be created on its own without a defined encoding
439    /// assert_eq!(
440    ///     Utf8TypedPath::derive("/../foo").normalize(),
441    ///     Utf8TypedPathBuf::from("/foo"),
442    /// );
443    /// ```
444    ///
445    /// If any `..` is left unresolved as the path is relative and no parent is found, it is
446    /// discarded:
447    ///
448    /// ```
449    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
450    ///
451    /// assert_eq!(
452    ///     Utf8TypedPath::derive("../foo/..").normalize(),
453    ///     Utf8TypedPathBuf::from(""),
454    /// );
455    ///
456    /// // Windows prefixes also count this way, but the prefix remains
457    /// assert_eq!(
458    ///     Utf8TypedPath::derive(r"C:..\foo\..").normalize(),
459    ///     Utf8TypedPathBuf::from(r"C:"),
460    /// );
461    /// ```
462    pub fn normalize(&self) -> Utf8TypedPathBuf {
463        match self {
464            Self::Unix(path) => Utf8TypedPathBuf::Unix(path.normalize()),
465            Self::Windows(path) => Utf8TypedPathBuf::Windows(path.normalize()),
466        }
467    }
468
469    /// Converts a path to an absolute form by [`normalizing`] the path, returning a
470    /// [`Utf8TypedPathBuf`].
471    ///
472    /// In the case that the path is relative, the current working directory is prepended prior to
473    /// normalizing.
474    ///
475    /// [`normalizing`]: Utf8TypedPath::normalize
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// use typed_path::{utils, Utf8TypedPath};
481    ///
482    /// // With an absolute path, it is just normalized
483    /// let path = Utf8TypedPath::derive("/a/b/../c/./d");
484    /// assert_eq!(path.absolutize().unwrap(), Utf8TypedPath::derive("/a/c/d"));
485    ///
486    /// // With a relative path, it is first joined with the current working directory
487    /// // and then normalized
488    /// let cwd = utils::current_dir().unwrap().with_unix_encoding().to_typed_path_buf();
489    /// let path = cwd.join("a/b/../c/./d");
490    /// assert_eq!(path.absolutize().unwrap(), cwd.join("a/c/d"));
491    /// ```
492    #[cfg(all(feature = "std", not(target_family = "wasm")))]
493    pub fn absolutize(&self) -> std::io::Result<Utf8TypedPathBuf> {
494        Ok(match self {
495            Self::Unix(path) => Utf8TypedPathBuf::Unix(path.absolutize()?),
496            Self::Windows(path) => Utf8TypedPathBuf::Windows(path.absolutize()?),
497        })
498    }
499
500    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`.
501    ///
502    /// See [`Utf8TypedPathBuf::push`] for more details on what it means to adjoin a path.
503    ///
504    /// # Difference from Path
505    ///
506    /// Unlike [`Utf8Path::join`], this implementation only supports types that implement
507    /// `AsRef<str>` instead of `AsRef<Path>`.
508    ///
509    /// [`Utf8Path::join`]: crate::Utf8Path::join
510    ///
511    /// # Examples
512    ///
513    /// ```
514    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
515    ///
516    /// assert_eq!(
517    ///     Utf8TypedPath::derive("/etc").join("passwd"),
518    ///     Utf8TypedPathBuf::from("/etc/passwd"),
519    /// );
520    /// ```
521    pub fn join(&self, path: impl AsRef<str>) -> Utf8TypedPathBuf {
522        match self {
523            Self::Unix(p) => Utf8TypedPathBuf::Unix(p.join(Utf8UnixPath::new(&path))),
524            Self::Windows(p) => Utf8TypedPathBuf::Windows(p.join(Utf8WindowsPath::new(&path))),
525        }
526    }
527
528    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`, checking the `path` to
529    /// ensure it is safe to join. _When dealing with user-provided paths, this is the preferred
530    /// method._
531    ///
532    /// See [`Utf8TypedPathBuf::push_checked`] for more details on what it means to adjoin a path
533    /// safely.
534    ///
535    /// # Difference from Path
536    ///
537    /// Unlike [`Utf8Path::join_checked`], this implementation only supports types that implement
538    /// `AsRef<str>` instead of `AsRef<Path>`.
539    ///
540    /// [`Utf8Path::join_checked`]: crate::Utf8Path::join_checked
541    ///
542    /// # Examples
543    ///
544    /// ```
545    /// use typed_path::{CheckedPathError, Utf8TypedPath, Utf8TypedPathBuf};
546    ///
547    /// assert_eq!(
548    ///     Utf8TypedPath::derive("/etc").join_checked("passwd"),
549    ///     Ok(Utf8TypedPathBuf::from("/etc/passwd")),
550    /// );
551    ///
552    /// assert_eq!(
553    ///     Utf8TypedPath::derive("/etc").join_checked("/sneaky/path"),
554    ///     Err(CheckedPathError::UnexpectedRoot),
555    /// );
556    /// ```
557    pub fn join_checked(
558        &self,
559        path: impl AsRef<str>,
560    ) -> Result<Utf8TypedPathBuf, CheckedPathError> {
561        Ok(match self {
562            Self::Unix(p) => Utf8TypedPathBuf::Unix(p.join_checked(Utf8UnixPath::new(&path))?),
563            Self::Windows(p) => {
564                Utf8TypedPathBuf::Windows(p.join_checked(Utf8WindowsPath::new(&path))?)
565            }
566        })
567    }
568
569    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given file name.
570    ///
571    /// See [`Utf8TypedPathBuf::set_file_name`] for more details.
572    ///
573    /// # Examples
574    ///
575    /// ```
576    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
577    ///
578    /// let path = Utf8TypedPath::derive("/tmp/foo.txt");
579    /// assert_eq!(path.with_file_name("bar.txt"), Utf8TypedPathBuf::from("/tmp/bar.txt"));
580    ///
581    /// let path = Utf8TypedPath::derive("/tmp");
582    /// assert_eq!(path.with_file_name("var"), Utf8TypedPathBuf::from("/var"));
583    /// ```
584    pub fn with_file_name<S: AsRef<str>>(&self, file_name: S) -> Utf8TypedPathBuf {
585        match self {
586            Self::Unix(path) => Utf8TypedPathBuf::Unix(path.with_file_name(file_name)),
587            Self::Windows(path) => Utf8TypedPathBuf::Windows(path.with_file_name(file_name)),
588        }
589    }
590
591    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given extension.
592    ///
593    /// See [`Utf8TypedPathBuf::set_extension`] for more details.
594    ///
595    /// # Examples
596    ///
597    /// ```
598    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
599    ///
600    /// let path = Utf8TypedPath::derive("foo.rs");
601    /// assert_eq!(path.with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
602    ///
603    /// let path = Utf8TypedPath::derive("foo.tar.gz");
604    /// assert_eq!(path.with_extension(""), Utf8TypedPathBuf::from("foo.tar"));
605    /// assert_eq!(path.with_extension("xz"), Utf8TypedPathBuf::from("foo.tar.xz"));
606    /// assert_eq!(path.with_extension("").with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
607    /// ```
608    pub fn with_extension<S: AsRef<str>>(&self, extension: S) -> Utf8TypedPathBuf {
609        match self {
610            Self::Unix(path) => Utf8TypedPathBuf::Unix(path.with_extension(extension)),
611            Self::Windows(path) => Utf8TypedPathBuf::Windows(path.with_extension(extension)),
612        }
613    }
614
615    /// Produces an iterator over the [`Utf8TypedComponent`]s of the path.
616    ///
617    /// When parsing the path, there is a small amount of normalization:
618    ///
619    /// * Repeated separators are ignored, so `a/b` and `a//b` both have
620    ///   `a` and `b` as components.
621    ///
622    /// * Occurrences of `.` are normalized away, except if they are at the
623    ///   beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and
624    ///   `a/b` all have `a` and `b` as components, but `./a/b` starts with
625    ///   an additional `CurDir` component.
626    ///
627    /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
628    ///
629    /// Note that no other normalization takes place; in particular, `a/c`
630    /// and `a/b/../c` are distinct, to account for the possibility that `b`
631    /// is a symbolic link (so its parent isn't `a`).
632    ///
633    /// # Examples
634    ///
635    /// ```
636    /// use typed_path::{Utf8TypedPath, Utf8TypedComponent};
637    ///
638    /// let mut components = Utf8TypedPath::derive("/tmp/foo.txt").components();
639    ///
640    /// assert!(components.next().unwrap().is_root());
641    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("tmp"));
642    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("foo.txt"));
643    /// assert_eq!(components.next(), None)
644    /// ```
645    ///
646    ///[`Utf8TypedComponent`]: crate::Utf8TypedComponent
647    pub fn components(&self) -> Utf8TypedComponents<'a> {
648        match self {
649            Self::Unix(p) => Utf8TypedComponents::Unix(p.components()),
650            Self::Windows(p) => Utf8TypedComponents::Windows(p.components()),
651        }
652    }
653
654    /// Produces an iterator over the path's components viewed as [`str`] slices.
655    ///
656    /// For more information about the particulars of how the path is separated
657    /// into components, see [`components`].
658    ///
659    /// [`components`]: Utf8TypedPath::components
660    ///
661    /// # Examples
662    ///
663    /// ```
664    /// use typed_path::Utf8TypedPath;
665    ///
666    /// let mut it = Utf8TypedPath::derive("/tmp/foo.txt").iter();
667    ///
668    /// assert_eq!(it.next(), Some(typed_path::constants::unix::SEPARATOR_STR));
669    /// assert_eq!(it.next(), Some("tmp"));
670    /// assert_eq!(it.next(), Some("foo.txt"));
671    /// assert_eq!(it.next(), None)
672    /// ```
673    #[inline]
674    pub fn iter(&self) -> Utf8TypedIter<'a> {
675        match self {
676            Self::Unix(p) => Utf8TypedIter::Unix(p.iter()),
677            Self::Windows(p) => Utf8TypedIter::Windows(p.iter()),
678        }
679    }
680
681    /// Returns true if this path represents a Unix path.
682    #[inline]
683    pub fn is_unix(&self) -> bool {
684        matches!(self, Self::Unix(_))
685    }
686
687    /// Returns true if this path represents a Windows path.
688    #[inline]
689    pub fn is_windows(&self) -> bool {
690        matches!(self, Self::Windows(_))
691    }
692
693    /// Converts this [`Utf8TypedPath`] into the Unix variant of [`Utf8TypedPathBuf`].
694    pub fn with_unix_encoding(&self) -> Utf8TypedPathBuf {
695        match self {
696            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding()),
697            _ => self.to_path_buf(),
698        }
699    }
700
701    /// Converts this [`Utf8TypedPath`] into the Unix variant of [`Utf8TypedPathBuf`], ensuring it
702    /// is a valid Unix path.
703    pub fn with_unix_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
704        Ok(match self {
705            Self::Unix(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
706            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
707        })
708    }
709
710    /// Converts this [`Utf8TypedPath`] into the Windows variant of [`Utf8TypedPathBuf`].
711    pub fn with_windows_encoding(&self) -> Utf8TypedPathBuf {
712        match self {
713            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding()),
714            _ => self.to_path_buf(),
715        }
716    }
717
718    /// Converts this [`Utf8TypedPath`] into the Windows variant of [`Utf8TypedPathBuf`], ensuring
719    /// it is a valid Windows path.
720    pub fn with_windows_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
721        Ok(match self {
722            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
723            Self::Windows(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
724        })
725    }
726}
727
728impl fmt::Display for Utf8TypedPath<'_> {
729    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
730        match self {
731            Self::Unix(path) => fmt::Display::fmt(path, f),
732            Self::Windows(path) => fmt::Display::fmt(path, f),
733        }
734    }
735}
736
737impl<'a> From<&'a str> for Utf8TypedPath<'a> {
738    #[inline]
739    fn from(s: &'a str) -> Self {
740        Utf8TypedPath::derive(s)
741    }
742}
743
744impl AsRef<str> for Utf8TypedPath<'_> {
745    #[inline]
746    fn as_ref(&self) -> &str {
747        self.as_str()
748    }
749}
750
751impl TryAsRef<Utf8UnixPath> for Utf8TypedPath<'_> {
752    fn try_as_ref(&self) -> Option<&Utf8UnixPath> {
753        match self {
754            Self::Unix(path) => Some(path),
755            _ => None,
756        }
757    }
758}
759
760impl TryAsRef<Utf8WindowsPath> for Utf8TypedPath<'_> {
761    fn try_as_ref(&self) -> Option<&Utf8WindowsPath> {
762        match self {
763            Self::Windows(path) => Some(path),
764            _ => None,
765        }
766    }
767}
768
769impl PartialEq<Utf8TypedPathBuf> for Utf8TypedPath<'_> {
770    fn eq(&self, path: &Utf8TypedPathBuf) -> bool {
771        self.eq(&path.to_path())
772    }
773}
774
775impl PartialEq<str> for Utf8TypedPath<'_> {
776    fn eq(&self, path: &str) -> bool {
777        self.as_str() == path
778    }
779}
780
781impl PartialEq<Utf8TypedPath<'_>> for str {
782    fn eq(&self, path: &Utf8TypedPath<'_>) -> bool {
783        self == path.as_str()
784    }
785}
786
787impl<'a> PartialEq<&'a str> for Utf8TypedPath<'_> {
788    fn eq(&self, path: &&'a str) -> bool {
789        self.as_str() == *path
790    }
791}
792
793impl PartialEq<Utf8TypedPath<'_>> for &str {
794    fn eq(&self, path: &Utf8TypedPath<'_>) -> bool {
795        *self == path.as_str()
796    }
797}