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}