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}