Skip to main content

path/
rel_path.rs

1use anyhow::{Context as _, Result, anyhow};
2use std::{
3    borrow::{Borrow, Cow},
4    fmt,
5    ops::Deref,
6    path::{Path, PathBuf},
7    sync::Arc,
8};
9
10use crate::{
11    PathStyle,
12    abs_path::{AbsPath, AbsPathBuf},
13};
14
15/// A file system path that is guaranteed to be relative and normalized.
16///
17/// This type can be used to represent paths in a uniform way, regardless of
18/// whether they refer to Windows or POSIX file systems, and regardless of
19/// the host platform.
20///
21/// Internally, paths are stored in POSIX ('/'-delimited) format, but they can
22/// be displayed in either POSIX or Windows format.
23///
24/// Relative paths are also guaranteed to be valid unicode.
25#[repr(transparent)]
26#[derive(PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28pub struct RelPath(str);
29
30/// An owned representation of a file system path that is guaranteed to be
31/// relative and normalized.
32///
33/// This type is to [`RelPath`] as [`std::path::PathBuf`] is to [`std::path::Path`]
34#[derive(PartialEq, Eq, Clone, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub struct RelPathBuf(String);
37
38impl RelPath {
39    /// Creates an empty [`RelPath`].
40    pub fn empty() -> &'static Self {
41        Self::from_str("")
42    }
43
44    /// Creates an empty [`RelPath`].
45    pub fn empty_arc() -> Arc<Self> {
46        static EMPTY: std::sync::OnceLock<Arc<RelPath>> = std::sync::OnceLock::new();
47        EMPTY.get_or_init(|| Arc::from(Self::empty())).clone()
48    }
49
50    /// Converts a path with a given style into a [`RelPath`].
51    ///
52    /// Returns an error if the path is absolute, or is not valid unicode.
53    ///
54    /// This method will normalize the path by removing `.` components,
55    /// processing `..` components, and removing trailing separators. It does
56    /// not allocate unless it's necessary to reformat the path.
57    #[track_caller]
58    pub fn new<'a>(path: &'a Path, path_style: PathStyle) -> Result<Cow<'a, Self>> {
59        let mut path = path.to_str().context("non utf-8 path")?;
60
61        let (prefixes, suffixes): (&[_], &[_]) = match path_style {
62            PathStyle::Unix => (&["./"], &['/']),
63            PathStyle::Windows => (&["./", ".\\"], &['/', '\\']),
64        };
65
66        while prefixes.iter().any(|prefix| path.starts_with(prefix)) {
67            path = &path[prefixes[0].len()..];
68        }
69        while let Some(prefix) = path.strip_suffix(suffixes)
70            && !prefix.is_empty()
71        {
72            path = prefix;
73        }
74
75        if path_style.has_root(Path::new(&path)) {
76            return Err(anyhow!("absolute path not allowed: {path:?}"));
77        }
78
79        let mut string = Cow::Borrowed(path);
80        if path_style == PathStyle::Windows && path.contains('\\') {
81            string = Cow::Owned(string.as_ref().replace('\\', "/"))
82        }
83
84        let mut result = match string {
85            Cow::Borrowed(string) => Cow::Borrowed(Self::from_str(string)),
86            Cow::Owned(string) => Cow::Owned(RelPathBuf(string)),
87        };
88
89        if result
90            .components()
91            .any(|component| component == "" || component == "." || component == "..")
92        {
93            let mut normalized = RelPathBuf::new();
94            for component in result.components() {
95                match component {
96                    "" => {}
97                    "." => {}
98                    ".." => {
99                        if !normalized.pop() {
100                            return Err(anyhow!("path is not relative: {result:?}"));
101                        }
102                    }
103                    other => normalized.push(RelPath::from_str(other)),
104                }
105            }
106            result = Cow::Owned(normalized)
107        }
108
109        Ok(result)
110    }
111
112    #[track_caller]
113    pub fn new_test<'a>(path: &'a str) -> Cow<'a, Self> {
114        Self::new(Path::new(path), PathStyle::Unix).unwrap()
115    }
116
117    /// Converts a path that is already normalized and uses '/' separators
118    /// into a [`RelPath`] .
119    ///
120    /// Returns an error if the path is not already in the correct format.
121    #[track_caller]
122    pub fn from_unix_str<S: AsRef<Path> + ?Sized>(path: &S) -> anyhow::Result<&Self> {
123        let path = path.as_ref();
124        match Self::new(path, PathStyle::Unix)? {
125            Cow::Borrowed(path) => Ok(path),
126            Cow::Owned(_) => Err(anyhow!("invalid relative path {path:?}")),
127        }
128    }
129
130    fn from_str(s: &str) -> &Self {
131        // Safety: `RelPath` is a transparent wrapper around `str`.
132        unsafe { &*(s as *const str as *const Self) }
133    }
134
135    pub fn is_empty(&self) -> bool {
136        self.0.is_empty()
137    }
138
139    pub fn components(&self) -> RelPathComponents<'_> {
140        RelPathComponents(&self.0)
141    }
142
143    pub fn ancestors(&self) -> RelPathAncestors<'_> {
144        RelPathAncestors {
145            full: &self.0,
146            front: self.0.len(),
147            back: 0,
148            done: false,
149        }
150    }
151
152    pub fn file_name(&self) -> Option<&str> {
153        self.components().next_back()
154    }
155
156    pub fn file_stem(&self) -> Option<&str> {
157        Some(self.as_std_path().file_stem()?.to_str().unwrap())
158    }
159
160    pub fn extension(&self) -> Option<&str> {
161        Some(self.as_std_path().extension()?.to_str().unwrap())
162    }
163
164    pub fn parent(&self) -> Option<&Self> {
165        let mut components = self.components();
166        components.next_back()?;
167        Some(components.rest())
168    }
169
170    pub fn starts_with(&self, other: &Self) -> bool {
171        self.strip_prefix(other).is_ok()
172    }
173
174    /// Returns true if this path is a strict descendant of `ancestor`.
175    ///
176    /// Unlike `starts_with`, this returns false when `self == ancestor`
177    /// and false when `ancestor` is empty (since every path trivially
178    /// starts with the empty prefix).
179    pub fn is_descendant_of(&self, ancestor: &Self) -> bool {
180        if ancestor.is_empty() || self == ancestor {
181            return false;
182        }
183        self.starts_with(ancestor)
184    }
185
186    pub fn ends_with(&self, other: &Self) -> bool {
187        if other.is_empty() {
188            return true;
189        }
190        if let Some(suffix) = self.0.strip_suffix(&other.0) {
191            if suffix.ends_with('/') {
192                return true;
193            } else if suffix.is_empty() {
194                return true;
195            }
196        }
197        false
198    }
199
200    pub fn strip_prefix<'a>(&'a self, other: &Self) -> Result<&'a Self, StripPrefixError> {
201        if other.is_empty() {
202            return Ok(self);
203        }
204        if let Some(suffix) = self.0.strip_prefix(&other.0) {
205            if let Some(suffix) = suffix.strip_prefix('/') {
206                return Ok(Self::from_str(suffix));
207            } else if suffix.is_empty() {
208                return Ok(Self::empty());
209            }
210        }
211        Err(StripPrefixError)
212    }
213
214    pub fn len(&self) -> usize {
215        if self.0.is_empty() {
216            0
217        } else {
218            self.0.matches('/').count() + 1
219        }
220    }
221
222    pub fn last_n_components(&self, count: usize) -> Option<&Self> {
223        let len = self.len();
224        if len >= count {
225            let mut components = self.components();
226            for _ in 0..(len - count) {
227                components.next()?;
228            }
229            Some(components.rest())
230        } else {
231            None
232        }
233    }
234
235    pub fn join(&self, other: &Self) -> RelPathBuf {
236        if self.0.is_empty() {
237            other.to_rel_path_buf()
238        } else if other.0.is_empty() {
239            self.to_rel_path_buf()
240        } else {
241            RelPathBuf(format!("{}/{}", &self.0, &other.0))
242        }
243    }
244
245    pub fn to_rel_path_buf(&self) -> RelPathBuf {
246        RelPathBuf(self.0.to_string())
247    }
248
249    pub fn into_arc(&self) -> Arc<Self> {
250        Arc::from(self)
251    }
252
253    /// Convert the path into a string with the given path style.
254    ///
255    /// Whenever a path is presented to the user, it should be converted to
256    /// a string via this method.
257    pub fn display(&self, style: PathStyle) -> Cow<'_, str> {
258        match style {
259            PathStyle::Unix => Cow::Borrowed(&self.0),
260            PathStyle::Windows if self.0.contains('/') => Cow::Owned(self.0.replace('/', "\\")),
261            PathStyle::Windows => Cow::Borrowed(&self.0),
262        }
263    }
264
265    /// Get the internal unix-style representation of the path.
266    ///
267    /// This should not be shown to the user.
268    pub fn as_unix_str(&self) -> &str {
269        &self.0
270    }
271
272    /// Interprets the path as a [`std::path::Path`], suitable for file system calls.
273    ///
274    /// This is guaranteed to be a valid path regardless of the host platform, because
275    /// the `/` is accepted as a path separator on windows.
276    ///
277    /// This should not be shown to the user.
278    pub fn as_std_path(&self) -> &Path {
279        Path::new(&self.0)
280    }
281
282    /// Resolves this relative path against an absolute base path.
283    pub fn absolutize(&self, base: impl AsRef<AbsPath>) -> AbsPathBuf {
284        base.as_ref().join(self.as_unix_str())
285    }
286}
287
288#[derive(Debug)]
289pub struct StripPrefixError;
290
291impl ToOwned for RelPath {
292    type Owned = RelPathBuf;
293
294    fn to_owned(&self) -> Self::Owned {
295        self.to_rel_path_buf()
296    }
297}
298
299impl Borrow<RelPath> for RelPathBuf {
300    fn borrow(&self) -> &RelPath {
301        self.as_rel_path()
302    }
303}
304
305impl PartialOrd for RelPath {
306    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
307        Some(self.cmp(other))
308    }
309}
310
311impl Ord for RelPath {
312    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
313        self.components().cmp(other.components())
314    }
315}
316
317impl fmt::Debug for RelPath {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        fmt::Debug::fmt(&self.0, f)
320    }
321}
322
323impl fmt::Debug for RelPathBuf {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        fmt::Debug::fmt(&self.0, f)
326    }
327}
328
329impl RelPathBuf {
330    pub fn new() -> Self {
331        Self(String::new())
332    }
333
334    pub fn pop(&mut self) -> bool {
335        if let Some(ix) = self.0.rfind('/') {
336            self.0.truncate(ix);
337            true
338        } else if !self.is_empty() {
339            self.0.clear();
340            true
341        } else {
342            false
343        }
344    }
345
346    pub fn push(&mut self, path: &RelPath) {
347        if path.is_empty() {
348            return;
349        }
350        if !self.is_empty() {
351            self.0.push('/');
352        }
353        self.0.push_str(&path.0);
354    }
355
356    pub fn push_component(&mut self, component: &str) -> Result<()> {
357        anyhow::ensure!(
358            !component.is_empty()
359                && !component.contains('/')
360                && component != "."
361                && component != "..",
362            "invalid relative path component: {component:?}"
363        );
364
365        if !self.is_empty() {
366            self.0.push('/');
367        }
368        self.0.push_str(component);
369        Ok(())
370    }
371
372    pub fn as_rel_path(&self) -> &RelPath {
373        RelPath::from_str(self.0.as_str())
374    }
375
376    pub fn set_extension(&mut self, extension: &str) -> bool {
377        if let Some(filename) = self.file_name() {
378            let mut filename = PathBuf::from(filename);
379            filename.set_extension(extension);
380            self.pop();
381            self.push(RelPath::from_str(filename.to_str().unwrap()));
382            true
383        } else {
384            false
385        }
386    }
387}
388
389impl PartialOrd for RelPathBuf {
390    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
391        Some(self.cmp(other))
392    }
393}
394
395impl Ord for RelPathBuf {
396    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
397        self.as_rel_path().cmp(other.as_rel_path())
398    }
399}
400
401impl From<RelPathBuf> for Arc<RelPath> {
402    fn from(value: RelPathBuf) -> Self {
403        Arc::from(value.as_rel_path())
404    }
405}
406
407impl AsRef<RelPath> for RelPathBuf {
408    fn as_ref(&self) -> &RelPath {
409        self.as_rel_path()
410    }
411}
412
413impl AsRef<std::path::Path> for RelPathBuf {
414    fn as_ref(&self) -> &Path {
415        self.as_std_path()
416    }
417}
418
419impl AsRef<RelPath> for RelPath {
420    fn as_ref(&self) -> &RelPath {
421        self
422    }
423}
424
425impl AsRef<std::path::Path> for RelPath {
426    fn as_ref(&self) -> &Path {
427        self.as_std_path()
428    }
429}
430
431impl Deref for RelPathBuf {
432    type Target = RelPath;
433
434    fn deref(&self) -> &Self::Target {
435        self.as_ref()
436    }
437}
438
439impl<'a> From<&'a RelPath> for Cow<'a, RelPath> {
440    fn from(value: &'a RelPath) -> Self {
441        Self::Borrowed(value)
442    }
443}
444
445impl From<&RelPath> for Arc<RelPath> {
446    fn from(rel_path: &RelPath) -> Self {
447        let bytes: Arc<str> = Arc::from(&rel_path.0);
448        // SAFETY: `AbsPath` is a `repr(transparent)` wrapper around `Path`.
449        unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
450    }
451}
452
453impl<'a> TryFrom<&'a str> for &'a RelPath {
454    type Error = anyhow::Error;
455
456    fn try_from(s: &'a str) -> Result<Self> {
457        RelPath::from_unix_str(s)
458    }
459}
460
461impl TryFrom<&str> for RelPathBuf {
462    type Error = anyhow::Error;
463
464    fn try_from(s: &str) -> Result<Self> {
465        RelPath::new(Path::new(s), PathStyle::Unix).map(|cow| cow.into_owned())
466    }
467}
468
469#[cfg(any(test, feature = "test-support"))]
470#[track_caller]
471pub fn rel_path(path: &str) -> &RelPath {
472    RelPath::from_unix_str(path).unwrap()
473}
474
475#[cfg(any(test, feature = "test-support"))]
476#[track_caller]
477pub fn rel_path_buf(path: &str) -> RelPathBuf {
478    rel_path(path).to_owned()
479}
480
481impl PartialEq<str> for RelPath {
482    fn eq(&self, other: &str) -> bool {
483        self.0 == *other
484    }
485}
486
487impl PartialEq<RelPath> for RelPathBuf {
488    fn eq(&self, other: &RelPath) -> bool {
489        self.as_rel_path() == other
490    }
491}
492
493impl PartialEq<RelPathBuf> for RelPath {
494    fn eq(&self, other: &RelPathBuf) -> bool {
495        other.as_rel_path() == self
496    }
497}
498
499impl fmt::Display for RelPath {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        f.write_str(&self.0)
502    }
503}
504
505impl fmt::Display for RelPathBuf {
506    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507        f.write_str(&self.0)
508    }
509}
510
511#[derive(Default)]
512pub struct RelPathComponents<'a>(&'a str);
513
514pub struct RelPathAncestors<'a> {
515    full: &'a str,
516    front: usize,
517    back: usize,
518    done: bool,
519}
520
521const SEPARATOR: char = '/';
522
523impl<'a> RelPathComponents<'a> {
524    pub fn rest(&self) -> &'a RelPath {
525        RelPath::from_str(self.0)
526    }
527}
528
529impl<'a> Iterator for RelPathComponents<'a> {
530    type Item = &'a str;
531
532    fn next(&mut self) -> Option<Self::Item> {
533        if let Some(sep_ix) = self.0.find(SEPARATOR) {
534            let (head, tail) = self.0.split_at(sep_ix);
535            self.0 = &tail[1..];
536            Some(head)
537        } else if self.0.is_empty() {
538            None
539        } else {
540            let result = self.0;
541            self.0 = "";
542            Some(result)
543        }
544    }
545}
546
547impl<'a> Iterator for RelPathAncestors<'a> {
548    type Item = &'a RelPath;
549
550    fn next(&mut self) -> Option<Self::Item> {
551        if self.done {
552            return None;
553        }
554        let result = &self.full[..self.front];
555        if self.front == self.back {
556            self.done = true;
557        } else {
558            self.front = result.rfind(SEPARATOR).unwrap_or_default();
559        }
560        Some(RelPath::from_str(result))
561    }
562}
563
564impl<'a> DoubleEndedIterator for RelPathAncestors<'a> {
565    fn next_back(&mut self) -> Option<Self::Item> {
566        if self.done {
567            return None;
568        }
569        let result = &self.full[..self.back];
570        if self.front == self.back {
571            self.done = true;
572        } else {
573            let search_start = if self.back == 0 { 0 } else { self.back + 1 };
574            self.back = match self.full[search_start..].find(SEPARATOR) {
575                Some(sep_ix) => search_start + sep_ix,
576                None => self.full.len(),
577            };
578        }
579        Some(RelPath::from_str(result))
580    }
581}
582
583impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
584    fn next_back(&mut self) -> Option<Self::Item> {
585        if let Some(sep_ix) = self.0.rfind(SEPARATOR) {
586            let (head, tail) = self.0.split_at(sep_ix);
587            self.0 = head;
588            Some(&tail[1..])
589        } else if self.0.is_empty() {
590            None
591        } else {
592            let result = self.0;
593            self.0 = "";
594            Some(result)
595        }
596    }
597}
598
599#[cfg(feature = "serde")]
600impl<'de> serde::Deserialize<'de> for RelPathBuf {
601    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
602    where
603        D: serde::Deserializer<'de>,
604    {
605        let path = String::deserialize(deserializer)?;
606        let rel_path =
607            RelPath::new(Path::new(&path), PathStyle::local()).map_err(serde::de::Error::custom)?;
608        Ok(rel_path.into_owned())
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615
616    #[test]
617    fn test_rel_path_new() {
618        assert!(RelPath::new(Path::new("/"), PathStyle::local()).is_err());
619        assert!(RelPath::new(Path::new("//"), PathStyle::local()).is_err());
620        assert!(RelPath::new(Path::new("/foo/"), PathStyle::local()).is_err());
621
622        let path = RelPath::new("foo/".as_ref(), PathStyle::local()).unwrap();
623        assert_eq!(path, rel_path("foo").into());
624        assert!(matches!(path, Cow::Borrowed(_)));
625
626        let path = RelPath::new("foo\\".as_ref(), PathStyle::Windows).unwrap();
627        assert_eq!(path, rel_path("foo").into());
628        assert!(matches!(path, Cow::Borrowed(_)));
629
630        assert_eq!(
631            RelPath::new("foo/bar/../baz/./quux/".as_ref(), PathStyle::local())
632                .unwrap()
633                .as_ref(),
634            rel_path("foo/baz/quux")
635        );
636
637        let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Unix).unwrap();
638        assert_eq!(path.as_ref(), rel_path("foo/bar"));
639        assert!(matches!(path, Cow::Borrowed(_)));
640
641        let path = RelPath::new(".\\foo".as_ref(), PathStyle::Windows).unwrap();
642        assert_eq!(path, rel_path("foo").into());
643        assert!(matches!(path, Cow::Borrowed(_)));
644
645        let path = RelPath::new("./.\\./foo/\\/".as_ref(), PathStyle::Windows).unwrap();
646        assert_eq!(path, rel_path("foo").into());
647        assert!(matches!(path, Cow::Borrowed(_)));
648
649        let path = RelPath::new("foo/./bar".as_ref(), PathStyle::Unix).unwrap();
650        assert_eq!(path.as_ref(), rel_path("foo/bar"));
651        assert!(matches!(path, Cow::Owned(_)));
652
653        let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Windows).unwrap();
654        assert_eq!(path.as_ref(), rel_path("foo/bar"));
655        assert!(matches!(path, Cow::Borrowed(_)));
656
657        let path = RelPath::new(".\\foo\\bar".as_ref(), PathStyle::Windows).unwrap();
658        assert_eq!(path.as_ref(), rel_path("foo/bar"));
659        assert!(matches!(path, Cow::Owned(_)));
660    }
661
662    #[test]
663    fn test_rel_path_components() {
664        let path = rel_path("foo/bar/baz");
665        assert_eq!(
666            path.components().collect::<Vec<_>>(),
667            vec!["foo", "bar", "baz"]
668        );
669        assert_eq!(
670            path.components().rev().collect::<Vec<_>>(),
671            vec!["baz", "bar", "foo"]
672        );
673
674        let path = rel_path("");
675        let mut components = path.components();
676        assert_eq!(components.next(), None);
677    }
678
679    #[test]
680    fn test_rel_path_ancestors() {
681        let path = rel_path("foo/bar/baz");
682        let mut ancestors = path.ancestors();
683        assert_eq!(ancestors.next(), Some(rel_path("foo/bar/baz")));
684        assert_eq!(ancestors.next(), Some(rel_path("foo/bar")));
685        assert_eq!(ancestors.next(), Some(rel_path("foo")));
686        assert_eq!(ancestors.next(), Some(rel_path("")));
687        assert_eq!(ancestors.next(), None);
688
689        let path = rel_path("foo");
690        let mut ancestors = path.ancestors();
691        assert_eq!(ancestors.next(), Some(rel_path("foo")));
692        assert_eq!(ancestors.next(), Some(RelPath::empty()));
693        assert_eq!(ancestors.next(), None);
694
695        let path = RelPath::empty();
696        let mut ancestors = path.ancestors();
697        assert_eq!(ancestors.next(), Some(RelPath::empty()));
698        assert_eq!(ancestors.next(), None);
699
700        let path = rel_path("foo/bar/baz");
701        let mut ancestors = path.ancestors();
702        assert_eq!(ancestors.next_back(), Some(rel_path("")));
703        assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
704        assert_eq!(ancestors.next_back(), Some(rel_path("foo/bar")));
705        assert_eq!(ancestors.next_back(), Some(rel_path("foo/bar/baz")));
706        assert_eq!(ancestors.next_back(), None);
707
708        let path = rel_path("foo/bar/baz");
709        let mut ancestors = path.ancestors();
710        assert_eq!(ancestors.next(), Some(rel_path("foo/bar/baz")));
711        assert_eq!(ancestors.next_back(), Some(rel_path("")));
712        assert_eq!(ancestors.next(), Some(rel_path("foo/bar")));
713        assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
714        assert_eq!(ancestors.next(), None);
715        assert_eq!(ancestors.next_back(), None);
716
717        let path = rel_path("foo");
718        let mut ancestors = path.ancestors();
719        assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
720        assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
721        assert_eq!(ancestors.next_back(), None);
722
723        let path = RelPath::empty();
724        let mut ancestors = path.ancestors();
725        assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
726        assert_eq!(ancestors.next_back(), None);
727
728        let path = rel_path("über/x");
729        let mut ancestors = path.ancestors();
730        assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
731        assert_eq!(ancestors.next_back(), Some(rel_path("über")));
732        assert_eq!(ancestors.next_back(), Some(rel_path("über/x")));
733        assert_eq!(ancestors.next_back(), None);
734    }
735
736    #[test]
737    fn test_rel_path_parent() {
738        assert_eq!(rel_path("foo/bar/baz").parent(), Some(rel_path("foo/bar")));
739        assert_eq!(rel_path("foo").parent(), Some(RelPath::empty()));
740        assert_eq!(rel_path("").parent(), None);
741    }
742
743    #[test]
744    fn test_rel_path_partial_ord_is_compatible_with_std() {
745        let test_cases = ["a/b/c", "relative/path/with/dot.", "relative/path/with.dot"];
746        for (i, lhs) in test_cases.iter().enumerate() {
747            for rhs in &test_cases[i + 1..] {
748                assert_eq!(
749                    Path::new(lhs).cmp(Path::new(rhs)),
750                    RelPath::from_unix_str(lhs)
751                        .unwrap()
752                        .cmp(RelPath::from_unix_str(rhs).unwrap()),
753                    "ordering mismatch for {:?} vs {:?}",
754                    lhs,
755                    rhs,
756                );
757            }
758        }
759    }
760
761    #[test]
762    fn test_strip_prefix() {
763        let parent = rel_path("");
764        let child = rel_path(".foo");
765
766        assert!(child.starts_with(parent));
767        assert_eq!(child.strip_prefix(parent).unwrap(), child);
768    }
769
770    #[test]
771    fn test_ends_with() {
772        assert!(rel_path("foo/bar").ends_with(rel_path("bar")));
773        assert!(rel_path("foo/bar").ends_with(rel_path("foo/bar")));
774        assert!(rel_path("foo/bar").ends_with(RelPath::empty()));
775        assert!(RelPath::empty().ends_with(RelPath::empty()));
776        assert!(!rel_path("foobar").ends_with(rel_path("bar")));
777    }
778
779    #[test]
780    fn test_rel_path_constructors_absolute_path() {
781        assert!(RelPath::new(Path::new("/a/b"), PathStyle::Windows).is_err());
782        assert!(RelPath::new(Path::new("\\a\\b"), PathStyle::Windows).is_err());
783        assert!(RelPath::new(Path::new("/a/b"), PathStyle::Unix).is_err());
784        assert!(RelPath::new(Path::new("C:/a/b"), PathStyle::Windows).is_err());
785        assert!(RelPath::new(Path::new("C:\\a\\b"), PathStyle::Windows).is_err());
786        assert!(RelPath::new(Path::new("C:/a/b"), PathStyle::Unix).is_ok());
787    }
788
789    #[test]
790    fn test_pop() {
791        let mut path = rel_path_buf("a/b");
792        path.pop();
793        assert_eq!(path.as_rel_path().as_unix_str(), "a");
794        path.pop();
795        assert_eq!(path.as_rel_path().as_unix_str(), "");
796        path.pop();
797        assert_eq!(path.as_rel_path().as_unix_str(), "");
798    }
799
800    #[test]
801    fn test_len() {
802        assert_eq!(RelPath::empty().len(), 0);
803        assert_eq!(rel_path("a").len(), 1);
804        assert_eq!(rel_path("a/b").len(), 2);
805        assert_eq!(rel_path("a/b/c").len(), 3);
806    }
807
808    #[test]
809    fn test_set_extension() {
810        let mut path = rel_path_buf("a/b/c.txt");
811        assert!(path.set_extension("rs"));
812        assert_eq!(path.as_rel_path().as_unix_str(), "a/b/c.rs");
813
814        let mut single = rel_path_buf("file.txt");
815        assert!(single.set_extension("md"));
816        assert_eq!(single.as_rel_path().as_unix_str(), "file.md");
817
818        let mut no_ext = rel_path_buf("a/b/c");
819        assert!(no_ext.set_extension("rs"));
820        assert_eq!(no_ext.as_rel_path().as_unix_str(), "a/b/c.rs");
821    }
822}