use super::Error;
#[cfg(feature = "alloc")]
use crate::{pathbuf::PathBuf, DELIMITER};
#[cfg(feature = "alloc")]
use alloc::{str, string::String, vec::Vec};
#[cfg(feature = "alloc")]
use core::fmt::{self, Debug};
pub const MAX_COMPONENT_LENGTH: usize = 256;
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Path([u8]);
impl Path {
#[allow(unsafe_code)]
pub fn new<P>(path: &P) -> Result<&Self, Error>
where
P: AsRef<[u8]> + ?Sized,
{
if Components::new(path.as_ref()).validate() {
Ok(unsafe { &*(path.as_ref() as *const [u8] as *const Self) })
} else {
Err(Error)
}
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
pub fn components(&self) -> Components<'_> {
Components::new(&self.0)
}
pub fn is_root(&self) -> bool {
self.0.is_empty()
}
#[cfg(feature = "alloc")]
pub fn join<P>(&self, path: P) -> PathBuf
where
P: AsRef<Path>,
{
let mut result = PathBuf::new();
result.extend(self.components());
result.extend(path.as_ref().components());
result
}
pub fn parent(&self) -> Option<&Path> {
let mut tail = None;
for component in self.components() {
tail = Some(component)
}
tail.map(|t| {
let tail_len = self.0.len() - t.len() - 1;
Path::new(&self.0[..tail_len]).unwrap()
})
}
#[cfg(feature = "alloc")]
pub fn stringify(&self) -> Result<String, Error> {
let mut result = String::new();
if self.is_root() {
result.push(DELIMITER);
return Ok(result);
}
for component in self.components() {
result.push(DELIMITER);
result.push_str(component.stringify()?.as_ref());
}
Ok(result)
}
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
#[cfg(feature = "alloc")]
pub(crate) fn debug_components(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(")?;
if let Ok(s) = self.stringify() {
s.fmt(f)?;
} else {
let component_count = self.components().count();
for (i, component) in self.components().enumerate() {
write!(f, "{:?}", component)?;
if i < component_count - 1 {
write!(f, ", ")?;
}
}
}
write!(f, ")")
}
}
impl AsRef<Path> for Path {
fn as_ref(&self) -> &Self {
self
}
}
#[cfg(feature = "alloc")]
impl Debug for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "hkd32::Path")?;
self.debug_components(f)
}
}
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct Component<'a>(&'a [u8]);
#[allow(clippy::len_without_is_empty)]
impl<'a> Component<'a> {
pub fn new(bytes: &'a [u8]) -> Result<Self, Error> {
if !bytes.is_empty() && bytes.len() <= MAX_COMPONENT_LENGTH {
Ok(Component(bytes))
} else {
Err(Error)
}
}
pub fn as_bytes(&self) -> &'a [u8] {
self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
#[cfg(feature = "alloc")]
pub fn stringify(&self) -> Result<String, Error> {
let s = str::from_utf8(self.as_bytes())
.map(String::from)
.map_err(|_| Error)?;
if s.is_ascii() {
Ok(s)
} else {
Err(Error)
}
}
#[cfg(feature = "alloc")]
pub fn to_bytes(self) -> Vec<u8> {
let mut serialized = Vec::with_capacity(1 + self.len());
serialized.push((self.len() - 1) as u8);
serialized.extend_from_slice(self.0);
serialized
}
}
impl<'a> AsRef<Component<'a>> for Component<'a> {
fn as_ref(&self) -> &Component<'a> {
self
}
}
#[cfg(feature = "alloc")]
impl<'a> Debug for Component<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "hkd32::Component")?;
if let Ok(s) = self.stringify() {
write!(f, "({:?})", s)
} else {
write!(f, "({:?})", self.as_bytes())
}
}
}
#[derive(Clone)]
pub struct Components<'a> {
path: &'a [u8],
valid: bool,
}
impl<'a> Components<'a> {
fn new(path: &'a [u8]) -> Self {
Self { path, valid: true }
}
fn validate(mut self) -> bool {
while self.next().is_some() {}
self.valid
}
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
fn next(&mut self) -> Option<Component<'a>> {
let component_len = *self.path.first()? as usize + 1;
self.path = &self.path[1..];
if self.path.len() < component_len {
self.valid = false;
return None;
}
let (component_bytes, remaining) = self.path.split_at(component_len);
self.path = remaining;
if let Ok(component) = Component::new(component_bytes) {
Some(component)
} else {
self.valid = false;
None
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
#[test]
fn test_root() {
let root_path = Path::new(&[]).unwrap();
assert_eq!(root_path.components().count(), 0);
assert_eq!(root_path.stringify().unwrap(), "/");
assert_eq!(&format!("{:?}", root_path), "hkd32::Path(\"/\")");
}
}