cd_manager/
lib.rs

1//! This is a simple crate designed to prevent you from forgetting to `pop` off of a `PathBuf` upon exiting scope
2//! (i.e. at the end of a loop, when returning from a function, etc). This makes it easy to have multiple
3//! return points, as your context will automatically be restored upon `Drop`.
4//!
5//! This crate is not perfectly safe at the moment (it is "safe" in the Rust sense, but you can trigger
6//! undesired behavior when used improperly), but the basic functionality works.
7//!
8//! For more documentation, see the main feature of this package, [`CdManager`].
9//!
10//! [`CdManager`]: ./CdManager.t.html
11
12#[macro_use]
13extern crate failure;
14
15use std::path::{Path, PathBuf};
16
17use failure::Error;
18
19pub mod error;
20
21/// A "Change Directory" manager or `CdManager` prevents you from forgetting to pop
22/// directories at the end of a block.
23///
24/// It takes a reference to a `PathBuf` and, upon going out of scope, will manually `pop`
25/// all elements of the `PathBuf` off that were added during its life.
26///
27/// The only supported operations are `push` or `pop`, more complex operations such as
28/// cannot easily be managed.
29///
30/// Note that the `CdManager` uses a path's `Components` to determine how many times
31/// to call `pop`, so this may cause some inconsistency if your path includes `.`.
32///
33/// A `CdManager` implements `AsRef<Path>` so it may be used anywhere a `Path` is needed.
34#[derive(Debug)]
35pub struct CdManager<'a> {
36    path: &'a mut PathBuf,
37    added_depth: usize,
38}
39
40impl<'a> CdManager<'a> {
41    /// Starts a new context from the given `PathBuf`
42    pub fn new(path: &'a mut PathBuf) -> Self {
43        CdManager {
44            path,
45            added_depth: 0,
46        }
47    }
48
49    /// Pushes a [`Path`] onto the [`PathBuf`], to be undone when the
50    /// [`CdManager`] goes out of scope.
51    ///
52    /// ```
53    /// extern crate cd_manager;
54    /// use cd_manager::CdManager;
55    /// use std::path::PathBuf;
56    ///
57    /// let mut path = PathBuf::from("a/path/to/something".to_string());
58    /// let mut p2 = path.clone();
59    ///
60    /// {
61    ///     let mut manager = CdManager::new(&mut p2);
62    ///
63    ///     path.push("foo/bar");
64    ///     manager.push("foo/bar");
65    ///
66    ///     assert_eq!(path, manager);
67    /// } // Automatically pop "foo" from the manager
68    ///
69    /// path.pop();
70    /// path.pop();
71    /// assert_eq!(path, p2);
72    /// ```
73    ///
74    /// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
75    /// [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
76    /// [`CdManager`]: ./CdManager.t.html
77    pub fn push<P: AsRef<Path>>(&mut self, path: P) {
78        self.added_depth += path.as_ref().components().count();
79        self.path.push(path);
80    }
81
82    /// Pops a single link from the underlying [`PathBuf`].
83    /// This will return an error if this is identical to the
84    /// [`PathBuf`] the [`CdManager`] was constructured with (that is,
85    /// the number of `pop` calls matches the number of pushed [`Path`] components).
86    ///
87    /// ```
88    /// extern crate cd_manager;
89    /// use cd_manager::CdManager;
90    /// use std::path::PathBuf;
91    ///
92    /// let mut path = PathBuf::from("a/path".to_string());
93    /// let mut p2 = path.clone();
94    /// {
95    ///     let mut cd = CdManager::new(&mut p2);
96    ///     cd.push("foo");
97    ///
98    ///     cd.pop().unwrap();
99    ///     assert_eq!(cd, path);
100    ///
101    ///     assert!(cd.pop().is_err());
102    /// }
103    ///
104    /// assert_eq!(path, p2);
105    /// ```
106    ///
107    /// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
108    /// [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
109    /// [`CdManager`]: ./CdManager.t.html
110    pub fn pop(&mut self) -> Result<(), Error> {
111        ensure!(self.added_depth > 0, error::TooManyPopError);
112
113        self.added_depth -= 1;
114        self.path.pop();
115
116        Ok(())
117    }
118
119    /// Creates a new "layer" of the manager, for scoping purposes
120    ///
121    /// That is, if you call [`CdManager::layer`] in a loop body or function call,
122    /// it ensures that any behavior done to the returned [`CdManager`] will be
123    /// undone for you.
124    ///
125    /// You can think of this as a cheaper, scoped [`Clone`].
126    ///
127    /// ```
128    /// extern crate cd_manager;
129    /// use cd_manager::CdManager;
130    /// use std::path::PathBuf;
131    ///
132    /// let mut path = PathBuf::from("a/path".to_string());
133    /// let mut p2 = path.clone();
134    ///
135    /// let mut cd = CdManager::new(&mut p2);
136    ///
137    /// for _ in 0..100 {
138    ///     assert_eq!(cd, path);
139    ///     let mut cd = cd.layer();
140    ///     cd.push("bar");
141    ///
142    ///     path.push("bar");
143    ///     assert_eq!(cd, path);
144    ///     path.pop();
145    /// }
146    /// ```
147    ///
148    /// [`CdManager`]: ./CdManager.t.html
149    /// [`CdManager::layer`]: ./CdManager.t.html#method.layer
150    /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html
151    pub fn layer(&mut self) -> CdManager {
152        CdManager::new(&mut self.path)
153    }
154
155    /// Returns a clone of the inner [`PathBuf`].
156    ///
157    /// [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
158    pub fn clone_inner(&self) -> PathBuf {
159        self.path.clone()
160    }
161}
162
163impl<'a, P: AsRef<Path>> PartialEq<P> for CdManager<'a> {
164    fn eq(&self, other: &P) -> bool {
165        self.path.as_path() == other.as_ref()
166    }
167}
168
169impl<'a> Eq for CdManager<'a> {}
170
171impl<'a> PartialEq<CdManager<'a>> for PathBuf {
172    fn eq(&self, other: &CdManager) -> bool {
173        self == other.path
174    }
175}
176
177impl<'a> Drop for CdManager<'a> {
178    fn drop(&mut self) {
179        for _ in 0..self.added_depth {
180            self.path.pop();
181        }
182    }
183}
184
185impl<'a> AsRef<Path> for CdManager<'a> {
186    fn as_ref(&self) -> &Path {
187        self.path
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use super::CdManager;
194    use std::path::PathBuf;
195
196    #[test]
197    fn cd_manager_push() {
198        let mut path = PathBuf::from("a/path/to/something".to_string());
199        let mut p2 = path.clone();
200
201        {
202            let mut cd_manager = CdManager::new(&mut p2);
203
204            cd_manager.push("abc/def");
205            path.push("abc/def");
206
207            assert_eq!(cd_manager.added_depth, 2);
208            assert_eq!(path, cd_manager);
209
210            path.pop();
211            path.pop();
212        }
213
214        assert_eq!(p2, path);
215    }
216
217    #[test]
218    fn cd_manager_pop() {
219        let mut path = PathBuf::from("a/path/to/something".to_string());
220        let mut p2 = path.clone();
221
222        {
223            let mut cd_manager = CdManager::new(&mut p2);
224
225            cd_manager.push("abc/def");
226            path.push("abc/def");
227
228            cd_manager.pop().unwrap();
229            path.pop();
230
231            assert_eq!(path, cd_manager);
232            assert_eq!(cd_manager.added_depth, 1);
233
234            path.pop();
235        }
236
237        assert_eq!(p2, path);
238    }
239
240    #[test]
241    fn cd_manager_error() {
242        let mut path = PathBuf::from("a/path/to/something".to_string());
243        let mut cd_manager = CdManager::new(&mut path);
244
245        assert!(cd_manager.pop().is_err());
246    }
247}