]> git.lizzy.rs Git - rust.git/blob - crates/paths/src/lib.rs
Merge #9944
[rust.git] / crates / paths / src / lib.rs
1 //! Thin wrappers around `std::path`, distinguishing between absolute and
2 //! relative paths.
3 use std::{
4     borrow::Borrow,
5     convert::{TryFrom, TryInto},
6     ffi::OsStr,
7     ops,
8     path::{Component, Path, PathBuf},
9 };
10
11 /// Wrapper around an absolute [`PathBuf`].
12 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
13 pub struct AbsPathBuf(PathBuf);
14
15 impl From<AbsPathBuf> for PathBuf {
16     fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf {
17         path_buf
18     }
19 }
20
21 impl ops::Deref for AbsPathBuf {
22     type Target = AbsPath;
23     fn deref(&self) -> &AbsPath {
24         self.as_path()
25     }
26 }
27
28 impl AsRef<Path> for AbsPathBuf {
29     fn as_ref(&self) -> &Path {
30         self.0.as_path()
31     }
32 }
33
34 impl AsRef<AbsPath> for AbsPathBuf {
35     fn as_ref(&self) -> &AbsPath {
36         self.as_path()
37     }
38 }
39
40 impl Borrow<AbsPath> for AbsPathBuf {
41     fn borrow(&self) -> &AbsPath {
42         self.as_path()
43     }
44 }
45
46 impl TryFrom<PathBuf> for AbsPathBuf {
47     type Error = PathBuf;
48     fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {
49         if !path_buf.is_absolute() {
50             return Err(path_buf);
51         }
52         Ok(AbsPathBuf(path_buf))
53     }
54 }
55
56 impl TryFrom<&str> for AbsPathBuf {
57     type Error = PathBuf;
58     fn try_from(path: &str) -> Result<AbsPathBuf, PathBuf> {
59         AbsPathBuf::try_from(PathBuf::from(path))
60     }
61 }
62
63 impl PartialEq<AbsPath> for AbsPathBuf {
64     fn eq(&self, other: &AbsPath) -> bool {
65         self.as_path() == other
66     }
67 }
68
69 impl serde::Serialize for AbsPathBuf {
70     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71     where
72         S: serde::Serializer,
73     {
74         self.0.serialize(serializer)
75     }
76 }
77
78 impl<'de> serde::Deserialize<'de> for AbsPathBuf {
79     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80     where
81         D: serde::Deserializer<'de>,
82     {
83         let path = PathBuf::deserialize(deserializer)?;
84         AbsPathBuf::try_from(path).map_err(|path| {
85             serde::de::Error::custom(format!("expected absolute path, got {}", path.display()))
86         })
87     }
88 }
89
90 impl AbsPathBuf {
91     /// Wrap the given absolute path in `AbsPathBuf`
92     ///
93     /// # Panics
94     ///
95     /// Panics if `path` is not absolute.
96     pub fn assert(path: PathBuf) -> AbsPathBuf {
97         AbsPathBuf::try_from(path)
98             .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display()))
99     }
100
101     /// Coerces to an `AbsPath` slice.
102     ///
103     /// Equivalent of [`PathBuf::as_path`] for `AbsPathBuf`.
104     pub fn as_path(&self) -> &AbsPath {
105         AbsPath::assert(self.0.as_path())
106     }
107
108     /// Equivalent of [`PathBuf::pop`] for `AbsPathBuf`.
109     ///
110     /// Note that this won't remove the root component, so `self` will still be
111     /// absolute.
112     pub fn pop(&mut self) -> bool {
113         self.0.pop()
114     }
115 }
116
117 /// Wrapper around an absolute [`Path`].
118 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
119 #[repr(transparent)]
120 pub struct AbsPath(Path);
121
122 impl AsRef<Path> for AbsPath {
123     fn as_ref(&self) -> &Path {
124         &self.0
125     }
126 }
127
128 impl<'a> TryFrom<&'a Path> for &'a AbsPath {
129     type Error = &'a Path;
130     fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> {
131         if !path.is_absolute() {
132             return Err(path);
133         }
134         Ok(AbsPath::assert(path))
135     }
136 }
137
138 impl AbsPath {
139     /// Wrap the given absolute path in `AbsPath`
140     ///
141     /// # Panics
142     ///
143     /// Panics if `path` is not absolute.
144     pub fn assert(path: &Path) -> &AbsPath {
145         assert!(path.is_absolute());
146         unsafe { &*(path as *const Path as *const AbsPath) }
147     }
148
149     /// Equivalent of [`Path::parent`] for `AbsPath`.
150     pub fn parent(&self) -> Option<&AbsPath> {
151         self.0.parent().map(AbsPath::assert)
152     }
153
154     /// Equivalent of [`Path::join`] for `AbsPath`.
155     pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf {
156         self.as_ref().join(path).try_into().unwrap()
157     }
158
159     /// Normalize the given path:
160     /// - Removes repeated separators: `/a//b` becomes `/a/b`
161     /// - Removes occurrences of `.` and resolves `..`.
162     /// - Removes trailing slashes: `/a/b/` becomes `/a/b`.
163     ///
164     /// # Example
165     /// ```
166     /// # use paths::AbsPathBuf;
167     /// let abs_path_buf = AbsPathBuf::assert("/a/../../b/.//c//".into());
168     /// let normalized = abs_path_buf.normalize();
169     /// assert_eq!(normalized, AbsPathBuf::assert("/b/c".into()));
170     /// ```
171     pub fn normalize(&self) -> AbsPathBuf {
172         AbsPathBuf(normalize_path(&self.0))
173     }
174
175     /// Equivalent of [`Path::to_path_buf`] for `AbsPath`.
176     pub fn to_path_buf(&self) -> AbsPathBuf {
177         AbsPathBuf::try_from(self.0.to_path_buf()).unwrap()
178     }
179
180     /// Equivalent of [`Path::strip_prefix`] for `AbsPath`.
181     ///
182     /// Returns a relative path.
183     pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> {
184         self.0.strip_prefix(base).ok().map(RelPath::new_unchecked)
185     }
186     pub fn starts_with(&self, base: &AbsPath) -> bool {
187         self.0.starts_with(&base.0)
188     }
189     pub fn ends_with(&self, suffix: &RelPath) -> bool {
190         self.0.ends_with(&suffix.0)
191     }
192
193     // region:delegate-methods
194
195     // Note that we deliberately don't implement `Deref<Target = Path>` here.
196     //
197     // The problem with `Path` is that it directly exposes convenience IO-ing
198     // methods. For example, `Path::exists` delegates to `fs::metadata`.
199     //
200     // For `AbsPath`, we want to make sure that this is a POD type, and that all
201     // IO goes via `fs`. That way, it becomes easier to mock IO when we need it.
202
203     pub fn file_name(&self) -> Option<&OsStr> {
204         self.0.file_name()
205     }
206     pub fn extension(&self) -> Option<&OsStr> {
207         self.0.extension()
208     }
209     pub fn file_stem(&self) -> Option<&OsStr> {
210         self.0.file_stem()
211     }
212     pub fn as_os_str(&self) -> &OsStr {
213         self.0.as_os_str()
214     }
215     pub fn display(&self) -> std::path::Display<'_> {
216         self.0.display()
217     }
218     #[deprecated(note = "use std::fs::metadata().is_ok() instead")]
219     pub fn exists(&self) -> bool {
220         self.0.exists()
221     }
222     // endregion:delegate-methods
223 }
224
225 /// Wrapper around a relative [`PathBuf`].
226 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
227 pub struct RelPathBuf(PathBuf);
228
229 impl From<RelPathBuf> for PathBuf {
230     fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf {
231         path_buf
232     }
233 }
234
235 impl ops::Deref for RelPathBuf {
236     type Target = RelPath;
237     fn deref(&self) -> &RelPath {
238         self.as_path()
239     }
240 }
241
242 impl AsRef<Path> for RelPathBuf {
243     fn as_ref(&self) -> &Path {
244         self.0.as_path()
245     }
246 }
247
248 impl TryFrom<PathBuf> for RelPathBuf {
249     type Error = PathBuf;
250     fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> {
251         if !path_buf.is_relative() {
252             return Err(path_buf);
253         }
254         Ok(RelPathBuf(path_buf))
255     }
256 }
257
258 impl TryFrom<&str> for RelPathBuf {
259     type Error = PathBuf;
260     fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> {
261         RelPathBuf::try_from(PathBuf::from(path))
262     }
263 }
264
265 impl RelPathBuf {
266     /// Coerces to a `RelPath` slice.
267     ///
268     /// Equivalent of [`PathBuf::as_path`] for `RelPathBuf`.
269     pub fn as_path(&self) -> &RelPath {
270         RelPath::new_unchecked(self.0.as_path())
271     }
272 }
273
274 /// Wrapper around a relative [`Path`].
275 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
276 #[repr(transparent)]
277 pub struct RelPath(Path);
278
279 impl AsRef<Path> for RelPath {
280     fn as_ref(&self) -> &Path {
281         &self.0
282     }
283 }
284
285 impl RelPath {
286     /// Creates a new `RelPath` from `path`, without checking if it is relative.
287     pub fn new_unchecked(path: &Path) -> &RelPath {
288         unsafe { &*(path as *const Path as *const RelPath) }
289     }
290 }
291
292 /// Taken from <https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85>
293 fn normalize_path(path: &Path) -> PathBuf {
294     let mut components = path.components().peekable();
295     let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
296         components.next();
297         PathBuf::from(c.as_os_str())
298     } else {
299         PathBuf::new()
300     };
301
302     for component in components {
303         match component {
304             Component::Prefix(..) => unreachable!(),
305             Component::RootDir => {
306                 ret.push(component.as_os_str());
307             }
308             Component::CurDir => {}
309             Component::ParentDir => {
310                 ret.pop();
311             }
312             Component::Normal(c) => {
313                 ret.push(c);
314             }
315         }
316     }
317     ret
318 }