standard_lib/fs/path/
path.rs

1use std::borrow::Borrow;
2use std::cmp::Ordering;
3use std::ffi::{CString, OsStr, OsString};
4use std::fmt::{self, Debug, Formatter};
5use std::marker::PhantomData;
6use std::mem;
7use std::ops::Deref;
8use std::os::unix::ffi::{OsStrExt, OsStringExt};
9
10use super::{DisplayPath, Rel};
11use crate::collections::contiguous::Vector;
12use crate::fs::path::{Ancestors, Components};
13use crate::util::{self, sealed::Sealed};
14
15use derive_more::IsVariant;
16
17pub trait PathState: Sealed + Debug {}
18
19#[derive(Clone)]
20pub struct OwnedPath<State: PathState> {
21    pub(crate) _state: PhantomData<fn() -> State>,
22    pub(crate) inner: OsString,
23}
24
25#[repr(transparent)]
26pub struct Path<State: PathState> {
27    pub(crate) _state: PhantomData<fn() -> State>,
28    pub(crate) inner: OsStr,
29}
30
31impl<S: PathState> OwnedPath<S> {
32    pub(crate) fn from_os_str_sanitized(value: &OsStr) -> Self {
33        Self {
34            _state: PhantomData,
35            inner: sanitize_os_str(value),
36        }
37    }
38
39    pub const unsafe fn from_unchecked(inner: OsString) -> Self {
40        Self {
41            _state: PhantomData,
42            inner,
43        }
44    }
45
46    pub fn as_path(&self) -> &Path<S> {
47        self
48    }
49
50    pub fn push<P: AsRef<Path<Rel>>>(&mut self, other: P) {
51        let other_path = other.as_ref();
52        let mut vec: Vector<u8> = mem::take(&mut self.inner).into_vec().into();
53        vec.reserve(other_path.len());
54        vec.extend(other_path.inner.as_bytes().iter().cloned());
55        let _ = mem::replace(
56            &mut self.inner,
57            OsString::from_vec(vec.into())
58        );
59    }
60}
61
62impl<S: PathState> Path<S> {
63    pub unsafe fn from_unchecked<O: AsRef<OsStr> + ?Sized>(value: &O) -> &Self {
64        // SAFETY: Path<S> is `repr(transparent)`, so to it has the same layout as OsStr.
65        unsafe { &*(value.as_ref() as *const OsStr as *const Self) }
66    }
67
68    pub unsafe fn from_unchecked_mut<O: AsMut<OsStr> + ?Sized>(value: &mut O) -> &mut Self {
69        // SAFETY: Path<S> is `repr(transparent)`, so to it has the same layout as OsStr.
70        unsafe { &mut *(value.as_mut() as *mut OsStr as *mut Self) }
71    }
72
73    pub const fn display<'a>(&'a self) -> DisplayPath<'a, S> {
74        DisplayPath::<S> {
75            _phantom: PhantomData,
76            inner: self,
77        }
78    }
79
80    pub fn len(&self) -> usize {
81        self.inner.len()
82    }
83
84    pub fn is_empty(&self) -> bool {
85        self.len() == 0
86    }
87
88    pub const fn as_os_str(&self) -> &OsStr {
89        &self.inner
90    }
91
92    pub fn as_os_str_no_lead(&self) -> &OsStr {
93        OsStr::from_bytes(&self.as_bytes()[1..])
94    }
95
96    pub fn as_bytes(&self) -> &[u8] {
97        self.inner.as_bytes()
98    }
99
100    // TODO: no_lead methods
101
102    // pub fn basename(&self) -> &OsStr
103
104    // pub fn parent(&self) -> Self
105
106    pub fn join<P: AsRef<Path<Rel>>>(&self, other: P) -> OwnedPath<S> {
107        unsafe {
108            OwnedPath::<S>::from_unchecked(
109                [self.as_os_str(), other.as_ref().as_os_str()].into_iter().collect()
110            )
111        }
112    }
113
114    pub fn relative(&self, other: &Self) -> Option<&Path<Rel>> {
115        // As a general note for path interpretation: paths on Linux have no encoding, with the only
116        // constant being that they are delimited by b'/'. Because of this, we don't have to
117        // consider encoding, and splitting by b"/" is always entirely valid because thats what
118        // Linux does, even if b'/' is a later part of a variable-length character.
119        match self.inner.as_bytes().strip_prefix(other.inner.as_bytes()) {
120            None => None,
121            // If there is no leading slash, strip_prefix matched only part of a component so
122            // treat it as a fail.
123            Some(replaced) if !replaced.starts_with(b"/") => None,
124            // SAFETY: If the relative path starts with a b"/", then it is still a valid Path.
125            Some(replaced) => unsafe {
126                Some(Path::<Rel>::from_unchecked(OsStr::from_bytes(replaced)))
127            },
128        }
129    }
130
131    pub fn components<'a>(&'a self) -> Components<'a, S> {
132        Components {
133            _state: PhantomData,
134            path: self.as_bytes(),
135            head: 0,
136        }
137    }
138
139    pub fn ancestors<'a>(&'a self) -> Ancestors<'a, S> {
140        Ancestors {
141            _state: PhantomData,
142            path: self.as_bytes(),
143            index: 0,
144        }
145    }
146}
147
148#[derive(Debug, Clone, Copy, IsVariant)]
149enum Seq {
150    Slash,
151    SlashDot,
152    Other,
153}
154
155// Unfortunately, it's cheaper to copy all values one by one that constantly move all bytes back and
156// forward with insertions and removals.
157pub(crate) fn sanitize_os_str(value: &OsStr) -> OsString {
158    let mut last_seq = Seq::Other;
159    let mut valid = Vector::with_cap(value.len() + 1);
160
161    for ch in b"/".iter().chain(value.as_bytes().iter()).cloned() {
162        match (ch, last_seq) {
163            (b'\0', _) => (),
164            (b'/', Seq::Slash) => (),
165            (b'/', Seq::SlashDot) => {
166                last_seq = Seq::Slash;
167            },
168            (b'/', Seq::Other) => {
169                last_seq = Seq::Slash;
170                valid.push(ch);
171            },
172            (b'.', Seq::Slash) => {
173                last_seq = Seq::SlashDot;
174            },
175            (_, Seq::Slash) => {
176                last_seq = Seq::Other;
177                valid.push(ch);
178            },
179            (_, Seq::SlashDot) => {
180                last_seq = Seq::Other;
181                valid.push(b'.');
182                valid.push(ch);
183            },
184            (_, Seq::Other) => {
185                valid.push(ch);
186            },
187        }
188    }
189
190    if last_seq.is_slash() && valid.len() > 1 {
191        valid.pop();
192    }
193
194    OsString::from_vec(valid.into())
195}
196
197impl<S: PathState> From<OwnedPath<S>> for CString {
198    fn from(value: OwnedPath<S>) -> Self {
199        let mut bytes = value.inner.into_vec();
200        bytes.push(b'\0');
201        // SAFETY: OsString already guarantees that the internal string contains no '\0'.
202        unsafe { CString::from_vec_with_nul_unchecked(bytes) }
203    }
204}
205
206impl<S: PathState> Deref for OwnedPath<S> {
207    type Target = Path<S>;
208
209    fn deref(&self) -> &Self::Target {
210        // SAFETY: OwnedPath upholds the same invariants as Path.
211        unsafe { Path::<S>::from_unchecked(&self.inner) }
212    }
213}
214
215impl<S: PathState> AsRef<Path<S>> for OwnedPath<S> {
216    fn as_ref(&self) -> &Path<S> {
217        self.deref()
218    }
219}
220
221// Apparently there isn't a blanket impl for this?
222impl<S: PathState> AsRef<Path<S>> for Path<S> {
223    fn as_ref(&self) -> &Path<S> {
224        self
225    }
226}
227
228impl<S: PathState> Borrow<Path<S>> for OwnedPath<S> {
229    fn borrow(&self) -> &Path<S> {
230        self.as_ref()
231    }
232}
233
234impl<S: PathState> AsRef<OsStr> for Path<S> {
235    fn as_ref(&self) -> &OsStr {
236        &self.inner
237    }
238}
239
240impl<S: PathState> ToOwned for Path<S> {
241    type Owned = OwnedPath<S>;
242
243    fn to_owned(&self) -> Self::Owned {
244        OwnedPath::<S>::from_os_str_sanitized(self.as_os_str())
245    }
246}
247
248impl<S: PathState> PartialEq for OwnedPath<S> {
249    fn eq(&self, other: &Self) -> bool {
250        self.as_ref().inner == other.as_ref().inner
251    }
252}
253
254impl<S: PathState> PartialEq for Path<S> {
255    fn eq(&self, other: &Self) -> bool {
256        self.inner == other.inner
257    }
258}
259
260impl<S: PathState> PartialEq<Path<S>> for OwnedPath<S> {
261    fn eq(&self, other: &Path<S>) -> bool {
262        self.as_ref().inner == other.inner
263    }
264}
265
266impl<S: PathState> PartialEq<OwnedPath<S>> for Path<S> {
267    fn eq(&self, other: &OwnedPath<S>) -> bool {
268        self.inner == other.as_ref().inner
269    }
270}
271
272impl<S: PathState> Eq for OwnedPath<S> {}
273
274impl<S: PathState> Eq for Path<S> {}
275
276impl<S: PathState> PartialOrd for OwnedPath<S> {
277    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
278        Some(self.cmp(other))
279    }
280}
281
282impl<S: PathState> Ord for OwnedPath<S> {
283    fn cmp(&self, other: &Self) -> Ordering {
284        self.as_ref().inner.cmp(&other.as_ref().inner)
285    }
286}
287
288impl<S: PathState> PartialOrd for Path<S> {
289    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
290        Some(self.cmp(other))
291    }
292}
293
294impl<S: PathState> Ord for Path<S> {
295    fn cmp(&self, other: &Self) -> Ordering {
296        self.inner.cmp(&other.inner)
297    }
298}
299
300impl<S: PathState> PartialOrd<Path<S>> for OwnedPath<S> {
301    fn partial_cmp(&self, other: &Path<S>) -> Option<Ordering> {
302        Some(self.as_ref().cmp(other))
303    }
304}
305
306impl<S: PathState> PartialOrd<OwnedPath<S>> for Path<S> {
307    fn partial_cmp(&self, other: &OwnedPath<S>) -> Option<Ordering> {
308        Some(self.cmp(other.as_ref()))
309    }
310}
311
312impl<S: PathState> Debug for OwnedPath<S> {
313    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
314        f.debug_struct("OwnedPath")
315            .field("<state>", &util::fmt::raw_type_name::<S>())
316            .field("value", &self.inner)
317            .finish()
318    }
319}
320
321impl<S: PathState> Debug for Path<S> {
322    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
323        f.debug_struct("Path")
324            .field("<state>", &util::fmt::raw_type_name::<S>())
325            .field("value", &&self.inner)
326            .finish()
327    }
328}