use crate::dir_entry::{DirEntryName, DirEntryNameError};
use crate::format::{BytesDisplay, format_bytes_debug};
use alloc::string::String;
use alloc::vec::Vec;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::str::{self, Utf8Error};
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PathError {
ComponentTooLong,
ContainsNull,
Encoding,
}
impl Display for PathError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::ComponentTooLong => {
write!(f, "path contains a component longer than 255 bytes")
}
Self::ContainsNull => write!(f, "path contains a null byte"),
Self::Encoding => {
write!(f, "path cannot be created due to encoding")
}
}
}
}
impl Error for PathError {}
#[derive(Clone, Copy, Eq, Ord, PartialOrd, Hash)]
pub struct Path<'a>(
&'a [u8],
);
impl<'a> Path<'a> {
pub const SEPARATOR: u8 = b'/';
pub const ROOT: Path<'static> = Path(&[Self::SEPARATOR]);
#[track_caller]
pub fn new<P>(p: &'a P) -> Self
where
P: AsRef<[u8]> + ?Sized,
{
Self::try_from(p.as_ref()).unwrap()
}
#[must_use]
pub fn is_absolute(self) -> bool {
if self.0.is_empty() {
false
} else {
self.0[0] == Self::SEPARATOR
}
}
pub fn display(self) -> BytesDisplay<'a> {
BytesDisplay(self.0)
}
#[must_use]
pub fn join(self, path: impl AsRef<[u8]>) -> PathBuf {
PathBuf::from(self).join(path)
}
#[must_use]
pub fn components(self) -> Components<'a> {
Components {
path: self,
offset: 0,
}
}
pub fn to_str(self) -> Result<&'a str, Utf8Error> {
str::from_utf8(self.0)
}
}
impl<'a> AsRef<[u8]> for Path<'a> {
fn as_ref(&self) -> &'a [u8] {
self.0
}
}
impl Debug for Path<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
format_bytes_debug(self.0, f)
}
}
impl<'a> TryFrom<&'a str> for Path<'a> {
type Error = PathError;
fn try_from(s: &'a str) -> Result<Self, PathError> {
Self::try_from(s.as_bytes())
}
}
impl<'a> TryFrom<&'a String> for Path<'a> {
type Error = PathError;
fn try_from(s: &'a String) -> Result<Self, PathError> {
Self::try_from(s.as_bytes())
}
}
impl<'a> TryFrom<&'a [u8]> for Path<'a> {
type Error = PathError;
fn try_from(s: &'a [u8]) -> Result<Self, PathError> {
if s.contains(&0) {
return Err(PathError::ContainsNull);
}
for component in s.split(|b| *b == Path::SEPARATOR) {
if component.len() > DirEntryName::MAX_LEN {
return Err(PathError::ComponentTooLong);
}
}
Ok(Self(s))
}
}
impl<'a, const N: usize> TryFrom<&'a [u8; N]> for Path<'a> {
type Error = PathError;
fn try_from(a: &'a [u8; N]) -> Result<Self, PathError> {
Self::try_from(a.as_slice())
}
}
impl<'a> TryFrom<&'a PathBuf> for Path<'a> {
type Error = PathError;
fn try_from(p: &'a PathBuf) -> Result<Self, PathError> {
Ok(p.as_path())
}
}
#[cfg(all(feature = "std", unix))]
impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
type Error = PathError;
fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
use std::os::unix::ffi::OsStrExt;
Self::try_from(p.as_bytes())
}
}
#[cfg(all(feature = "std", not(unix)))]
impl<'a> TryFrom<&'a std::ffi::OsStr> for Path<'a> {
type Error = PathError;
fn try_from(p: &'a std::ffi::OsStr) -> Result<Self, PathError> {
Self::try_from(p.to_str().ok_or(PathError::Encoding)?)
}
}
#[cfg(feature = "std")]
impl<'a> TryFrom<&'a std::path::Path> for Path<'a> {
type Error = PathError;
fn try_from(p: &'a std::path::Path) -> Result<Self, PathError> {
Self::try_from(p.as_os_str())
}
}
impl<T> PartialEq<T> for Path<'_>
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
}
}
#[cfg(all(feature = "std", unix))]
impl<'a> From<Path<'a>> for &'a std::path::Path {
fn from(p: Path<'a>) -> &'a std::path::Path {
use std::os::unix::ffi::OsStrExt;
let s = std::ffi::OsStr::from_bytes(p.0);
std::path::Path::new(s)
}
}
#[derive(Clone, Default, Eq, Ord, PartialOrd, Hash)]
pub struct PathBuf(Vec<u8>);
impl PathBuf {
#[track_caller]
pub fn new<P>(p: &P) -> Self
where
P: AsRef<[u8]> + ?Sized,
{
Self::try_from(p.as_ref()).unwrap()
}
#[must_use]
pub const fn empty() -> Self {
Self(Vec::new())
}
#[must_use]
pub fn as_path(&self) -> Path<'_> {
Path(&self.0)
}
#[must_use]
pub fn is_absolute(&self) -> bool {
self.as_path().is_absolute()
}
pub fn display(&self) -> BytesDisplay<'_> {
BytesDisplay(&self.0)
}
#[track_caller]
pub fn push(&mut self, path: impl AsRef<[u8]>) {
#[track_caller]
fn inner(this: &mut PathBuf, p: &[u8]) {
let p = Path::try_from(p).expect("push arg must be a valid path");
if p.is_absolute() {
this.0.clear();
this.0.extend(p.0);
return;
}
let add_sep = if let Some(last) = this.0.last() {
*last != b'/'
} else {
false
};
if add_sep {
let len = p.0.len().checked_add(1).unwrap();
this.0.reserve(len);
this.0.push(Path::SEPARATOR);
} else {
this.0.reserve(p.0.len());
}
this.0.extend(p.0);
}
inner(self, path.as_ref())
}
#[must_use]
pub fn join(&self, path: impl AsRef<[u8]>) -> Self {
let mut t = self.clone();
t.push(path);
t
}
#[must_use]
pub fn components(&self) -> Components<'_> {
self.as_path().components()
}
pub fn to_str(&self) -> Result<&str, Utf8Error> {
self.as_path().to_str()
}
}
impl AsRef<[u8]> for PathBuf {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Debug for PathBuf {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.as_path().fmt(f)
}
}
impl TryFrom<&str> for PathBuf {
type Error = PathError;
fn try_from(s: &str) -> Result<Self, PathError> {
Self::try_from(s.as_bytes().to_vec())
}
}
impl TryFrom<&String> for PathBuf {
type Error = PathError;
fn try_from(s: &String) -> Result<Self, PathError> {
Self::try_from(s.as_bytes().to_vec())
}
}
impl TryFrom<String> for PathBuf {
type Error = PathError;
fn try_from(s: String) -> Result<Self, PathError> {
Self::try_from(s.into_bytes())
}
}
impl TryFrom<&[u8]> for PathBuf {
type Error = PathError;
fn try_from(s: &[u8]) -> Result<Self, PathError> {
Self::try_from(s.to_vec())
}
}
impl<const N: usize> TryFrom<&[u8; N]> for PathBuf {
type Error = PathError;
fn try_from(a: &[u8; N]) -> Result<Self, PathError> {
Self::try_from(a.as_slice().to_vec())
}
}
impl TryFrom<Vec<u8>> for PathBuf {
type Error = PathError;
fn try_from(s: Vec<u8>) -> Result<Self, PathError> {
Path::try_from(s.as_slice())?;
Ok(Self(s))
}
}
#[cfg(all(feature = "std", unix))]
impl TryFrom<std::ffi::OsString> for PathBuf {
type Error = PathError;
fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
use std::os::unix::ffi::OsStringExt;
Self::try_from(p.into_vec())
}
}
#[cfg(all(feature = "std", not(unix)))]
impl TryFrom<std::ffi::OsString> for PathBuf {
type Error = PathError;
fn try_from(p: std::ffi::OsString) -> Result<Self, PathError> {
Self::try_from(p.into_string().map_err(|_| PathError::Encoding)?)
}
}
#[cfg(feature = "std")]
impl TryFrom<std::path::PathBuf> for PathBuf {
type Error = PathError;
fn try_from(p: std::path::PathBuf) -> Result<Self, PathError> {
Self::try_from(p.into_os_string())
}
}
impl<T> PartialEq<T> for PathBuf
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
}
}
impl<'a> From<Path<'a>> for PathBuf {
fn from(p: Path<'a>) -> Self {
Self(p.0.to_vec())
}
}
#[cfg(all(feature = "std", unix))]
impl From<PathBuf> for std::path::PathBuf {
fn from(p: PathBuf) -> Self {
use std::os::unix::ffi::OsStringExt;
let s = std::ffi::OsString::from_vec(p.0);
Self::from(s)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Component<'a> {
RootDir,
CurDir,
ParentDir,
Normal(DirEntryName<'a>),
}
impl<'a> Component<'a> {
pub fn normal<T: AsRef<[u8]> + ?Sized>(
name: &'a T,
) -> Result<Self, DirEntryNameError> {
Ok(Component::Normal(DirEntryName::try_from(name.as_ref())?))
}
}
impl Debug for Component<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Component::RootDir => write!(f, "RootDir"),
Component::CurDir => write!(f, "CurDir"),
Component::ParentDir => write!(f, "ParentDir"),
Component::Normal(name) => {
write!(f, "Normal(")?;
format_bytes_debug(name.as_ref(), f)?;
write!(f, ")")
}
}
}
}
impl<T> PartialEq<T> for Component<'_>
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
let other = other.as_ref();
match self {
Component::RootDir => other == b"/",
Component::CurDir => other == b".",
Component::ParentDir => other == b"..",
Component::Normal(c) => *c == other,
}
}
}
pub struct Components<'a> {
path: Path<'a>,
offset: usize,
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
fn next(&mut self) -> Option<Component<'a>> {
let path = &self.path.0;
if self.offset >= path.len() {
return None;
}
if self.offset == 0 && path[0] == Path::SEPARATOR {
self.offset = 1;
return Some(Component::RootDir);
}
while self.offset < path.len() && path[self.offset] == Path::SEPARATOR {
self.offset = self.offset.checked_add(1).unwrap();
}
if self.offset >= path.len() {
return None;
}
let end: usize = if let Some(index) = self
.path
.0
.iter()
.skip(self.offset)
.position(|b| *b == Path::SEPARATOR)
{
self.offset.checked_add(index).unwrap()
} else {
path.len()
};
let component = &path[self.offset..end];
let component = if component == b"." {
Component::CurDir
} else if component == b".." {
Component::ParentDir
} else {
Component::Normal(DirEntryName(component))
};
self.offset = end;
Some(component)
}
}