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}