1use crate::dir_entry::{DirEntryName, DirEntryNameError};
10use crate::format::{BytesDisplay, format_bytes_debug};
11use alloc::string::String;
12use alloc::vec::Vec;
13use core::error::Error;
14use core::fmt::{self, Debug, Display, Formatter};
15use core::str::{self, Utf8Error};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19#[non_exhaustive]
20pub enum PathError {
21 ComponentTooLong,
23
24 ContainsNull,
26
27 Encoding,
38}
39
40impl Display for PathError {
41 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::ComponentTooLong => {
44 write!(f, "path contains a component longer than 255 bytes")
45 }
46 Self::ContainsNull => write!(f, "path contains a null byte"),
47 Self::Encoding => {
48 write!(f, "path cannot be created due to encoding")
49 }
50 }
51 }
52}
53
54impl Error for PathError {}
55
56#[derive(Clone, Copy, Eq, Ord, PartialOrd, Hash)]
62pub struct Path<'a>(
63 &'a [u8],
67);
68
69impl<'a> Path<'a> {
70 pub const SEPARATOR: u8 = b'/';
72
73 pub const ROOT: Path<'static> = Path(&[Self::SEPARATOR]);
75
76 #[track_caller]
86 pub fn new<P>(p: &'a P) -> Self
87 where
88 P: AsRef<[u8]> + ?Sized,
89 {
90 Self::try_from(p.as_ref()).unwrap()
91 }
92
93 #[must_use]
95 pub fn is_absolute(self) -> bool {
96 if self.0.is_empty() {
97 false
98 } else {
99 self.0[0] == Self::SEPARATOR
100 }
101 }
102
103 pub fn display(self) -> BytesDisplay<'a> {
109 BytesDisplay(self.0)
110 }
111
112 #[must_use]
121 pub fn join(self, path: impl AsRef<[u8]>) -> PathBuf {
122 PathBuf::from(self).join(path)
123 }
124
125 #[must_use]
127 pub fn components(self) -> Components<'a> {
128 Components {
129 path: self,
130 offset: 0,
131 }
132 }
133
134 pub fn to_str(self) -> Result<&'a str, Utf8Error> {
136 str::from_utf8(self.0)
137 }
138}
139
140impl<'a> AsRef<[u8]> for Path<'a> {
141 fn as_ref(&self) -> &'a [u8] {
142 self.0
143 }
144}
145
146impl Debug for Path<'_> {
147 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
148 format_bytes_debug(self.0, f)
149 }
150}
151
152impl<'a> TryFrom<&'a str> for Path<'a> {
153 type Error = PathError;
154
155 fn try_from(s: &'a str) -> Result<Self, PathError> {
156 Self::try_from(s.as_bytes())
157 }
158}
159
160impl<'a> TryFrom<&'a String> for Path<'a> {
161 type Error = PathError;
162
163 fn try_from(s: &'a String) -> Result<Self, PathError> {
164 Self::try_from(s.as_bytes())
165 }
166}
167
168impl<'a> TryFrom<&'a [u8]> for Path<'a> {
169 type Error = PathError;
170
171 fn try_from(s: &'a [u8]) -> Result<Self, PathError> {
172 if s.contains(&0) {
173 return Err(PathError::ContainsNull);
174 }
175
176 for component in s.split(|b| *b == Path::SEPARATOR) {
177 if component.len() > DirEntryName::MAX_LEN {
178 return Err(PathError::ComponentTooLong);
179 }
180 }
181
182 Ok(Self(s))
183 }
184}
185
186impl<'a, const N: usize> TryFrom<&'a [u8; N]> for Path<'a> {
187 type Error = PathError;
188
189 fn try_from(a: &'a [u8; N]) -> Result<Self, PathError> {
190 Self::try_from(a.as_slice())
191 }
192}
193
194impl<'a> TryFrom<&'a PathBuf> for Path<'a> {
195 type Error = PathError;
196
197 fn try_from(p: &'a PathBuf) -> Result<Self, PathError> {
198 Ok(p.as_path())
199 }
200}
201
202#[cfg(all(feature = "std", unix))]
203impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
204 type Error = PathError;
205
206 fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
207 use std::os::unix::ffi::OsStrExt;
208
209 Self::try_from(p.as_bytes())
210 }
211}
212
213#[cfg(all(feature = "std", not(unix)))]
214impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
215 type Error = PathError;
216
217 fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
218 Self::try_from(p.to_str().ok_or(PathError::Encoding)?)
219 }
220}
221
222#[cfg(feature = "std")]
223impl<'a> TryFrom<&'a std::path::Path> for Path<'a> {
224 type Error = PathError;
225
226 fn try_from(p: &'a std::path::Path) -> Result<Self, PathError> {
227 Self::try_from(p.as_os_str())
228 }
229}
230
231impl<T> PartialEq<T> for Path<'_>
232where
233 T: AsRef<[u8]>,
234{
235 fn eq(&self, other: &T) -> bool {
236 self.0 == other.as_ref()
237 }
238}
239
240#[cfg(all(feature = "std", unix))]
241impl<'a> From<Path<'a>> for &'a std::path::Path {
242 fn from(p: Path<'a>) -> &'a std::path::Path {
243 use std::os::unix::ffi::OsStrExt;
244
245 let s = std::ffi::OsStr::from_bytes(p.0);
246 std::path::Path::new(s)
247 }
248}
249
250#[derive(Clone, Default, Eq, Ord, PartialOrd, Hash)]
256pub struct PathBuf(Vec<u8>);
257
258impl PathBuf {
259 #[track_caller]
269 pub fn new<P>(p: &P) -> Self
270 where
271 P: AsRef<[u8]> + ?Sized,
272 {
273 Self::try_from(p.as_ref()).unwrap()
274 }
275
276 #[must_use]
278 pub const fn empty() -> Self {
279 Self(Vec::new())
280 }
281
282 #[must_use]
284 pub fn as_path(&self) -> Path {
285 Path(&self.0)
286 }
287
288 #[must_use]
290 pub fn is_absolute(&self) -> bool {
291 self.as_path().is_absolute()
292 }
293
294 pub fn display(&self) -> BytesDisplay {
300 BytesDisplay(&self.0)
301 }
302
303 #[track_caller]
313 pub fn push(&mut self, path: impl AsRef<[u8]>) {
314 #[track_caller]
315 fn inner(this: &mut PathBuf, p: &[u8]) {
316 let p = Path::try_from(p).expect("push arg must be a valid path");
318
319 if p.is_absolute() {
322 this.0.clear();
323 this.0.extend(p.0);
324 return;
325 }
326
327 let add_sep = if let Some(last) = this.0.last() {
328 *last != b'/'
329 } else {
330 false
331 };
332
333 if add_sep {
334 let len = p.0.len().checked_add(1).unwrap();
337 this.0.reserve(len);
338 this.0.push(Path::SEPARATOR);
339 } else {
340 this.0.reserve(p.0.len());
341 }
342
343 this.0.extend(p.0);
344 }
345
346 inner(self, path.as_ref())
347 }
348
349 #[must_use]
358 pub fn join(&self, path: impl AsRef<[u8]>) -> Self {
359 let mut t = self.clone();
360 t.push(path);
361 t
362 }
363
364 #[must_use]
366 pub fn components(&self) -> Components<'_> {
367 self.as_path().components()
368 }
369
370 pub fn to_str(&self) -> Result<&str, Utf8Error> {
372 self.as_path().to_str()
373 }
374}
375
376impl AsRef<[u8]> for PathBuf {
377 fn as_ref(&self) -> &[u8] {
378 &self.0
379 }
380}
381
382impl Debug for PathBuf {
383 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
384 self.as_path().fmt(f)
385 }
386}
387
388impl TryFrom<&str> for PathBuf {
389 type Error = PathError;
390
391 fn try_from(s: &str) -> Result<Self, PathError> {
392 Self::try_from(s.as_bytes().to_vec())
393 }
394}
395
396impl TryFrom<&String> for PathBuf {
397 type Error = PathError;
398
399 fn try_from(s: &String) -> Result<Self, PathError> {
400 Self::try_from(s.as_bytes().to_vec())
401 }
402}
403
404impl TryFrom<String> for PathBuf {
405 type Error = PathError;
406
407 fn try_from(s: String) -> Result<Self, PathError> {
408 Self::try_from(s.into_bytes())
409 }
410}
411
412impl TryFrom<&[u8]> for PathBuf {
413 type Error = PathError;
414
415 fn try_from(s: &[u8]) -> Result<Self, PathError> {
416 Self::try_from(s.to_vec())
417 }
418}
419
420impl<const N: usize> TryFrom<&[u8; N]> for PathBuf {
421 type Error = PathError;
422
423 fn try_from(a: &[u8; N]) -> Result<Self, PathError> {
424 Self::try_from(a.as_slice().to_vec())
425 }
426}
427
428impl TryFrom<Vec<u8>> for PathBuf {
429 type Error = PathError;
430
431 fn try_from(s: Vec<u8>) -> Result<Self, PathError> {
432 Path::try_from(s.as_slice())?;
434
435 Ok(Self(s))
436 }
437}
438
439#[cfg(all(feature = "std", unix))]
440impl TryFrom<std::ffi::OsString> for PathBuf {
441 type Error = PathError;
442
443 fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
444 use std::os::unix::ffi::OsStringExt;
445
446 Self::try_from(p.into_vec())
447 }
448}
449
450#[cfg(all(feature = "std", not(unix)))]
451impl TryFrom<std::ffi::OsString> for PathBuf {
452 type Error = PathError;
453
454 fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
455 Self::try_from(p.into_string().map_err(|_| PathError::Encoding)?)
456 }
457}
458
459#[cfg(feature = "std")]
460impl TryFrom<std::path::PathBuf> for PathBuf {
461 type Error = PathError;
462
463 fn try_from(p: std::path::PathBuf) -> Result<Self, PathError> {
464 Self::try_from(p.into_os_string())
465 }
466}
467
468impl<T> PartialEq<T> for PathBuf
469where
470 T: AsRef<[u8]>,
471{
472 fn eq(&self, other: &T) -> bool {
473 self.0 == other.as_ref()
474 }
475}
476
477impl<'a> From<Path<'a>> for PathBuf {
478 fn from(p: Path<'a>) -> Self {
479 Self(p.0.to_vec())
480 }
481}
482
483#[cfg(all(feature = "std", unix))]
484impl From<PathBuf> for std::path::PathBuf {
485 fn from(p: PathBuf) -> Self {
486 use std::os::unix::ffi::OsStringExt;
487
488 let s = std::ffi::OsString::from_vec(p.0);
489 Self::from(s)
490 }
491}
492
493#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
495pub enum Component<'a> {
496 RootDir,
498
499 CurDir,
501
502 ParentDir,
504
505 Normal(DirEntryName<'a>),
507}
508
509impl<'a> Component<'a> {
510 pub fn normal<T: AsRef<[u8]> + ?Sized>(
512 name: &'a T,
513 ) -> Result<Self, DirEntryNameError> {
514 Ok(Component::Normal(DirEntryName::try_from(name.as_ref())?))
515 }
516}
517
518impl Debug for Component<'_> {
519 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
520 match self {
521 Component::RootDir => write!(f, "RootDir"),
522 Component::CurDir => write!(f, "CurDir"),
523 Component::ParentDir => write!(f, "ParentDir"),
524 Component::Normal(name) => {
525 write!(f, "Normal(")?;
526 format_bytes_debug(name.as_ref(), f)?;
527 write!(f, ")")
528 }
529 }
530 }
531}
532
533impl<T> PartialEq<T> for Component<'_>
534where
535 T: AsRef<[u8]>,
536{
537 fn eq(&self, other: &T) -> bool {
538 let other = other.as_ref();
539 match self {
540 Component::RootDir => other == b"/",
541 Component::CurDir => other == b".",
542 Component::ParentDir => other == b"..",
543 Component::Normal(c) => *c == other,
544 }
545 }
546}
547
548pub struct Components<'a> {
550 path: Path<'a>,
551 offset: usize,
552}
553
554impl<'a> Iterator for Components<'a> {
555 type Item = Component<'a>;
556
557 fn next(&mut self) -> Option<Component<'a>> {
558 let path = &self.path.0;
559
560 if self.offset >= path.len() {
561 return None;
562 }
563
564 if self.offset == 0 && path[0] == Path::SEPARATOR {
565 self.offset = 1;
566 return Some(Component::RootDir);
567 }
568
569 while self.offset < path.len() && path[self.offset] == Path::SEPARATOR {
571 self.offset = self.offset.checked_add(1).unwrap();
574 }
575 if self.offset >= path.len() {
576 return None;
577 }
578
579 let end: usize = if let Some(index) = self
580 .path
581 .0
582 .iter()
583 .skip(self.offset)
584 .position(|b| *b == Path::SEPARATOR)
585 {
586 self.offset.checked_add(index).unwrap()
589 } else {
590 path.len()
591 };
592
593 let component = &path[self.offset..end];
594 let component = if component == b"." {
595 Component::CurDir
596 } else if component == b".." {
597 Component::ParentDir
598 } else {
599 Component::Normal(DirEntryName(component))
603 };
604
605 self.offset = end;
606 Some(component)
607 }
608}