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}