rel_path.rs

  1use std::{
  2    borrow::Cow,
  3    ffi::OsStr,
  4    os::unix::ffi::OsStrExt,
  5    path::{Path, PathBuf},
  6    sync::Arc,
  7};
  8
  9use anyhow::{Result, bail};
 10
 11#[repr(transparent)]
 12#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
 13pub struct RelPath([u8]);
 14
 15impl RelPath {
 16    pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
 17        unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
 18    }
 19
 20    pub fn components(&self) -> RelPathComponents {
 21        RelPathComponents(&self.0)
 22    }
 23
 24    pub fn file_name(&self) -> Option<&[u8]> {
 25        self.components().next_back()
 26    }
 27
 28    pub fn parent(&self) -> Option<&Self> {
 29        let mut components = self.components();
 30        components.next_back()?;
 31        Some(Self::new(components.0))
 32    }
 33
 34    pub fn starts_with(&self, other: &Self) -> bool {
 35        let mut components = self.components();
 36        other.components().all(|other_component| {
 37            components
 38                .next()
 39                .map_or(false, |component| component == other_component)
 40        })
 41    }
 42
 43    pub fn strip_prefix(&self, other: &Self) -> Result<&Self, ()> {
 44        let mut components = self.components();
 45        other
 46            .components()
 47            .all(|other_component| {
 48                components
 49                    .next()
 50                    .map_or(false, |component| component == other_component)
 51            })
 52            .then(|| Self::new(components.0))
 53            .ok_or_else(|| ())
 54    }
 55
 56    pub fn from_path(relative_path: &Path) -> Result<&Self> {
 57        use std::path::Component;
 58        match relative_path.components().next() {
 59            Some(Component::Prefix(_)) => bail!(
 60                "path `{}` should be relative, not a windows prefix",
 61                relative_path.to_string_lossy()
 62            ),
 63            Some(Component::RootDir) => {
 64                bail!(
 65                    "path `{}` should be relative",
 66                    relative_path.to_string_lossy()
 67                )
 68            }
 69            Some(Component::CurDir) => {
 70                bail!(
 71                    "path `{}` should not start with `.`",
 72                    relative_path.to_string_lossy()
 73                )
 74            }
 75            Some(Component::ParentDir) => {
 76                bail!(
 77                    "path `{}` should not start with `..`",
 78                    relative_path.to_string_lossy()
 79                )
 80            }
 81            None => bail!("relative path should not be empty"),
 82            _ => Ok(Self::new(relative_path.as_os_str().as_bytes())),
 83        }
 84    }
 85
 86    pub fn append_to_abs_path(&self, abs_path: &Path) -> PathBuf {
 87        // TODO: implement this differently
 88        let mut result = abs_path.to_path_buf();
 89        for component in self.components() {
 90            result.push(String::from_utf8_lossy(component).as_ref());
 91        }
 92        result
 93    }
 94
 95    pub fn to_proto(&self) -> String {
 96        String::from_utf8_lossy(&self.0).to_string()
 97    }
 98
 99    pub fn as_bytes(&self) -> &[u8] {
100        &self.0
101    }
102
103    pub fn as_os_str(&self) -> Cow<'_, OsStr> {
104        #[cfg(target_os = "windows")]
105        {
106            use std::ffi::OsString;
107            let path = String::from_utf8_lossy(&self.0);
108            match path {
109                Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
110                Cow::Owned(s) => Cow::Owned(OsString::from(s)),
111            }
112        }
113        #[cfg(not(target_os = "windows"))]
114        {
115            use std::os::unix::ffi::OsStrExt;
116
117            Cow::Borrowed(OsStr::from_bytes(&self.0))
118        }
119    }
120}
121
122impl From<&RelPath> for Arc<RelPath> {
123    fn from(rel_path: &RelPath) -> Self {
124        let bytes: Arc<[u8]> = Arc::from(&rel_path.0);
125        unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
126    }
127}
128
129impl AsRef<RelPath> for &str {
130    fn as_ref(&self) -> &RelPath {
131        RelPath::new(self)
132    }
133}
134
135impl std::fmt::Debug for RelPath {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        if let Ok(str) = std::str::from_utf8(&self.0) {
138            write!(f, "RelPath({})", str)
139        } else {
140            write!(f, "RelPath({:?})", &self.0)
141        }
142    }
143}
144
145pub struct RelPathComponents<'a>(&'a [u8]);
146
147const SEPARATOR: u8 = b'/';
148
149impl<'a> Iterator for RelPathComponents<'a> {
150    type Item = &'a [u8];
151
152    fn next(&mut self) -> Option<Self::Item> {
153        if let Some(sep_ix) = self.0.iter().position(|&byte| byte == SEPARATOR) {
154            let (head, tail) = self.0.split_at(sep_ix);
155            self.0 = &tail[1..];
156            Some(head)
157        } else if self.0.is_empty() {
158            None
159        } else {
160            let result = self.0;
161            self.0 = &[];
162            Some(result)
163        }
164    }
165}
166
167impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
168    fn next_back(&mut self) -> Option<Self::Item> {
169        if let Some(sep_ix) = self.0.iter().rposition(|&byte| byte == SEPARATOR) {
170            let (head, tail) = self.0.split_at(sep_ix);
171            self.0 = head;
172            Some(&tail[1..])
173        } else if self.0.is_empty() {
174            None
175        } else {
176            let result = self.0;
177            self.0 = &[];
178            Some(result)
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_rel_path_components() {
189        let path = RelPath::new("foo/bar/baz");
190        let mut components = path.components();
191        assert_eq!(components.next(), Some("foo".as_bytes()));
192        assert_eq!(components.next(), Some("bar".as_bytes()));
193        assert_eq!(components.next(), Some("baz".as_bytes()));
194        assert_eq!(components.next(), None);
195    }
196
197    #[test]
198    fn test_rel_path_parent() {
199        assert_eq!(
200            RelPath::new("foo/bar/baz").parent().unwrap(),
201            RelPath::new("foo/bar")
202        );
203        assert_eq!(RelPath::new("foo").parent().unwrap(), RelPath::new(""));
204        assert_eq!(RelPath::new("").parent(), None);
205    }
206}