rel_path.rs

  1use crate::paths::{PathStyle, is_absolute};
  2use anyhow::{Context as _, Result, anyhow, bail};
  3use serde::{Deserialize, Serialize};
  4use std::{
  5    borrow::Cow,
  6    ffi::OsStr,
  7    fmt,
  8    ops::Deref,
  9    path::{Path, PathBuf},
 10    sync::Arc,
 11};
 12
 13#[repr(transparent)]
 14#[derive(PartialEq, Eq, Hash, Serialize)]
 15pub struct RelPath(str);
 16
 17#[derive(Clone, Serialize, Deserialize)]
 18pub struct RelPathBuf(String);
 19
 20impl RelPath {
 21    pub fn empty() -> &'static Self {
 22        unsafe { Self::new_unchecked("") }
 23    }
 24
 25    #[track_caller]
 26    pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> anyhow::Result<&Self> {
 27        let this = unsafe { Self::new_unchecked(s) };
 28        if this.0.starts_with("/")
 29            || this.0.ends_with("/")
 30            || this
 31                .components()
 32                .any(|component| component == ".." || component == "." || component.is_empty())
 33        {
 34            bail!("invalid relative path: {:?}", &this.0);
 35        }
 36        Ok(this)
 37    }
 38
 39    #[track_caller]
 40    pub fn from_std_path(path: &Path, path_style: PathStyle) -> Result<Arc<Self>> {
 41        let path = path.to_str().context("non utf-8 path")?;
 42        let mut string = Cow::Borrowed(path);
 43
 44        if is_absolute(&string, path_style) {
 45            return Err(anyhow!("absolute path not allowed: {path:?}"));
 46        }
 47
 48        if path_style == PathStyle::Windows {
 49            string = Cow::Owned(string.as_ref().replace('\\', "/"))
 50        }
 51
 52        let mut this = RelPathBuf::new();
 53        for component in unsafe { Self::new_unchecked(string.as_ref()) }.components() {
 54            match component {
 55                "" => {}
 56                "." => {}
 57                ".." => {
 58                    if !this.pop() {
 59                        return Err(anyhow!("path is not relative: {string:?}"));
 60                    }
 61                }
 62                other => this.push(RelPath::new(other)?),
 63            }
 64        }
 65
 66        Ok(this.into())
 67    }
 68
 69    pub unsafe fn new_unchecked<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
 70        unsafe { &*(s.as_ref() as *const str as *const Self) }
 71    }
 72
 73    pub fn is_empty(&self) -> bool {
 74        self.0.is_empty()
 75    }
 76
 77    pub fn components(&self) -> RelPathComponents<'_> {
 78        RelPathComponents(&self.0)
 79    }
 80
 81    pub fn ancestors(&self) -> RelPathAncestors<'_> {
 82        RelPathAncestors(Some(&self.0))
 83    }
 84
 85    pub fn file_name(&self) -> Option<&str> {
 86        self.components().next_back()
 87    }
 88
 89    pub fn file_stem(&self) -> Option<&str> {
 90        Some(self.as_std_path().file_stem()?.to_str().unwrap())
 91    }
 92
 93    pub fn extension(&self) -> Option<&str> {
 94        Some(self.as_std_path().extension()?.to_str().unwrap())
 95    }
 96
 97    pub fn parent(&self) -> Option<&Self> {
 98        let mut components = self.components();
 99        components.next_back()?;
100        Some(components.rest())
101    }
102
103    pub fn starts_with(&self, other: &Self) -> bool {
104        self.strip_prefix(other).is_ok()
105    }
106
107    pub fn ends_with(&self, other: &Self) -> bool {
108        if let Some(suffix) = self.0.strip_suffix(&other.0) {
109            if suffix.ends_with('/') {
110                return true;
111            } else if suffix.is_empty() {
112                return true;
113            }
114        }
115        false
116    }
117
118    pub fn strip_prefix(&self, other: &Self) -> Result<&Self> {
119        if other.is_empty() {
120            return Ok(self);
121        }
122        if let Some(suffix) = self.0.strip_prefix(&other.0) {
123            if let Some(suffix) = suffix.strip_prefix('/') {
124                return Ok(unsafe { Self::new_unchecked(suffix) });
125            } else if suffix.is_empty() {
126                return Ok(Self::empty());
127            }
128        }
129        Err(anyhow!("failed to strip prefix: {other:?} from {self:?}"))
130    }
131
132    pub fn len(&self) -> usize {
133        self.0.matches('/').count() + 1
134    }
135
136    pub fn last_n_components(&self, count: usize) -> Option<&Self> {
137        let len = self.len();
138        if len >= count {
139            let mut components = self.components();
140            for _ in 0..(len - count) {
141                components.next()?;
142            }
143            Some(components.rest())
144        } else {
145            None
146        }
147    }
148
149    pub fn push(&self, component: &str) -> Result<Arc<Self>> {
150        if component.is_empty() {
151            bail!("pushed component is empty");
152        } else if component.contains('/') {
153            bail!("pushed component contains a separator: {component:?}");
154        }
155        let path = format!(
156            "{}{}{}",
157            &self.0,
158            if self.is_empty() { "" } else { "/" },
159            component
160        );
161        Ok(Arc::from(unsafe { Self::new_unchecked(&path) }))
162    }
163
164    pub fn join(&self, other: &Self) -> Arc<Self> {
165        let result = if self.0.is_empty() {
166            Cow::Borrowed(&other.0)
167        } else if other.0.is_empty() {
168            Cow::Borrowed(&self.0)
169        } else {
170            Cow::Owned(format!("{}/{}", &self.0, &other.0))
171        };
172        Arc::from(unsafe { Self::new_unchecked(result.as_ref()) })
173    }
174
175    pub fn to_proto(&self) -> String {
176        self.0.to_owned()
177    }
178
179    pub fn to_rel_path_buf(&self) -> RelPathBuf {
180        RelPathBuf(self.0.to_string())
181    }
182
183    pub fn from_proto(path: &str) -> Result<Arc<Self>> {
184        Ok(Arc::from(Self::new(path)?))
185    }
186
187    pub fn as_str(&self) -> &str {
188        &self.0
189    }
190
191    pub fn display(&self, style: PathStyle) -> Cow<'_, str> {
192        match style {
193            PathStyle::Posix => Cow::Borrowed(&self.0),
194            PathStyle::Windows => Cow::Owned(self.0.replace('/', "\\")),
195        }
196    }
197
198    pub fn as_bytes(&self) -> &[u8] {
199        &self.0.as_bytes()
200    }
201
202    pub fn as_os_str(&self) -> &OsStr {
203        self.0.as_ref()
204    }
205
206    pub fn as_std_path(&self) -> &Path {
207        Path::new(&self.0)
208    }
209}
210
211impl PartialOrd for RelPath {
212    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
213        Some(self.cmp(other))
214    }
215}
216
217impl Ord for RelPath {
218    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
219        self.components().cmp(other.components())
220    }
221}
222
223impl fmt::Debug for RelPath {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        fmt::Debug::fmt(&self.0, f)
226    }
227}
228
229impl fmt::Debug for RelPathBuf {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        fmt::Debug::fmt(&self.0, f)
232    }
233}
234
235impl RelPathBuf {
236    pub fn new() -> Self {
237        Self(String::new())
238    }
239
240    pub fn pop(&mut self) -> bool {
241        if let Some(ix) = self.0.rfind('/') {
242            self.0.truncate(ix);
243            true
244        } else if !self.is_empty() {
245            self.0.clear();
246            true
247        } else {
248            false
249        }
250    }
251
252    pub fn push(&mut self, path: &RelPath) {
253        if !self.is_empty() {
254            self.0.push('/');
255        }
256        self.0.push_str(&path.0);
257    }
258
259    pub fn as_rel_path(&self) -> &RelPath {
260        unsafe { RelPath::new_unchecked(self.0.as_str()) }
261    }
262
263    pub fn set_extension(&mut self, extension: &str) -> bool {
264        if let Some(filename) = self.file_name() {
265            let mut filename = PathBuf::from(filename);
266            filename.set_extension(extension);
267            self.pop();
268            self.0.push_str(filename.to_str().unwrap());
269            true
270        } else {
271            false
272        }
273    }
274}
275
276impl Into<Arc<RelPath>> for RelPathBuf {
277    fn into(self) -> Arc<RelPath> {
278        Arc::from(self.as_rel_path())
279    }
280}
281
282impl AsRef<RelPath> for RelPathBuf {
283    fn as_ref(&self) -> &RelPath {
284        self.as_rel_path()
285    }
286}
287
288impl Deref for RelPathBuf {
289    type Target = RelPath;
290
291    fn deref(&self) -> &Self::Target {
292        self.as_ref()
293    }
294}
295
296impl AsRef<Path> for RelPath {
297    fn as_ref(&self) -> &Path {
298        Path::new(&self.0)
299    }
300}
301
302impl From<&RelPath> for Arc<RelPath> {
303    fn from(rel_path: &RelPath) -> Self {
304        let bytes: Arc<str> = Arc::from(&rel_path.0);
305        unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
306    }
307}
308
309#[cfg(any(test, feature = "test-support"))]
310#[track_caller]
311pub fn rel_path(path: &str) -> &RelPath {
312    RelPath::new(path).unwrap()
313}
314
315impl PartialEq<str> for RelPath {
316    fn eq(&self, other: &str) -> bool {
317        self.0 == *other
318    }
319}
320
321pub struct RelPathComponents<'a>(&'a str);
322
323pub struct RelPathAncestors<'a>(Option<&'a str>);
324
325const SEPARATOR: char = '/';
326
327impl<'a> RelPathComponents<'a> {
328    pub fn rest(&self) -> &'a RelPath {
329        unsafe { RelPath::new_unchecked(self.0) }
330    }
331}
332
333impl<'a> Iterator for RelPathComponents<'a> {
334    type Item = &'a str;
335
336    fn next(&mut self) -> Option<Self::Item> {
337        if let Some(sep_ix) = self.0.find(SEPARATOR) {
338            let (head, tail) = self.0.split_at(sep_ix);
339            self.0 = &tail[1..];
340            Some(head)
341        } else if self.0.is_empty() {
342            None
343        } else {
344            let result = self.0;
345            self.0 = "";
346            Some(result)
347        }
348    }
349}
350
351impl<'a> Iterator for RelPathAncestors<'a> {
352    type Item = &'a RelPath;
353
354    fn next(&mut self) -> Option<Self::Item> {
355        let result = self.0?;
356        if let Some(sep_ix) = result.rfind(SEPARATOR) {
357            self.0 = Some(&result[..sep_ix]);
358        } else if !result.is_empty() {
359            self.0 = Some("");
360        } else {
361            self.0 = None;
362        }
363        Some(unsafe { RelPath::new_unchecked(result) })
364    }
365}
366
367impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
368    fn next_back(&mut self) -> Option<Self::Item> {
369        if let Some(sep_ix) = self.0.rfind(SEPARATOR) {
370            let (head, tail) = self.0.split_at(sep_ix);
371            self.0 = head;
372            Some(&tail[1..])
373        } else if self.0.is_empty() {
374            None
375        } else {
376            let result = self.0;
377            self.0 = "";
378            Some(result)
379        }
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386    use itertools::Itertools;
387    use std::path::PathBuf;
388
389    #[test]
390    fn test_path_construction() {
391        assert!(RelPath::new("/").is_err());
392        assert!(RelPath::new("/foo").is_err());
393        assert!(RelPath::new("foo/").is_err());
394        assert!(RelPath::new("foo//bar").is_err());
395        assert!(RelPath::new("foo/../bar").is_err());
396        assert!(RelPath::new("./foo/bar").is_err());
397        assert!(RelPath::new("..").is_err());
398
399        assert!(RelPath::from_std_path(Path::new("/"), PathStyle::local()).is_err());
400        assert!(RelPath::from_std_path(Path::new("//"), PathStyle::local()).is_err());
401        assert!(RelPath::from_std_path(Path::new("/foo/"), PathStyle::local()).is_err());
402        assert_eq!(
403            RelPath::from_std_path(&PathBuf::from_iter(["foo", ""]), PathStyle::local()).unwrap(),
404            Arc::from(rel_path("foo"))
405        );
406    }
407
408    #[test]
409    fn test_rel_path_from_std_path() {
410        assert_eq!(
411            RelPath::from_std_path(Path::new("foo/bar/../baz/./quux/"), PathStyle::local())
412                .unwrap()
413                .as_ref(),
414            rel_path("foo/baz/quux")
415        );
416    }
417
418    #[test]
419    fn test_rel_path_components() {
420        let path = rel_path("foo/bar/baz");
421        assert_eq!(
422            path.components().collect::<Vec<_>>(),
423            vec!["foo", "bar", "baz"]
424        );
425        assert_eq!(
426            path.components().rev().collect::<Vec<_>>(),
427            vec!["baz", "bar", "foo"]
428        );
429
430        let path = rel_path("");
431        let mut components = path.components();
432        assert_eq!(components.next(), None);
433    }
434
435    #[test]
436    fn test_rel_path_ancestors() {
437        let path = rel_path("foo/bar/baz");
438        let mut ancestors = path.ancestors();
439        assert_eq!(ancestors.next(), Some(rel_path("foo/bar/baz")));
440        assert_eq!(ancestors.next(), Some(rel_path("foo/bar")));
441        assert_eq!(ancestors.next(), Some(rel_path("foo")));
442        assert_eq!(ancestors.next(), Some(rel_path("")));
443        assert_eq!(ancestors.next(), None);
444
445        let path = rel_path("foo");
446        let mut ancestors = path.ancestors();
447        assert_eq!(ancestors.next(), Some(rel_path("foo")));
448        assert_eq!(ancestors.next(), Some(RelPath::empty()));
449        assert_eq!(ancestors.next(), None);
450
451        let path = RelPath::empty();
452        let mut ancestors = path.ancestors();
453        assert_eq!(ancestors.next(), Some(RelPath::empty()));
454        assert_eq!(ancestors.next(), None);
455    }
456
457    #[test]
458    fn test_rel_path_parent() {
459        assert_eq!(
460            rel_path("foo/bar/baz").parent(),
461            Some(RelPath::new("foo/bar").unwrap())
462        );
463        assert_eq!(rel_path("foo").parent(), Some(RelPath::empty()));
464        assert_eq!(rel_path("").parent(), None);
465    }
466
467    #[test]
468    fn test_rel_path_partial_ord_is_compatible_with_std() {
469        let test_cases = ["a/b/c", "relative/path/with/dot.", "relative/path/with.dot"];
470        for [lhs, rhs] in test_cases.iter().array_combinations::<2>() {
471            assert_eq!(
472                Path::new(lhs).cmp(Path::new(rhs)),
473                RelPath::new(lhs).unwrap().cmp(RelPath::new(rhs).unwrap())
474            );
475        }
476    }
477
478    #[test]
479    fn test_strip_prefix() {
480        let parent = rel_path("");
481        let child = rel_path(".foo");
482
483        assert!(child.starts_with(parent));
484        assert_eq!(child.strip_prefix(parent).unwrap(), child);
485    }
486
487    #[test]
488    fn test_rel_path_constructors_absolute_path() {
489        assert!(RelPath::from_std_path(Path::new("/a/b"), PathStyle::Windows).is_err());
490        assert!(RelPath::from_std_path(Path::new("\\a\\b"), PathStyle::Windows).is_err());
491        assert!(RelPath::from_std_path(Path::new("/a/b"), PathStyle::Posix).is_err());
492        assert!(RelPath::from_std_path(Path::new("C:/a/b"), PathStyle::Windows).is_err());
493        assert!(RelPath::from_std_path(Path::new("C:\\a\\b"), PathStyle::Windows).is_err());
494        assert!(RelPath::from_std_path(Path::new("C:/a/b"), PathStyle::Posix).is_ok());
495    }
496
497    #[test]
498    fn test_push() {
499        assert_eq!(rel_path("a/b").push("c").unwrap().as_str(), "a/b/c");
500        assert_eq!(rel_path("").push("c").unwrap().as_str(), "c");
501        assert!(rel_path("a/b").push("").is_err());
502        assert!(rel_path("a/b").push("c/d").is_err());
503    }
504
505    #[test]
506    fn test_pop() {
507        let mut path = rel_path("a/b").to_rel_path_buf();
508        path.pop();
509        assert_eq!(path.as_rel_path().as_str(), "a");
510        path.pop();
511        assert_eq!(path.as_rel_path().as_str(), "");
512        path.pop();
513        assert_eq!(path.as_rel_path().as_str(), "");
514    }
515}