579 lines · 17.2 KB · Rust
RawDownload
use crate::paths::{PathStyle, is_absolute};use anyhow::{Context as _, Result, anyhow};use serde::{Deserialize, Serialize};use std::{    borrow::{Borrow, Cow},    fmt,    ops::Deref,    path::{Path, PathBuf},    sync::Arc,}; /// A file system path that is guaranteed to be relative and normalized.////// This type can be used to represent paths in a uniform way, regardless of/// whether they refer to Windows or POSIX file systems, and regardless of/// the host platform.////// Internally, paths are stored in POSIX ('/'-delimited) format, but they can/// be displayed in either POSIX or Windows format.////// Relative paths are also guaranteed to be valid unicode.#[repr(transparent)]#[derive(PartialEq, Eq, Hash, Serialize)]pub struct RelPath(str); /// An owned representation of a file system path that is guaranteed to be/// relative and normalized.////// This type is to [`RelPath`] as [`std::path::PathBuf`] is to [`std::path::Path`]#[derive(Clone, Serialize, Deserialize)]pub struct RelPathBuf(String); impl RelPath {    /// Creates an empty [`RelPath`].    pub fn empty() -> &'static Self {        Self::new_unchecked("")    }     /// Converts a path with a given style into a [`RelPath`].    ///    /// Returns an error if the path is absolute, or is not valid unicode.    ///    /// This method will normalize the path by removing `.` components,    /// processing `..` components, and removing trailing separators. It does    /// not allocate unless it's necessary to reformat the path.    #[track_caller]    pub fn new<'a>(path: &'a Path, path_style: PathStyle) -> Result<Cow<'a, Self>> {        let mut path = path.to_str().context("non utf-8 path")?;         let (prefixes, suffixes): (&[_], &[_]) = match path_style {            PathStyle::Posix => (&["./"], &['/']),            PathStyle::Windows => (&["./", ".\\"], &['/', '\\']),        };         while prefixes.iter().any(|prefix| path.starts_with(prefix)) {            path = &path[prefixes[0].len()..];        }        while let Some(prefix) = path.strip_suffix(suffixes)            && !prefix.is_empty()        {            path = prefix;        }         if is_absolute(&path, path_style) {            return Err(anyhow!("absolute path not allowed: {path:?}"));        }         let mut string = Cow::Borrowed(path);        if path_style == PathStyle::Windows && path.contains('\\') {            string = Cow::Owned(string.as_ref().replace('\\', "/"))        }         let mut result = match string {            Cow::Borrowed(string) => Cow::Borrowed(Self::new_unchecked(string)),            Cow::Owned(string) => Cow::Owned(RelPathBuf(string)),        };         if result            .components()            .any(|component| component == "" || component == "." || component == "..")        {            let mut normalized = RelPathBuf::new();            for component in result.components() {                match component {                    "" => {}                    "." => {}                    ".." => {                        if !normalized.pop() {                            return Err(anyhow!("path is not relative: {result:?}"));                        }                    }                    other => normalized.push(RelPath::new_unchecked(other)),                }            }            result = Cow::Owned(normalized)        }         Ok(result)    }     /// Converts a path that is already normalized and uses '/' separators    /// into a [`RelPath`] .    ///    /// Returns an error if the path is not already in the correct format.    #[track_caller]    pub fn unix<S: AsRef<Path> + ?Sized>(path: &S) -> anyhow::Result<&Self> {        let path = path.as_ref();        match Self::new(path, PathStyle::Posix)? {            Cow::Borrowed(path) => Ok(path),            Cow::Owned(_) => Err(anyhow!("invalid relative path {path:?}")),        }    }     fn new_unchecked(s: &str) -> &Self {        // Safety: `RelPath` is a transparent wrapper around `str`.        unsafe { &*(s as *const str as *const Self) }    }     pub fn is_empty(&self) -> bool {        self.0.is_empty()    }     pub fn components(&self) -> RelPathComponents<'_> {        RelPathComponents(&self.0)    }     pub fn ancestors(&self) -> RelPathAncestors<'_> {        RelPathAncestors(Some(&self.0))    }     pub fn file_name(&self) -> Option<&str> {        self.components().next_back()    }     pub fn file_stem(&self) -> Option<&str> {        Some(self.as_std_path().file_stem()?.to_str().unwrap())    }     pub fn extension(&self) -> Option<&str> {        Some(self.as_std_path().extension()?.to_str().unwrap())    }     pub fn parent(&self) -> Option<&Self> {        let mut components = self.components();        components.next_back()?;        Some(components.rest())    }     pub fn starts_with(&self, other: &Self) -> bool {        self.strip_prefix(other).is_ok()    }     pub fn ends_with(&self, other: &Self) -> bool {        if let Some(suffix) = self.0.strip_suffix(&other.0) {            if suffix.ends_with('/') {                return true;            } else if suffix.is_empty() {                return true;            }        }        false    }     pub fn strip_prefix<'a>(&'a self, other: &Self) -> Result<&'a Self> {        if other.is_empty() {            return Ok(self);        }        if let Some(suffix) = self.0.strip_prefix(&other.0) {            if let Some(suffix) = suffix.strip_prefix('/') {                return Ok(Self::new_unchecked(suffix));            } else if suffix.is_empty() {                return Ok(Self::empty());            }        }        Err(anyhow!("failed to strip prefix: {other:?} from {self:?}"))    }     pub fn len(&self) -> usize {        self.0.matches('/').count() + 1    }     pub fn last_n_components(&self, count: usize) -> Option<&Self> {        let len = self.len();        if len >= count {            let mut components = self.components();            for _ in 0..(len - count) {                components.next()?;            }            Some(components.rest())        } else {            None        }    }     pub fn join(&self, other: &Self) -> Arc<Self> {        let result = if self.0.is_empty() {            Cow::Borrowed(&other.0)        } else if other.0.is_empty() {            Cow::Borrowed(&self.0)        } else {            Cow::Owned(format!("{}/{}", &self.0, &other.0))        };        Arc::from(Self::new_unchecked(result.as_ref()))    }     pub fn to_rel_path_buf(&self) -> RelPathBuf {        RelPathBuf(self.0.to_string())    }     pub fn into_arc(&self) -> Arc<Self> {        Arc::from(self)    }     /// Convert the path into the wire representation.    pub fn to_proto(&self) -> String {        self.as_unix_str().to_owned()    }     /// Load the path from its wire representation.    pub fn from_proto(path: &str) -> Result<Arc<Self>> {        Ok(Arc::from(Self::unix(path)?))    }     /// Convert the path into a string with the given path style.    ///    /// Whenever a path is presented to the user, it should be converted to    /// a string via this method.    pub fn display(&self, style: PathStyle) -> Cow<'_, str> {        match style {            PathStyle::Posix => Cow::Borrowed(&self.0),            PathStyle::Windows => Cow::Owned(self.0.replace('/', "\\")),        }    }     /// Get the internal unix-style representation of the path.    ///    /// This should not be shown to the user.    pub fn as_unix_str(&self) -> &str {        &self.0    }     /// Interprets the path as a [`std::path::Path`], suitable for file system calls.    ///    /// This is guaranteed to be a valid path regardless of the host platform, because    /// the `/` is accepted as a path separator on windows.    ///    /// This should not be shown to the user.    pub fn as_std_path(&self) -> &Path {        Path::new(&self.0)    }} impl ToOwned for RelPath {    type Owned = RelPathBuf;     fn to_owned(&self) -> Self::Owned {        self.to_rel_path_buf()    }} impl Borrow<RelPath> for RelPathBuf {    fn borrow(&self) -> &RelPath {        self.as_rel_path()    }} impl PartialOrd for RelPath {    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {        Some(self.cmp(other))    }} impl Ord for RelPath {    fn cmp(&self, other: &Self) -> std::cmp::Ordering {        self.components().cmp(other.components())    }} impl fmt::Debug for RelPath {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        fmt::Debug::fmt(&self.0, f)    }} impl fmt::Debug for RelPathBuf {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        fmt::Debug::fmt(&self.0, f)    }} impl RelPathBuf {    pub fn new() -> Self {        Self(String::new())    }     pub fn pop(&mut self) -> bool {        if let Some(ix) = self.0.rfind('/') {            self.0.truncate(ix);            true        } else if !self.is_empty() {            self.0.clear();            true        } else {            false        }    }     pub fn push(&mut self, path: &RelPath) {        if !self.is_empty() {            self.0.push('/');        }        self.0.push_str(&path.0);    }     pub fn as_rel_path(&self) -> &RelPath {        RelPath::new_unchecked(self.0.as_str())    }     pub fn set_extension(&mut self, extension: &str) -> bool {        if let Some(filename) = self.file_name() {            let mut filename = PathBuf::from(filename);            filename.set_extension(extension);            self.pop();            self.0.push_str(filename.to_str().unwrap());            true        } else {            false        }    }} impl Into<Arc<RelPath>> for RelPathBuf {    fn into(self) -> Arc<RelPath> {        Arc::from(self.as_rel_path())    }} impl AsRef<RelPath> for RelPathBuf {    fn as_ref(&self) -> &RelPath {        self.as_rel_path()    }} impl Deref for RelPathBuf {    type Target = RelPath;     fn deref(&self) -> &Self::Target {        self.as_ref()    }} impl<'a> From<&'a RelPath> for Cow<'a, RelPath> {    fn from(value: &'a RelPath) -> Self {        Self::Borrowed(value)    }} impl From<&RelPath> for Arc<RelPath> {    fn from(rel_path: &RelPath) -> Self {        let bytes: Arc<str> = Arc::from(&rel_path.0);        unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }    }} #[cfg(any(test, feature = "test-support"))]#[track_caller]pub fn rel_path(path: &str) -> &RelPath {    RelPath::unix(path).unwrap()} impl PartialEq<str> for RelPath {    fn eq(&self, other: &str) -> bool {        self.0 == *other    }} pub struct RelPathComponents<'a>(&'a str); pub struct RelPathAncestors<'a>(Option<&'a str>); const SEPARATOR: char = '/'; impl<'a> RelPathComponents<'a> {    pub fn rest(&self) -> &'a RelPath {        RelPath::new_unchecked(self.0)    }} impl<'a> Iterator for RelPathComponents<'a> {    type Item = &'a str;     fn next(&mut self) -> Option<Self::Item> {        if let Some(sep_ix) = self.0.find(SEPARATOR) {            let (head, tail) = self.0.split_at(sep_ix);            self.0 = &tail[1..];            Some(head)        } else if self.0.is_empty() {            None        } else {            let result = self.0;            self.0 = "";            Some(result)        }    }} impl<'a> Iterator for RelPathAncestors<'a> {    type Item = &'a RelPath;     fn next(&mut self) -> Option<Self::Item> {        let result = self.0?;        if let Some(sep_ix) = result.rfind(SEPARATOR) {            self.0 = Some(&result[..sep_ix]);        } else if !result.is_empty() {            self.0 = Some("");        } else {            self.0 = None;        }        Some(RelPath::new_unchecked(result))    }} impl<'a> DoubleEndedIterator for RelPathComponents<'a> {    fn next_back(&mut self) -> Option<Self::Item> {        if let Some(sep_ix) = self.0.rfind(SEPARATOR) {            let (head, tail) = self.0.split_at(sep_ix);            self.0 = head;            Some(&tail[1..])        } else if self.0.is_empty() {            None        } else {            let result = self.0;            self.0 = "";            Some(result)        }    }} #[cfg(test)]mod tests {    use super::*;    use itertools::Itertools;    use pretty_assertions::assert_matches;     #[test]    fn test_rel_path_new() {        assert!(RelPath::new(Path::new("/"), PathStyle::local()).is_err());        assert!(RelPath::new(Path::new("//"), PathStyle::local()).is_err());        assert!(RelPath::new(Path::new("/foo/"), PathStyle::local()).is_err());         let path = RelPath::new("foo/".as_ref(), PathStyle::local()).unwrap();        assert_eq!(path, rel_path("foo").into());        assert_matches!(path, Cow::Borrowed(_));         let path = RelPath::new("foo\\".as_ref(), PathStyle::Windows).unwrap();        assert_eq!(path, rel_path("foo").into());        assert_matches!(path, Cow::Borrowed(_));         assert_eq!(            RelPath::new("foo/bar/../baz/./quux/".as_ref(), PathStyle::local())                .unwrap()                .as_ref(),            rel_path("foo/baz/quux")        );         let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Posix).unwrap();        assert_eq!(path.as_ref(), rel_path("foo/bar"));        assert_matches!(path, Cow::Borrowed(_));         let path = RelPath::new(".\\foo".as_ref(), PathStyle::Windows).unwrap();        assert_eq!(path, rel_path("foo").into());        assert_matches!(path, Cow::Borrowed(_));         let path = RelPath::new("./.\\./foo/\\/".as_ref(), PathStyle::Windows).unwrap();        assert_eq!(path, rel_path("foo").into());        assert_matches!(path, Cow::Borrowed(_));         let path = RelPath::new("foo/./bar".as_ref(), PathStyle::Posix).unwrap();        assert_eq!(path.as_ref(), rel_path("foo/bar"));        assert_matches!(path, Cow::Owned(_));         let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Windows).unwrap();        assert_eq!(path.as_ref(), rel_path("foo/bar"));        assert_matches!(path, Cow::Borrowed(_));         let path = RelPath::new(".\\foo\\bar".as_ref(), PathStyle::Windows).unwrap();        assert_eq!(path.as_ref(), rel_path("foo/bar"));        assert_matches!(path, Cow::Owned(_));    }     #[test]    fn test_rel_path_components() {        let path = rel_path("foo/bar/baz");        assert_eq!(            path.components().collect::<Vec<_>>(),            vec!["foo", "bar", "baz"]        );        assert_eq!(            path.components().rev().collect::<Vec<_>>(),            vec!["baz", "bar", "foo"]

Lines 1–500 of 579.