| use std::borrow::Borrow; |
| use std::borrow::Cow; |
| use std::cmp::Ordering; |
| use std::ffi::OsStr; |
| use std::ffi::OsString; |
| use std::fs::Metadata; |
| use std::fs::ReadDir; |
| use std::io; |
| use std::mem; |
| use std::ops::Deref; |
| use std::path::Component; |
| use std::path::Components; |
| use std::path::Path; |
| use std::path::PathBuf; |
| |
| use super::error::MissingPrefixBufError; |
| use super::error::MissingPrefixError; |
| use super::error::ParentError; |
| use super::normalize; |
| use super::PathExt; |
| |
| /// A borrowed path that has a [prefix] on Windows. |
| /// |
| /// Note that comparison traits such as [`PartialEq`] will compare paths |
| /// literally instead of comparing components. The former is more efficient and |
| /// easier to use correctly. |
| /// |
| /// # Safety |
| /// |
| /// This type should not be used for memory safety, but implementations can |
| /// panic if this path is missing a prefix on Windows. A safe `new_unchecked` |
| /// method might be added later that can safely create invalid base paths. |
| /// |
| /// Although this type is annotated with `#[repr(transparent)]`, the inner |
| /// representation is not stable. Transmuting between this type and any other |
| /// causes immediate undefined behavior. |
| /// |
| /// [prefix]: ::std::path::Prefix |
| #[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| #[repr(transparent)] |
| pub struct BasePath(pub(super) OsStr); |
| |
| impl BasePath { |
| fn from_inner(path: &OsStr) -> &Self { |
| // SAFETY: This struct has a layout that makes this operation safe. |
| unsafe { mem::transmute(path) } |
| } |
| |
| /// Creates a new base path. |
| /// |
| /// On Windows, if `path` is missing a [prefix], it will be joined to the |
| /// current directory. |
| /// |
| /// # Errors |
| /// |
| /// Returns an error if reading the current directory fails. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::io; |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePath; |
| /// |
| /// if cfg!(windows) { |
| /// let path = Path::new(r"X:\foo\bar"); |
| /// assert_eq!(path, *BasePath::new(path)?); |
| /// |
| /// assert!(BasePath::new(Path::new(r"foo\bar")).is_ok()); |
| /// } |
| /// # |
| /// # Ok::<_, io::Error>(()) |
| /// ``` |
| /// |
| /// [prefix]: ::std::path::Prefix |
| #[inline] |
| pub fn new<'a, P>(path: P) -> io::Result<Cow<'a, Self>> |
| where |
| P: Into<Cow<'a, Path>>, |
| { |
| let path = path.into(); |
| match path { |
| Cow::Borrowed(path) => Self::try_new(path) |
| .map(Cow::Borrowed) |
| .or_else(|_| normalize::to_base(path).map(Cow::Owned)), |
| Cow::Owned(path) => BasePathBuf::new(path).map(Cow::Owned), |
| } |
| } |
| |
| /// Creates a new base path. |
| /// |
| /// # Errors |
| /// |
| /// On Windows, returns an error if `path` is missing a [prefix]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// # use normpath::error::MissingPrefixError; |
| /// use normpath::BasePath; |
| /// |
| /// if cfg!(windows) { |
| /// let path = r"X:\foo\bar"; |
| /// assert_eq!(Path::new(path), BasePath::try_new(path)?); |
| /// |
| /// assert!(BasePath::try_new(r"foo\bar").is_err()); |
| /// } |
| /// # |
| /// # Ok::<_, MissingPrefixError>(()) |
| /// ``` |
| /// |
| /// [prefix]: ::std::path::Prefix |
| #[inline] |
| pub fn try_new<P>(path: &P) -> Result<&Self, MissingPrefixError> |
| where |
| P: AsRef<Path> + ?Sized, |
| { |
| let path = path.as_ref(); |
| if normalize::is_base(path) { |
| Ok(Self::from_inner(path.as_os_str())) |
| } else { |
| Err(MissingPrefixError(())) |
| } |
| } |
| |
| /// Returns a reference to the wrapped path as a platform string. |
| #[inline] |
| #[must_use] |
| pub fn as_os_str(&self) -> &OsStr { |
| &self.0 |
| } |
| |
| /// Returns a reference to the wrapped path. |
| #[inline] |
| #[must_use] |
| pub fn as_path(&self) -> &Path { |
| Path::new(&self.0) |
| } |
| |
| /// Equivalent to [`Path::canonicalize`]. |
| #[inline] |
| pub fn canonicalize(&self) -> io::Result<BasePathBuf> { |
| self.as_path().canonicalize().map(|base| { |
| debug_assert!(normalize::is_base(&base)); |
| BasePathBuf(base.into_os_string()) |
| }) |
| } |
| |
| /// Equivalent to [`Path::components`]. |
| #[inline] |
| pub fn components(&self) -> Components<'_> { |
| self.as_path().components() |
| } |
| |
| /// Equivalent to [`Path::ends_with`]. |
| #[inline] |
| #[must_use] |
| pub fn ends_with<P>(&self, child: P) -> bool |
| where |
| P: AsRef<Path>, |
| { |
| self.as_path().ends_with(child) |
| } |
| |
| /// Equivalent to [`Path::exists`]. |
| #[inline] |
| #[must_use] |
| pub fn exists(&self) -> bool { |
| self.as_path().exists() |
| } |
| |
| /// Equivalent to [`Path::extension`]. |
| #[inline] |
| #[must_use] |
| pub fn extension(&self) -> Option<&OsStr> { |
| self.as_path().extension() |
| } |
| |
| /// Equivalent to [`Path::file_name`]. |
| #[inline] |
| #[must_use] |
| pub fn file_name(&self) -> Option<&OsStr> { |
| self.as_path().file_name() |
| } |
| |
| /// Equivalent to [`Path::file_stem`]. |
| #[inline] |
| #[must_use] |
| pub fn file_stem(&self) -> Option<&OsStr> { |
| self.as_path().file_stem() |
| } |
| |
| /// Equivalent to [`Path::has_root`]. |
| #[inline] |
| #[must_use] |
| pub fn has_root(&self) -> bool { |
| self.as_path().has_root() |
| } |
| |
| /// Equivalent to [`Path::is_absolute`]. |
| #[inline] |
| #[must_use] |
| pub fn is_absolute(&self) -> bool { |
| self.as_path().is_absolute() |
| } |
| |
| /// Equivalent to [`Path::is_dir`]. |
| #[inline] |
| #[must_use] |
| pub fn is_dir(&self) -> bool { |
| self.as_path().is_dir() |
| } |
| |
| /// Equivalent to [`Path::is_file`]. |
| #[inline] |
| #[must_use] |
| pub fn is_file(&self) -> bool { |
| self.as_path().is_file() |
| } |
| |
| /// Equivalent to [`Path::is_relative`]. |
| #[inline] |
| #[must_use] |
| pub fn is_relative(&self) -> bool { |
| self.as_path().is_relative() |
| } |
| |
| /// An improved version of [`Path::join`] that handles more edge cases. |
| /// |
| /// For example, on Windows, leading `.` and `..` components of `path` will |
| /// be normalized if possible. If `self` is a [verbatim] path, it would be |
| /// invalid to normalize them later. |
| /// |
| /// You should still call [`normalize`] before [`parent`] to normalize some |
| /// additional components. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePath; |
| /// |
| /// if cfg!(windows) { |
| /// assert_eq!( |
| /// Path::new(r"\\?\foo\baz\test.rs"), |
| /// BasePath::try_new(r"\\?\foo\bar") |
| /// .unwrap() |
| /// .join("../baz/test.rs"), |
| /// ); |
| /// } |
| /// ``` |
| /// |
| /// [`normalize`]: Self::normalize |
| /// [`parent`]: Self::parent |
| /// [verbatim]: ::std::path::Prefix::is_verbatim |
| #[inline] |
| pub fn join<P>(&self, path: P) -> BasePathBuf |
| where |
| P: AsRef<Path>, |
| { |
| let mut base = self.to_owned(); |
| base.push(path); |
| base |
| } |
| |
| /// Equivalent to [`PathExt::localize_name`]. |
| #[cfg(feature = "localization")] |
| #[cfg_attr(normpath_docs_rs, doc(cfg(feature = "localization")))] |
| #[inline] |
| #[must_use] |
| pub fn localize_name(&self) -> Cow<'_, OsStr> { |
| self.as_path().localize_name() |
| } |
| |
| /// Equivalent to [`Path::metadata`]. |
| #[inline] |
| pub fn metadata(&self) -> io::Result<Metadata> { |
| self.as_path().metadata() |
| } |
| |
| /// Equivalent to [`PathExt::normalize`]. |
| #[inline] |
| pub fn normalize(&self) -> io::Result<BasePathBuf> { |
| self.as_path().normalize() |
| } |
| |
| /// Equivalent to [`PathExt::normalize_virtually`]. |
| #[cfg(any(doc, windows))] |
| #[cfg_attr(normpath_docs_rs, doc(cfg(windows)))] |
| #[inline] |
| pub fn normalize_virtually(&self) -> io::Result<BasePathBuf> { |
| self.as_path().normalize_virtually() |
| } |
| |
| fn check_parent(&self) -> Result<(), ParentError> { |
| self.components() |
| .next_back() |
| .filter(|x| matches!(x, Component::Normal(_) | Component::RootDir)) |
| .map(|_| ()) |
| .ok_or(ParentError(())) |
| } |
| |
| /// Returns this path without its last component. |
| /// |
| /// Returns `Ok(None)` if the last component is [`Component::RootDir`]. |
| /// |
| /// You should usually only call this method on [normalized] paths. They |
| /// will prevent an unexpected path from being returned due to symlinks, |
| /// and some `.` and `..` components will be normalized. |
| /// |
| /// # Errors |
| /// |
| /// Returns an error if the last component is not [`Component::Normal`] or |
| /// [`Component::RootDir`]. To ignore this error, use [`parent_unchecked`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// # use normpath::error::ParentError; |
| /// use normpath::BasePath; |
| /// |
| /// if cfg!(windows) { |
| /// assert_eq!( |
| /// Path::new(r"X:\foo"), |
| /// BasePath::try_new(r"X:\foo\bar").unwrap().parent()?.unwrap(), |
| /// ); |
| /// } |
| /// # |
| /// # Ok::<_, ParentError>(()) |
| /// ``` |
| /// |
| /// [normalized]: Self::normalize |
| /// [`parent_unchecked`]: Self::parent_unchecked |
| #[inline] |
| pub fn parent(&self) -> Result<Option<&Self>, ParentError> { |
| self.check_parent().map(|()| self.parent_unchecked()) |
| } |
| |
| /// Equivalent to [`Path::parent`]. |
| /// |
| /// It is usually better to use [`parent`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePath; |
| /// |
| /// if cfg!(windows) { |
| /// assert_eq!( |
| /// Path::new(r"X:\foo"), |
| /// BasePath::try_new(r"X:\foo\..") |
| /// .unwrap() |
| /// .parent_unchecked() |
| /// .unwrap(), |
| /// ); |
| /// } |
| /// ``` |
| /// |
| /// [`parent`]: Self::parent |
| #[inline] |
| #[must_use] |
| pub fn parent_unchecked(&self) -> Option<&Self> { |
| self.as_path() |
| .parent() |
| .map(|x| Self::from_inner(x.as_os_str())) |
| } |
| |
| /// Equivalent to [`Path::read_dir`]. |
| #[inline] |
| pub fn read_dir(&self) -> io::Result<ReadDir> { |
| self.as_path().read_dir() |
| } |
| |
| /// Equivalent to [`Path::read_link`]. |
| #[inline] |
| pub fn read_link(&self) -> io::Result<PathBuf> { |
| self.as_path().read_link() |
| } |
| |
| /// Equivalent to [`Path::starts_with`]. |
| #[inline] |
| #[must_use] |
| pub fn starts_with<P>(&self, base: P) -> bool |
| where |
| P: AsRef<Path>, |
| { |
| self.as_path().starts_with(base) |
| } |
| |
| /// Equivalent to [`Path::symlink_metadata`]. |
| #[inline] |
| pub fn symlink_metadata(&self) -> io::Result<Metadata> { |
| self.as_path().symlink_metadata() |
| } |
| } |
| |
| impl AsRef<OsStr> for BasePath { |
| #[inline] |
| fn as_ref(&self) -> &OsStr { |
| &self.0 |
| } |
| } |
| |
| impl AsRef<Path> for BasePath { |
| #[inline] |
| fn as_ref(&self) -> &Path { |
| self.as_path() |
| } |
| } |
| |
| impl AsRef<Self> for BasePath { |
| #[inline] |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<'a> From<&'a BasePath> for Cow<'a, BasePath> { |
| #[inline] |
| fn from(value: &'a BasePath) -> Self { |
| Cow::Borrowed(value) |
| } |
| } |
| |
| impl PartialEq<Path> for BasePath { |
| #[inline] |
| fn eq(&self, other: &Path) -> bool { |
| &self.0 == other.as_os_str() |
| } |
| } |
| |
| impl PartialEq<BasePath> for Path { |
| #[inline] |
| fn eq(&self, other: &BasePath) -> bool { |
| other == self |
| } |
| } |
| |
| impl PartialOrd<Path> for BasePath { |
| #[inline] |
| fn partial_cmp(&self, other: &Path) -> Option<Ordering> { |
| self.0.partial_cmp(other.as_os_str()) |
| } |
| } |
| |
| impl PartialOrd<BasePath> for Path { |
| #[inline] |
| fn partial_cmp(&self, other: &BasePath) -> Option<Ordering> { |
| other.partial_cmp(self) |
| } |
| } |
| |
| impl ToOwned for BasePath { |
| type Owned = BasePathBuf; |
| |
| #[inline] |
| fn to_owned(&self) -> Self::Owned { |
| BasePathBuf(self.0.to_owned()) |
| } |
| } |
| |
| /// An owned path that has a [prefix] on Windows. |
| /// |
| /// For more information, see [`BasePath`]. |
| /// |
| /// [prefix]: ::std::path::Prefix |
| #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| pub struct BasePathBuf(pub(super) OsString); |
| |
| impl BasePathBuf { |
| /// Equivalent to [`BasePath::new`] but returns an owned path. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # use std::io; |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePathBuf; |
| /// |
| /// if cfg!(windows) { |
| /// let path = r"X:\foo\bar"; |
| /// assert_eq!(Path::new(path), BasePathBuf::new(path)?); |
| /// |
| /// assert!(BasePathBuf::new(r"foo\bar").is_ok()); |
| /// } |
| /// # |
| /// # Ok::<_, io::Error>(()) |
| /// ``` |
| #[inline] |
| pub fn new<P>(path: P) -> io::Result<Self> |
| where |
| P: Into<PathBuf>, |
| { |
| Self::try_new(path).or_else(|x| normalize::to_base(&x.0)) |
| } |
| |
| /// Equivalent to [`BasePath::try_new`] but returns an owned path. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// # use normpath::error::MissingPrefixBufError; |
| /// use normpath::BasePathBuf; |
| /// |
| /// if cfg!(windows) { |
| /// let path = r"X:\foo\bar"; |
| /// assert_eq!(Path::new(path), BasePathBuf::try_new(path)?); |
| /// |
| /// assert!(BasePathBuf::try_new(r"foo\bar").is_err()); |
| /// } |
| /// # |
| /// # Ok::<_, MissingPrefixBufError>(()) |
| /// ``` |
| #[inline] |
| pub fn try_new<P>(path: P) -> Result<Self, MissingPrefixBufError> |
| where |
| P: Into<PathBuf>, |
| { |
| let path = path.into(); |
| if normalize::is_base(&path) { |
| Ok(Self(path.into_os_string())) |
| } else { |
| Err(MissingPrefixBufError(path)) |
| } |
| } |
| |
| /// Returns the wrapped path as a platform string. |
| #[inline] |
| #[must_use] |
| pub fn into_os_string(self) -> OsString { |
| self.0 |
| } |
| |
| /// Returns the wrapped path. |
| #[inline] |
| #[must_use] |
| pub fn into_path_buf(self) -> PathBuf { |
| self.0.into() |
| } |
| |
| /// Equivalent to [`BasePath::parent`] but modifies `self` in place. |
| /// |
| /// Returns `Ok(false)` when [`BasePath::parent`] returns `Ok(None)`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// # use normpath::error::ParentError; |
| /// use normpath::BasePathBuf; |
| /// |
| /// if cfg!(windows) { |
| /// let mut path = BasePathBuf::try_new(r"X:\foo\bar").unwrap(); |
| /// assert!(path.pop()?); |
| /// assert_eq!(Path::new(r"X:\foo"), path); |
| /// } |
| /// # |
| /// # Ok::<_, ParentError>(()) |
| /// ``` |
| #[inline] |
| pub fn pop(&mut self) -> Result<bool, ParentError> { |
| self.check_parent().map(|()| self.pop_unchecked()) |
| } |
| |
| pub(super) fn replace_with<F>(&mut self, replace_fn: F) |
| where |
| F: FnOnce(PathBuf) -> PathBuf, |
| { |
| self.0 = replace_fn(mem::take(&mut self.0).into()).into_os_string(); |
| } |
| |
| /// Equivalent to [`PathBuf::pop`]. |
| /// |
| /// It is usually better to use [`pop`]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePathBuf; |
| /// |
| /// if cfg!(windows) { |
| /// let mut path = BasePathBuf::try_new(r"X:\foo\..").unwrap(); |
| /// assert!(path.pop_unchecked()); |
| /// assert_eq!(Path::new(r"X:\foo"), path); |
| /// } |
| /// ``` |
| /// |
| /// [`pop`]: Self::pop |
| #[inline] |
| pub fn pop_unchecked(&mut self) -> bool { |
| // This value is never used. |
| let mut result = false; |
| self.replace_with(|mut base| { |
| result = base.pop(); |
| base |
| }); |
| result |
| } |
| |
| /// Equivalent to [`BasePath::join`] but modifies `self` in place. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use std::path::Path; |
| /// |
| /// use normpath::BasePathBuf; |
| /// |
| /// if cfg!(windows) { |
| /// let mut path = BasePathBuf::try_new(r"\\?\foo\bar").unwrap(); |
| /// path.push("../baz/test.rs"); |
| /// assert_eq!(Path::new(r"\\?\foo\baz\test.rs"), path); |
| /// } |
| /// ``` |
| #[inline] |
| pub fn push<P>(&mut self, path: P) |
| where |
| P: AsRef<Path>, |
| { |
| normalize::push(self, path.as_ref()); |
| } |
| } |
| |
| impl AsRef<OsStr> for BasePathBuf { |
| #[inline] |
| fn as_ref(&self) -> &OsStr { |
| &self.0 |
| } |
| } |
| |
| impl AsRef<Path> for BasePathBuf { |
| #[inline] |
| fn as_ref(&self) -> &Path { |
| self.as_path() |
| } |
| } |
| |
| impl AsRef<BasePath> for BasePathBuf { |
| #[inline] |
| fn as_ref(&self) -> &BasePath { |
| self |
| } |
| } |
| |
| impl Borrow<BasePath> for BasePathBuf { |
| #[inline] |
| fn borrow(&self) -> &BasePath { |
| self |
| } |
| } |
| |
| impl Deref for BasePathBuf { |
| type Target = BasePath; |
| |
| #[inline] |
| fn deref(&self) -> &BasePath { |
| BasePath::from_inner(&self.0) |
| } |
| } |
| |
| impl From<BasePathBuf> for Cow<'_, BasePath> { |
| #[inline] |
| fn from(value: BasePathBuf) -> Self { |
| Cow::Owned(value) |
| } |
| } |
| |
| impl From<BasePathBuf> for OsString { |
| #[inline] |
| fn from(value: BasePathBuf) -> Self { |
| value.0 |
| } |
| } |
| |
| impl From<BasePathBuf> for PathBuf { |
| #[inline] |
| fn from(value: BasePathBuf) -> Self { |
| value.into_path_buf() |
| } |
| } |
| |
| #[cfg(feature = "print_bytes")] |
| #[cfg_attr(normpath_docs_rs, doc(cfg(feature = "print_bytes")))] |
| mod print_bytes { |
| use print_bytes::ByteStr; |
| use print_bytes::ToBytes; |
| #[cfg(windows)] |
| use print_bytes::WideStr; |
| |
| use super::BasePath; |
| use super::BasePathBuf; |
| |
| impl ToBytes for BasePath { |
| #[inline] |
| fn to_bytes(&self) -> ByteStr<'_> { |
| self.0.to_bytes() |
| } |
| |
| #[cfg(windows)] |
| #[inline] |
| fn to_wide(&self) -> Option<WideStr> { |
| self.0.to_wide() |
| } |
| } |
| |
| impl ToBytes for BasePathBuf { |
| #[inline] |
| fn to_bytes(&self) -> ByteStr<'_> { |
| (**self).to_bytes() |
| } |
| |
| #[cfg(windows)] |
| #[inline] |
| fn to_wide(&self) -> Option<WideStr> { |
| (**self).to_wide() |
| } |
| } |
| } |
| |
| #[cfg(feature = "serde")] |
| #[cfg_attr(normpath_docs_rs, doc(cfg(feature = "serde")))] |
| mod serde { |
| use std::ffi::OsString; |
| |
| use serde::Deserialize; |
| use serde::Deserializer; |
| use serde::Serialize; |
| use serde::Serializer; |
| |
| use super::BasePath; |
| use super::BasePathBuf; |
| |
| impl<'de> Deserialize<'de> for BasePathBuf { |
| #[inline] |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: Deserializer<'de>, |
| { |
| OsString::deserialize(deserializer).map(Self) |
| } |
| } |
| |
| impl Serialize for BasePath { |
| #[inline] |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| serializer.serialize_newtype_struct("BasePath", &self.0) |
| } |
| } |
| |
| impl Serialize for BasePathBuf { |
| #[inline] |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| serializer.serialize_newtype_struct("BasePathBuf", &self.0) |
| } |
| } |
| } |
| |
| #[cfg(feature = "uniquote")] |
| #[cfg_attr(normpath_docs_rs, doc(cfg(feature = "uniquote")))] |
| mod uniquote { |
| use uniquote::Formatter; |
| use uniquote::Quote; |
| use uniquote::Result; |
| |
| use super::BasePath; |
| use super::BasePathBuf; |
| |
| impl Quote for BasePath { |
| #[inline] |
| fn escape(&self, f: &mut Formatter<'_>) -> Result { |
| self.0.escape(f) |
| } |
| } |
| |
| impl Quote for BasePathBuf { |
| #[inline] |
| fn escape(&self, f: &mut Formatter<'_>) -> Result { |
| (**self).escape(f) |
| } |
| } |
| } |