]> git.lizzy.rs Git - rust.git/blobdiff - crates/paths/src/lib.rs
Auto merge of #12808 - Veykril:check-workspace, r=Veykril
[rust.git] / crates / paths / src / lib.rs
index 1b259682d9fa349ebc68ad37f10d37c3f2167850..6ae23ac841adaa6a7f97d8841c57aec96f2d2439 100644 (file)
@@ -1,11 +1,16 @@
 //! Thin wrappers around `std::path`, distinguishing between absolute and
 //! relative paths.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
 use std::{
-    convert::{TryFrom, TryInto},
+    borrow::Borrow,
+    ffi::OsStr,
     ops,
     path::{Component, Path, PathBuf},
 };
 
+/// Wrapper around an absolute [`PathBuf`].
 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
 pub struct AbsPathBuf(PathBuf);
 
@@ -34,6 +39,12 @@ fn as_ref(&self) -> &AbsPath {
     }
 }
 
+impl Borrow<AbsPath> for AbsPathBuf {
+    fn borrow(&self) -> &AbsPath {
+        self.as_path()
+    }
+}
+
 impl TryFrom<PathBuf> for AbsPathBuf {
     type Error = PathBuf;
     fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {
@@ -58,32 +69,48 @@ fn eq(&self, other: &AbsPath) -> bool {
 }
 
 impl AbsPathBuf {
+    /// Wrap the given absolute path in `AbsPathBuf`
+    ///
+    /// # Panics
+    ///
+    /// Panics if `path` is not absolute.
     pub fn assert(path: PathBuf) -> AbsPathBuf {
         AbsPathBuf::try_from(path)
             .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display()))
     }
+
+    /// Coerces to an `AbsPath` slice.
+    ///
+    /// Equivalent of [`PathBuf::as_path`] for `AbsPathBuf`.
     pub fn as_path(&self) -> &AbsPath {
         AbsPath::assert(self.0.as_path())
     }
+
+    /// Equivalent of [`PathBuf::pop`] for `AbsPathBuf`.
+    ///
+    /// Note that this won't remove the root component, so `self` will still be
+    /// absolute.
     pub fn pop(&mut self) -> bool {
         self.0.pop()
     }
 }
 
+/// Wrapper around an absolute [`Path`].
 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
 #[repr(transparent)]
 pub struct AbsPath(Path);
 
-impl ops::Deref for AbsPath {
-    type Target = Path;
-    fn deref(&self) -> &Path {
+impl AsRef<Path> for AbsPath {
+    fn as_ref(&self) -> &Path {
         &self.0
     }
 }
 
-impl AsRef<Path> for AbsPath {
-    fn as_ref(&self) -> &Path {
-        &self.0
+impl ToOwned for AbsPath {
+    type Owned = AbsPathBuf;
+
+    fn to_owned(&self) -> Self::Owned {
+        AbsPathBuf(self.0.to_owned())
     }
 }
 
@@ -98,28 +125,93 @@ fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> {
 }
 
 impl AbsPath {
+    /// Wrap the given absolute path in `AbsPath`
+    ///
+    /// # Panics
+    ///
+    /// Panics if `path` is not absolute.
     pub fn assert(path: &Path) -> &AbsPath {
         assert!(path.is_absolute());
         unsafe { &*(path as *const Path as *const AbsPath) }
     }
 
+    /// Equivalent of [`Path::parent`] for `AbsPath`.
     pub fn parent(&self) -> Option<&AbsPath> {
         self.0.parent().map(AbsPath::assert)
     }
+
+    /// Equivalent of [`Path::join`] for `AbsPath`.
     pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf {
         self.as_ref().join(path).try_into().unwrap()
     }
+
+    /// Normalize the given path:
+    /// - Removes repeated separators: `/a//b` becomes `/a/b`
+    /// - Removes occurrences of `.` and resolves `..`.
+    /// - Removes trailing slashes: `/a/b/` becomes `/a/b`.
+    ///
+    /// # Example
+    /// ```
+    /// # use paths::AbsPathBuf;
+    /// let abs_path_buf = AbsPathBuf::assert("/a/../../b/.//c//".into());
+    /// let normalized = abs_path_buf.normalize();
+    /// assert_eq!(normalized, AbsPathBuf::assert("/b/c".into()));
+    /// ```
     pub fn normalize(&self) -> AbsPathBuf {
         AbsPathBuf(normalize_path(&self.0))
     }
+
+    /// Equivalent of [`Path::to_path_buf`] for `AbsPath`.
     pub fn to_path_buf(&self) -> AbsPathBuf {
         AbsPathBuf::try_from(self.0.to_path_buf()).unwrap()
     }
+
+    /// Equivalent of [`Path::strip_prefix`] for `AbsPath`.
+    ///
+    /// Returns a relative path.
     pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> {
         self.0.strip_prefix(base).ok().map(RelPath::new_unchecked)
     }
+    pub fn starts_with(&self, base: &AbsPath) -> bool {
+        self.0.starts_with(&base.0)
+    }
+    pub fn ends_with(&self, suffix: &RelPath) -> bool {
+        self.0.ends_with(&suffix.0)
+    }
+
+    // region:delegate-methods
+
+    // Note that we deliberately don't implement `Deref<Target = Path>` here.
+    //
+    // The problem with `Path` is that it directly exposes convenience IO-ing
+    // methods. For example, `Path::exists` delegates to `fs::metadata`.
+    //
+    // For `AbsPath`, we want to make sure that this is a POD type, and that all
+    // IO goes via `fs`. That way, it becomes easier to mock IO when we need it.
+
+    pub fn file_name(&self) -> Option<&OsStr> {
+        self.0.file_name()
+    }
+    pub fn extension(&self) -> Option<&OsStr> {
+        self.0.extension()
+    }
+    pub fn file_stem(&self) -> Option<&OsStr> {
+        self.0.file_stem()
+    }
+    pub fn as_os_str(&self) -> &OsStr {
+        self.0.as_os_str()
+    }
+    pub fn display(&self) -> std::path::Display<'_> {
+        self.0.display()
+    }
+    #[deprecated(note = "use std::fs::metadata().is_ok() instead")]
+    pub fn exists(&self) -> bool {
+        self.0.exists()
+    }
+    // endregion:delegate-methods
 }
 
+/// Wrapper around a relative [`PathBuf`].
 #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
 pub struct RelPathBuf(PathBuf);
 
@@ -160,22 +252,19 @@ fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> {
 }
 
 impl RelPathBuf {
+    /// Coerces to a `RelPath` slice.
+    ///
+    /// Equivalent of [`PathBuf::as_path`] for `RelPathBuf`.
     pub fn as_path(&self) -> &RelPath {
         RelPath::new_unchecked(self.0.as_path())
     }
 }
 
+/// Wrapper around a relative [`Path`].
 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
 #[repr(transparent)]
 pub struct RelPath(Path);
 
-impl ops::Deref for RelPath {
-    type Target = Path;
-    fn deref(&self) -> &Path {
-        &self.0
-    }
-}
-
 impl AsRef<Path> for RelPath {
     fn as_ref(&self) -> &Path {
         &self.0
@@ -183,15 +272,16 @@ fn as_ref(&self) -> &Path {
 }
 
 impl RelPath {
+    /// Creates a new `RelPath` from `path`, without checking if it is relative.
     pub fn new_unchecked(path: &Path) -> &RelPath {
         unsafe { &*(path as *const Path as *const RelPath) }
     }
 }
 
-// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
+/// Taken from <https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85>
 fn normalize_path(path: &Path) -> PathBuf {
     let mut components = path.components().peekable();
-    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
+    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
         components.next();
         PathBuf::from(c.as_os_str())
     } else {