poppable_path/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright Julian Ganz 2025
3//! This crate provides the [Poppable] trait for removing the last component of
4//! [Path]-like things. It is intended for use in situations where you don't
5//! know whether you'll have a [Path] or a [PathBuf] and don't want to do an
6//! additional allocation.
7//!
8//!     use poppable_path::Poppable;
9//!
10//!     trait PathGenerator<'a> {
11//!         /// Something path-like that may depend on the lifetime `'a`
12//!         type Path: Poppable + AsRef<std::path::Path>;
13//!
14//!         fn path(&'a self) -> Self::Path;
15//!
16//!         fn dir(&'a self) -> Option<Self::Path> {
17//!             let mut path = self.path();
18//!             if path.as_ref().is_dir() {
19//!                 Some(path)
20//!             } else if !path.pop() {
21//!                 None
22//!             } else if path.as_ref().is_dir() {
23//!                 Some(path)
24//!             } else {
25//!                 None
26//!             }
27//!         }
28//!     }
29use std::path::{Path, PathBuf};
30
31/// Something poppable [Path]-like
32///
33/// This trait provides the [pop](Poppable::pop) method, which does the same as
34/// [PathBuf::pop]. However, the trait is implemented for a number of different
35/// types that implement `AsRef<Path>`.
36pub trait Poppable {
37    /// Remove the last component from this path
38    ///
39    /// Returns [true] if a component was removed. Returns [false] and does not
40    /// mutate the path if `self.as_ref().parent()` would return [None].
41    ///
42    /// This fn is equivalent to what [PathBuf::pop] does.
43    fn pop(&mut self) -> bool;
44}
45
46impl Poppable for &Path {
47    fn pop(&mut self) -> bool {
48        self.parent().map(|p| *self = p).is_some()
49    }
50}
51
52impl Poppable for PathBuf {
53    fn pop(&mut self) -> bool {
54        self.pop()
55    }
56}
57
58impl Poppable for std::borrow::Cow<'_, Path> {
59    fn pop(&mut self) -> bool {
60        match self {
61            Self::Borrowed(p) => p.pop(),
62            Self::Owned(p) => p.pop(),
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn poppable_path() {
73        do_test(std::convert::identity)
74    }
75
76    #[test]
77    fn poppable_pathbuf() {
78        do_test::<PathBuf>(ToOwned::to_owned)
79    }
80
81    #[test]
82    fn poppable_cow_borrowed() {
83        do_test(std::borrow::Cow::Borrowed)
84    }
85
86    #[test]
87    fn poppable_cow_owned() {
88        do_test(|p| std::borrow::Cow::Owned(p.to_owned()))
89    }
90
91    fn do_test<'p, P>(make: impl Fn(&'static Path) -> P)
92    where
93        P: Poppable + PartialEq<&'p Path> + std::fmt::Debug,
94    {
95        let mut path = make(Path::new("foo/bar"));
96        assert!(path.pop());
97        assert_eq!(path, Path::new("foo"));
98        assert!(path.pop());
99        assert_eq!(path, Path::new(""));
100        assert!(!path.pop());
101        assert_eq!(path, Path::new(""));
102    }
103}