littlefs2_core/
path.rs

1//! Paths
2
3use core::{
4    cmp::Ordering,
5    convert::TryFrom,
6    ffi::{c_char, CStr},
7    fmt,
8    iter::FusedIterator,
9    ops, ptr, slice, str,
10};
11
12use crate::path;
13
14/// A path
15///
16/// Paths must be null terminated ASCII strings with at most [`PathBuf::MAX_SIZE`][] bytes (not
17/// including the trailing null).
18// Invariants:
19// 1. inner.to_bytes().is_ascii()
20// 2. inner.to_bytes().len() <= PathBuf::MAX_SIZE
21#[derive(PartialEq, Eq)]
22#[repr(transparent)]
23pub struct Path {
24    inner: CStr,
25}
26
27impl Path {
28    /// Checks two paths for equality.
29    ///
30    /// This provides an easy way to check paths in a const context.
31    ///
32    /// # Example
33    ///
34    /// ```
35    /// # use littlefs2_core::{Path, path};
36    /// const fn check(path: &Path) -> bool {
37    ///     !path.const_eq(path!("forbidden-path"))
38    /// }
39    ///
40    /// assert!(check(path!("allowed-path")));
41    /// assert!(!check(path!("forbidden-path")));
42    /// ```
43    pub const fn const_eq(&self, path: &Path) -> bool {
44        let a = self.inner.to_bytes();
45        let b = path.inner.to_bytes();
46
47        if a.len() != b.len() {
48            return false;
49        }
50
51        let mut i = 0;
52        while i < a.len() {
53            if a[i] != b[i] {
54                return false;
55            }
56            i += 1;
57        }
58
59        true
60    }
61
62    /// Compare the path using their string representation
63    /// This comarison function as would be expected for a `String` type.
64    ///
65    /// <div class="warning">
66    ///   This ordering does not match the ordering obsvered when iterating over a directory.
67    ///
68    ///   See <a href="#method.cmp_lfs">cmp_lfs</a> and <a href = "https://github.com/littlefs-project/littlefs/issues/923">littlefs#923</a>.
69    /// </div>
70    ///
71    /// ```
72    ///# use std::cmp::Ordering;
73    ///# use littlefs2_core::path;
74    /// assert_eq!(path!("some_path_a").cmp_str(path!("some_path_b")), Ordering::Less);
75    /// assert_eq!(path!("some_path_b").cmp_str(path!("some_path_a")), Ordering::Greater);
76    /// assert_eq!(path!("some_path").cmp_str(path!("some_path_a")), Ordering::Less);
77    /// assert_eq!(path!("some_path").cmp_str(path!("some_path_b")), Ordering::Less);
78    /// assert_eq!(path!("some_path").cmp_str(path!("some_path")), Ordering::Equal);
79    ///```
80    pub fn cmp_str(&self, other: &Path) -> Ordering {
81        self.inner.cmp(&other.inner)
82    }
83
84    /// Compare the path using their string representation
85    ///
86    /// This comparison function matches the iteration order of `littlefs` when iterating over directory.
87    /// For more information, see [littlefs#923](https://github.com/littlefs-project/littlefs/issues/923)
88    ///
89    /// ```
90    ///# use std::cmp::Ordering;
91    ///# use littlefs2_core::path;
92    /// assert_eq!(path!("some_path_a").cmp_lfs(path!("some_path_b")), Ordering::Less);
93    /// assert_eq!(path!("some_path_b").cmp_lfs(path!("some_path_a")), Ordering::Greater);
94    /// assert_eq!(path!("some_path").cmp_lfs(path!("some_path_a")), Ordering::Greater);
95    /// assert_eq!(path!("some_path").cmp_lfs(path!("some_path_b")), Ordering::Greater);
96    /// assert_eq!(path!("some_path_a").cmp_lfs(path!("some_path")), Ordering::Less);
97    /// assert_eq!(path!("some_path_b").cmp_lfs(path!("some_path")), Ordering::Less);
98    /// assert_eq!(path!("some_path").cmp_lfs(path!("some_path")), Ordering::Equal);
99    ///```
100    pub fn cmp_lfs(&self, other: &Path) -> Ordering {
101        let this = self.inner.to_bytes();
102        let other = other.inner.to_bytes();
103
104        let min_len = this.len().min(other.len());
105
106        match this[0..min_len].cmp(&other[0..min_len]) {
107            // if they have a clear ordering, return this ordering
108            Ordering::Less => Ordering::Less,
109            // if they have a clear ordering, return this ordering
110            Ordering::Greater => Ordering::Greater,
111            // If one is a prefix of the other, the longest on is the first
112            Ordering::Equal => other.len().cmp(&this.len()),
113        }
114    }
115}
116
117/// Iterator over the ancestors of a Path
118///
119/// See documentation for [`Path::ancestors`][]
120pub struct Ancestors<'a> {
121    path: &'a str,
122}
123
124impl Iterator for Ancestors<'_> {
125    type Item = PathBuf;
126    fn next(&mut self) -> Option<PathBuf> {
127        if self.path.is_empty() {
128            return None;
129        } else if self.path == "/" {
130            self.path = "";
131            return Some(path!("/").into());
132        }
133
134        let item = self.path;
135
136        let Some((rem, item_name)) = self.path.rsplit_once('/') else {
137            self.path = "";
138            return item.try_into().ok();
139        };
140
141        if self.path.starts_with('/') && rem.is_empty() {
142            self.path = "/";
143        } else {
144            self.path = rem;
145        }
146
147        // Case of a path ending with a trailing `/`
148        if item_name.is_empty() {
149            self.next();
150        }
151        item.try_into().ok()
152    }
153}
154
155impl FusedIterator for Ancestors<'_> {}
156
157/// Iterator over the components of a Path
158///
159/// See documentation for [`Path::iter`][]
160pub struct Iter<'a> {
161    path: &'a str,
162}
163
164impl Iterator for Iter<'_> {
165    type Item = PathBuf;
166    fn next(&mut self) -> Option<PathBuf> {
167        if self.path.is_empty() {
168            return None;
169        }
170        if self.path.starts_with('/') {
171            self.path = &self.path[1..];
172            return Some(path!("/").into());
173        }
174
175        let Some((path, rem)) = self.path.split_once('/') else {
176            let ret_val = self.path.try_into().ok();
177            self.path = "";
178            return ret_val;
179        };
180
181        self.path = rem;
182        path.try_into().ok()
183    }
184}
185
186impl Path {
187    /// Return true if the path is empty
188    ///
189    /// ```rust
190    ///# use littlefs2_core::path;
191    ///
192    /// assert!(path!("").is_empty());
193    /// assert!(!path!("something").is_empty());
194    /// ```
195    pub const fn is_empty(&self) -> bool {
196        self.inner.to_bytes().is_empty()
197    }
198
199    /// Get the name of the file this path points to if it points to one
200    ///
201    /// ```
202    ///# use littlefs2_core::path;
203    /// let path = path!("/some/path/file.extension");
204    /// assert_eq!(path.file_name(), Some(path!("file.extension")));
205    ///
206    /// let path = path!("/");
207    /// assert_eq!(path.file_name(), None);
208    ///
209    /// let path = path!("");
210    /// assert_eq!(path.file_name(), None);
211    ///
212    /// let path = path!("/some/path/file.extension/");
213    /// assert_eq!(path.file_name(), None);
214    /// ```
215    pub fn file_name(&self) -> Option<&Path> {
216        if self.is_empty() {
217            return None;
218        }
219
220        let this = self.as_str_ref_with_trailing_nul();
221        match this.rsplit_once('/') {
222            None | Some((_, "\x00")) => None,
223            Some((_, path)) => {
224                debug_assert!(path.ends_with('\x00'));
225                unsafe {
226                    let cstr = CStr::from_bytes_with_nul_unchecked(path.as_bytes());
227                    Some(Path::from_cstr_unchecked(cstr))
228                }
229            }
230        }
231    }
232
233    /// Iterate over the ancestors of the path
234    ///
235    /// ```
236    ///# use littlefs2_core::path;
237    /// let path = path!("/some/path/file.extension");
238    /// let mut ancestors = path.ancestors();
239    /// assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/file.extension"));
240    /// assert_eq!(&*ancestors.next().unwrap(), path!("/some/path"));
241    /// assert_eq!(&*ancestors.next().unwrap(), path!("/some"));
242    /// assert_eq!(&*ancestors.next().unwrap(), path!("/"));
243    /// assert!(ancestors.next().is_none());
244    /// ```
245    pub fn ancestors(&self) -> Ancestors {
246        Ancestors {
247            path: self.as_str(),
248        }
249    }
250
251    /// Iterate over the components of the path
252    ///
253    /// ```
254    ///# use littlefs2_core::path;
255    /// let path = path!("/some/path/file.extension");
256    /// let mut iter = path.iter();
257    /// assert_eq!(&*iter.next().unwrap(), path!("/"));
258    /// assert_eq!(&*iter.next().unwrap(), path!("some"));
259    /// assert_eq!(&*iter.next().unwrap(), path!("path"));
260    /// assert_eq!(&*iter.next().unwrap(), path!("file.extension"));
261    /// assert!(iter.next().is_none());
262    /// ```
263    pub fn iter(&self) -> Iter {
264        Iter {
265            path: self.as_str(),
266        }
267    }
268
269    /// Creates a path from a string.
270    ///
271    /// The string must only consist of ASCII characters.  The last character must be null.  It
272    /// must contain at most [`PathBuf::MAX_SIZE`][] bytes, not including the trailing null.  If
273    /// these conditions are not met, this function returns an error.
274    pub const fn from_str_with_nul(s: &str) -> Result<&Self> {
275        Self::from_bytes_with_nul(s.as_bytes())
276    }
277
278    /// Creates a path from a byte buffer.
279    ///
280    /// The byte buffer must only consist of ASCII characters.  The last character must be null.
281    /// It must contain at most [`PathBuf::MAX_SIZE`][] bytes, not including the trailing null.  If
282    /// these conditions are not met, this function returns an error.
283    pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self> {
284        match CStr::from_bytes_with_nul(bytes) {
285            Ok(cstr) => Self::from_cstr(cstr),
286            Err(_) => Err(PathError::NotCStr),
287        }
288    }
289
290    /// Creates a path from a C string.
291    ///
292    /// The string must only consist of ASCII characters.  It must contain at most
293    /// [`PathBuf::MAX_SIZE`][] bytes, not including the trailing null.  If these conditions are
294    /// not met, this function returns an error.
295    // XXX should we reject empty paths (`""`) here?
296    pub const fn from_cstr(cstr: &CStr) -> Result<&Self> {
297        let bytes = cstr.to_bytes();
298        let n = cstr.to_bytes().len();
299        if n > PathBuf::MAX_SIZE {
300            Err(PathError::TooLarge)
301        } else if bytes.is_ascii() {
302            Ok(unsafe { Self::from_cstr_unchecked(cstr) })
303        } else {
304            Err(PathError::NotAscii)
305        }
306    }
307
308    /// Creates a path from a C string without checking the invariants.
309    ///
310    /// # Safety
311    /// The string must only consist of ASCII characters.  It must contain at most
312    /// [`PathBuf::MAX_SIZE`][] bytes, not including the trailing null.
313    pub const unsafe fn from_cstr_unchecked(cstr: &CStr) -> &Self {
314        &*(cstr as *const CStr as *const Path)
315    }
316
317    /// Returns the inner pointer to this C string.
318    pub const fn as_ptr(&self) -> *const c_char {
319        self.inner.as_ptr()
320    }
321
322    /// Creates an owned `PathBuf` with `path` adjoined to `self`.
323    pub fn join(&self, path: &Path) -> PathBuf {
324        let mut p = PathBuf::from(self);
325        p.push(path);
326        p
327    }
328
329    // helpful for debugging wither the trailing nul is indeed a trailing nul.
330    pub const fn as_str_ref_with_trailing_nul(&self) -> &str {
331        // SAFETY: ASCII is valid UTF-8
332        unsafe { str::from_utf8_unchecked(self.inner.to_bytes_with_nul()) }
333    }
334
335    pub const fn as_str(&self) -> &str {
336        // SAFETY: ASCII is valid UTF-8
337        unsafe { str::from_utf8_unchecked(self.inner.to_bytes()) }
338    }
339
340    pub fn parent(&self) -> Option<PathBuf> {
341        let rk_path_bytes = self.as_ref()[..].as_bytes();
342        match rk_path_bytes.iter().rposition(|x| *x == b'/') {
343            Some(0) if rk_path_bytes.len() != 1 => Some(path!("/").into()),
344            Some(slash_index) => {
345                // if we have a directory that ends with `/`,
346                // still need to "go up" one parent
347                if slash_index + 1 == rk_path_bytes.len() {
348                    PathBuf::try_from(&rk_path_bytes[..slash_index])
349                        .ok()?
350                        .parent()
351                } else {
352                    PathBuf::try_from(&rk_path_bytes[..slash_index]).ok()
353                }
354            }
355            None => None,
356        }
357    }
358}
359
360impl AsRef<str> for Path {
361    fn as_ref(&self) -> &str {
362        self.as_str()
363    }
364}
365
366impl fmt::Debug for Path {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        // helpful for debugging wither the trailing nul is indeed a trailing nul.
369        write!(f, "p{:?}", self.as_str_ref_with_trailing_nul())
370    }
371}
372
373impl fmt::Display for Path {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        f.write_str(self.as_ref())
376    }
377}
378
379impl<'b> TryFrom<&'b [u8]> for &'b Path {
380    type Error = PathError;
381
382    fn try_from(bytes: &[u8]) -> Result<&Path> {
383        Path::from_bytes_with_nul(bytes)
384    }
385}
386
387impl PartialEq<str> for Path {
388    fn eq(&self, rhs: &str) -> bool {
389        self.as_ref() == rhs
390    }
391}
392
393// without this you need to slice byte string literals (`b"foo\0"[..].try_into()`)
394macro_rules! array_impls {
395    ($($N:expr),+) => {
396        $(
397            impl<'b> TryFrom<&'b [u8; $N]> for &'b Path {
398                type Error = PathError;
399
400                fn try_from(bytes: &[u8; $N]) -> Result<&Path> {
401                    Path::from_bytes_with_nul(&bytes[..])
402                }
403            }
404
405            impl TryFrom<&[u8; $N]> for PathBuf {
406                type Error = PathError;
407
408                fn try_from(bytes: &[u8; $N]) -> Result<Self> {
409                    Self::try_from(&bytes[..])
410                }
411            }
412
413            impl PartialEq<[u8; $N]> for Path {
414                fn eq(&self, rhs: &[u8; $N]) -> bool {
415                    self.as_ref().as_bytes() == &rhs[..]
416                }
417            }
418
419        )+
420    }
421}
422
423array_impls!(
424    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
425    27, 28, 29, 30, 31, 32
426);
427
428/// An owned, mutable path
429///
430/// Paths must be null terminated ASCII strings with at most [`PathBuf::MAX_SIZE`][] bytes (not
431/// including the trailing null).
432// Invariants:
433// 1. 0 < len <= PathBuf::MAX_SIZE_PLUS_ONE
434// 2. buf[len - 1] == 0
435// 3. buf[i].is_ascii() for 0 <= i < len - 1
436#[derive(Clone)]
437pub struct PathBuf {
438    buf: [c_char; PathBuf::MAX_SIZE_PLUS_ONE],
439    // NOTE `len` DOES include the final null byte
440    len: usize,
441}
442
443/// # Safety
444/// `s` must point to valid memory; `s` will be treated as a null terminated string
445const unsafe fn strlen(mut s: *const c_char) -> usize {
446    let mut n = 0;
447    while *s != 0 {
448        s = s.add(1);
449        n += 1;
450    }
451    n
452}
453
454impl Default for PathBuf {
455    fn default() -> Self {
456        Self::new()
457    }
458}
459
460impl PathBuf {
461    pub const MAX_SIZE: usize = 255;
462    pub const MAX_SIZE_PLUS_ONE: usize = Self::MAX_SIZE + 1;
463
464    pub const fn new() -> Self {
465        Self {
466            buf: [0; Self::MAX_SIZE_PLUS_ONE],
467            len: 1,
468        }
469    }
470
471    /// Creates a `PathBuf` from a `Path`.
472    ///
473    /// This method is a const-friendly version of the `From<&Path>` implementation.  If you don’t
474    /// need a const method, prefer `From<&Path>` as it is more idiomatic and more efficient.
475    ///
476    /// The [`path_buf`][`crate::path_buf`] macro can be used instead to construct a `PathBuf` from
477    /// a string literal.
478    ///
479    /// # Example
480    ///
481    /// ```        
482    /// # use littlefs2_core::{path, path_buf, PathBuf};
483    /// const PATH: PathBuf = PathBuf::from_path(path!("test"));
484    /// assert_eq!(PATH, path_buf!("test"));
485    /// ```
486    pub const fn from_path(path: &Path) -> Self {
487        let bytes = path.inner.to_bytes();
488
489        let mut buf = [0; Self::MAX_SIZE_PLUS_ONE];
490        let len = bytes.len();
491        assert!(len < Self::MAX_SIZE_PLUS_ONE);
492
493        let mut i = 0;
494        while i < len {
495            buf[i] = bytes[i] as _;
496            i += 1;
497        }
498
499        Self { buf, len: len + 1 }
500    }
501
502    pub const fn as_path(&self) -> &Path {
503        unsafe {
504            let bytes = slice::from_raw_parts(self.buf.as_ptr().cast(), self.len);
505            let cstr = CStr::from_bytes_with_nul_unchecked(bytes);
506            Path::from_cstr_unchecked(cstr)
507        }
508    }
509
510    pub const fn as_str(&self) -> &str {
511        self.as_path().as_str()
512    }
513
514    pub fn clear(&mut self) {
515        self.buf = [0; Self::MAX_SIZE_PLUS_ONE];
516        self.len = 1;
517    }
518
519    /// Creates a from a raw buffer containing a null-terminated ASCII string.
520    ///
521    /// # Safety
522    ///
523    /// The buffer must contain only ASCII characters and at least one null byte.
524    pub const unsafe fn from_buffer_unchecked(buf: [c_char; Self::MAX_SIZE_PLUS_ONE]) -> Self {
525        let len = strlen(buf.as_ptr()) + 1 /* null byte */;
526        PathBuf { buf, len }
527    }
528
529    /// Extends `self` with `path`
530    pub fn push(&mut self, path: &Path) {
531        match path.as_ref() {
532            // no-operation
533            "" => return,
534
535            // `self` becomes `/` (root), to match `std::Path` implementation
536            // NOTE(allow) cast is necessary on some architectures (e.g. x86)
537            #[allow(clippy::unnecessary_cast)]
538            "/" => {
539                self.buf[0] = b'/' as c_char;
540                self.buf[1] = 0;
541                self.len = 2;
542                return;
543            }
544            _ => {}
545        }
546
547        let src = path.as_ref().as_bytes();
548        let needs_separator = self
549            .as_ref()
550            .as_bytes()
551            .last()
552            .map(|byte| *byte != b'/')
553            .unwrap_or(false);
554        let slen = src.len();
555        assert!(
556            self.len
557                + slen
558                + if needs_separator {
559                    // b'/'
560                    1
561                } else {
562                    0
563                }
564                <= Self::MAX_SIZE_PLUS_ONE
565        );
566
567        let len = self.len;
568        unsafe {
569            let mut p = self.buf.as_mut_ptr().cast::<u8>().add(len - 1);
570            if needs_separator {
571                p.write(b'/');
572                p = p.add(1);
573                self.len += 1;
574            }
575            ptr::copy_nonoverlapping(src.as_ptr(), p, slen);
576            p.add(slen).write(0); // null byte
577            self.len += slen;
578        }
579    }
580}
581
582impl From<&Path> for PathBuf {
583    #[inline(never)]
584    fn from(path: &Path) -> Self {
585        let bytes = path.as_ref().as_bytes();
586
587        let mut buf = [0; Self::MAX_SIZE_PLUS_ONE];
588        let len = bytes.len();
589        assert!(len <= Self::MAX_SIZE_PLUS_ONE);
590        unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr().cast(), len + 1) }
591        Self { buf, len: len + 1 }
592    }
593}
594
595/// Accepts byte strings, with or without trailing nul.
596impl TryFrom<&[u8]> for PathBuf {
597    type Error = PathError;
598
599    fn try_from(bytes: &[u8]) -> Result<Self> {
600        // NB: This needs to set the final NUL byte, unless it already has one
601        // It also checks that there are no inner NUL bytes
602        let bytes = if !bytes.is_empty() && bytes[bytes.len() - 1] == b'\0' {
603            &bytes[..bytes.len() - 1]
604        } else {
605            bytes
606        };
607        if bytes.len() > Self::MAX_SIZE {
608            return Err(PathError::TooLarge);
609        }
610        for byte in bytes {
611            if *byte == 0 {
612                return Err(PathError::NotCStr);
613            }
614            if !byte.is_ascii() {
615                return Err(PathError::NotAscii);
616            }
617        }
618
619        let mut buf = [0; Self::MAX_SIZE_PLUS_ONE];
620        let len = bytes.len();
621        unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr().cast(), len) }
622        Ok(Self { buf, len: len + 1 })
623    }
624}
625
626/// Accepts strings, with or without trailing nul.
627impl TryFrom<&str> for PathBuf {
628    type Error = PathError;
629
630    fn try_from(s: &str) -> Result<Self> {
631        PathBuf::try_from(s.as_bytes())
632    }
633}
634
635impl ops::Deref for PathBuf {
636    type Target = Path;
637
638    fn deref(&self) -> &Path {
639        self.as_path()
640    }
641}
642
643#[cfg(feature = "serde")]
644impl serde::Serialize for PathBuf {
645    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
646    where
647        S: serde::Serializer,
648    {
649        serializer.serialize_bytes(self.as_ref().as_bytes())
650    }
651}
652
653#[cfg(feature = "serde")]
654impl<'de> serde::Deserialize<'de> for PathBuf {
655    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
656    where
657        D: serde::Deserializer<'de>,
658    {
659        use core::marker::PhantomData;
660
661        struct ValueVisitor<'de>(PhantomData<&'de ()>);
662
663        impl<'de> serde::de::Visitor<'de> for ValueVisitor<'de> {
664            type Value = PathBuf;
665
666            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
667                formatter.write_str("a path buffer")
668            }
669
670            fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
671            where
672                E: serde::de::Error,
673            {
674                if v.len() > PathBuf::MAX_SIZE {
675                    return Err(E::invalid_length(v.len(), &self));
676                }
677                PathBuf::try_from(v).map_err(|_| E::custom("invalid path buffer"))
678            }
679        }
680
681        deserializer.deserialize_bytes(ValueVisitor(PhantomData))
682    }
683}
684
685impl fmt::Debug for PathBuf {
686    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687        <Path as fmt::Debug>::fmt(self, f)
688    }
689}
690
691impl fmt::Display for PathBuf {
692    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693        <Path as fmt::Display>::fmt(self, f)
694    }
695}
696
697impl core::cmp::PartialEq for PathBuf {
698    fn eq(&self, other: &Self) -> bool {
699        // from cstr_core
700        self.as_ref() == other.as_ref()
701
702        // // use cortex_m_semihosting::hprintln;
703        // // hprintln!("inside PathBuf PartialEq");
704        // // hprintln!("self.len {}, other.len {}", self.len, other.len).ok();
705        // // hprintln!("self..len {:?}, other..len {:?}", &self.buf[..self.len], &other.buf[..other.len]).ok();
706        // self.len == other.len && self.buf[..self.len - 1] == other.buf[..other.len - 1]
707    }
708}
709
710impl core::cmp::Eq for PathBuf {}
711
712// use core::cmp::Ordering;
713
714// impl Ord for PathBuf {
715//     fn cmp(&self, other: &Self) -> Ordering {
716//         self.len.cmp(&other.len)
717//     }
718// }
719
720// impl PartialOrd for PathBuf {
721//     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
722//         Some(self.cmp(other))
723//     }
724// }
725
726/// Errors that arise from converting byte buffers into paths
727#[derive(Clone, Copy, Debug)]
728pub enum PathError {
729    /// Byte buffer contains non-ASCII characters
730    NotAscii,
731    /// Byte buffer is not a C string
732    NotCStr,
733    /// Byte buffer is too long (longer than [`PathBuf::MAX_SIZE_PLUS_ONE`][])
734    TooLarge,
735}
736
737type Result<T> = core::result::Result<T, PathError>;
738
739#[cfg(test)]
740mod tests {
741    use super::{Path, PathBuf};
742    use crate::path;
743
744    const EMPTY: &Path = path!("");
745    const SLASH: &Path = path!("/");
746
747    #[test]
748    fn path_macro() {
749        assert_eq!(EMPTY, &*PathBuf::try_from("").unwrap());
750        assert_eq!(SLASH, &*PathBuf::try_from("/").unwrap());
751    }
752
753    // does not compile:
754    // const NON_ASCII: &Path = path!("über");
755    // const NULL: &Path = path!("ub\0er");
756
757    #[test]
758    fn nul_in_from_str_with_nul() {
759        assert!(Path::from_str_with_nul("ub\0er").is_err());
760    }
761
762    #[test]
763    fn non_ascii_in_from_str_with_nul() {
764        assert!(Path::from_str_with_nul("über").is_err());
765    }
766
767    #[test]
768    fn join() {
769        let empty = Path::from_bytes_with_nul(b"\0").unwrap();
770        let slash = Path::from_bytes_with_nul(b"/\0").unwrap();
771        let a = Path::from_bytes_with_nul(b"a\0").unwrap();
772        let b = Path::from_bytes_with_nul(b"b\0").unwrap();
773
774        assert_eq!(empty.join(empty).as_ref(), "");
775        assert_eq!(empty.join(slash).as_ref(), "/");
776        assert_eq!(empty.join(a).as_ref(), "a");
777        assert_eq!(empty.join(b).as_ref(), "b");
778
779        assert_eq!(slash.join(empty).as_ref(), "/");
780        assert_eq!(slash.join(slash).as_ref(), "/");
781        assert_eq!(slash.join(a).as_ref(), "/a");
782        assert_eq!(slash.join(b).as_ref(), "/b");
783
784        assert_eq!(a.join(empty).as_ref(), "a");
785        assert_eq!(a.join(slash).as_ref(), "/");
786        assert_eq!(a.join(a).as_ref(), "a/a");
787        assert_eq!(a.join(b).as_ref(), "a/b");
788
789        assert_eq!(b.join(empty).as_ref(), "b");
790        assert_eq!(b.join(slash).as_ref(), "/");
791        assert_eq!(b.join(a).as_ref(), "b/a");
792        assert_eq!(b.join(b).as_ref(), "b/b");
793    }
794
795    #[test]
796    fn nulls() {
797        assert!(Path::from_bytes_with_nul(b"abc\0def").is_err());
798    }
799
800    #[test]
801    fn trailing_nuls() {
802        assert_eq!(
803            PathBuf::try_from("abc").unwrap(),
804            PathBuf::try_from("abc\0").unwrap()
805        );
806    }
807
808    #[test]
809    fn ancestors() {
810        fn assert_ancestor_parent(path: &Path) {
811            let mut ancestors = path.ancestors();
812            if !path.as_str().is_empty() {
813                assert_eq!(&*ancestors.next().unwrap(), path);
814            }
815            let mut buf = PathBuf::from(path);
816            loop {
817                let parent = buf.parent();
818                assert_eq!(parent, ancestors.next());
819                match parent {
820                    Some(p) => buf = p,
821                    None => return,
822                }
823            }
824        }
825
826        let path = path!("/some/path/.././file.extension");
827        assert_ancestor_parent(path);
828        let mut ancestors = path.ancestors();
829        assert_eq!(
830            &*ancestors.next().unwrap(),
831            path!("/some/path/.././file.extension")
832        );
833        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/../."));
834        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/.."));
835        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path"));
836        assert_eq!(&*ancestors.next().unwrap(), path!("/some"));
837        assert_eq!(&*ancestors.next().unwrap(), path!("/"));
838        assert!(ancestors.next().is_none());
839
840        let path = path!("/some/path/.././file.extension/");
841        assert_ancestor_parent(path);
842        let mut ancestors = path.ancestors();
843        assert_eq!(
844            &*ancestors.next().unwrap(),
845            path!("/some/path/.././file.extension/")
846        );
847        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/../."));
848        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/.."));
849        assert_eq!(&*ancestors.next().unwrap(), path!("/some/path"));
850        assert_eq!(&*ancestors.next().unwrap(), path!("/some"));
851        assert_eq!(&*ancestors.next().unwrap(), path!("/"));
852        assert!(ancestors.next().is_none());
853
854        let path = path!("some/path/.././file.extension");
855        assert_ancestor_parent(path);
856        let mut ancestors = path.ancestors();
857        assert_eq!(
858            &*ancestors.next().unwrap(),
859            path!("some/path/.././file.extension")
860        );
861        assert_eq!(&*ancestors.next().unwrap(), path!("some/path/../."));
862        assert_eq!(&*ancestors.next().unwrap(), path!("some/path/.."));
863        assert_eq!(&*ancestors.next().unwrap(), path!("some/path"));
864        assert_eq!(&*ancestors.next().unwrap(), path!("some"));
865        assert!(ancestors.next().is_none());
866    }
867
868    #[test]
869    fn iter() {
870        let path = path!("/some/path/.././file.extension");
871        let mut ancestors = path.iter();
872        assert_eq!(&*ancestors.next().unwrap(), path!("/"));
873        assert_eq!(&*ancestors.next().unwrap(), path!("some"));
874        assert_eq!(&*ancestors.next().unwrap(), path!("path"));
875        assert_eq!(&*ancestors.next().unwrap(), path!(".."));
876        assert_eq!(&*ancestors.next().unwrap(), path!("."));
877        assert_eq!(&*ancestors.next().unwrap(), path!("file.extension"));
878        assert!(ancestors.next().is_none());
879        let path = path!("some/path/.././file.extension/");
880        let mut ancestors = path.iter();
881        assert_eq!(&*ancestors.next().unwrap(), path!("some"));
882        assert_eq!(&*ancestors.next().unwrap(), path!("path"));
883        assert_eq!(&*ancestors.next().unwrap(), path!(".."));
884        assert_eq!(&*ancestors.next().unwrap(), path!("."));
885        assert_eq!(&*ancestors.next().unwrap(), path!("file.extension"));
886        assert!(ancestors.next().is_none());
887    }
888
889    #[test]
890    fn file_name() {
891        let path = path!("/some/path/.././file.extension");
892        assert_eq!(path.file_name(), Some(path!("file.extension")));
893
894        let path = path!("/");
895        assert_eq!(path.file_name(), None);
896
897        let path = path!("");
898        assert_eq!(path.file_name(), None);
899
900        let path = path!("/some/path/.././file.extension/");
901        assert_eq!(path.file_name(), None);
902    }
903}