use alloc::borrow::{Cow, ToOwned};
use alloc::rc::Rc;
#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::str::Utf8Error;
use core::{cmp, fmt};
use crate::no_std_compat::*;
use crate::{
CheckedPathError, Encoding, Path, StripPrefixError, Utf8Ancestors, Utf8Component,
Utf8Components, Utf8Encoding, Utf8Iter, Utf8PathBuf,
};
#[repr(transparent)]
pub struct Utf8Path<T>
where
T: Utf8Encoding,
{
_encoding: PhantomData<T>,
pub(crate) inner: str,
}
impl<T> Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
unsafe { &*(s.as_ref() as *const str as *const Self) }
}
pub fn as_str(&self) -> &str {
&self.inner
}
pub fn to_path_buf(&self) -> Utf8PathBuf<T> {
Utf8PathBuf {
_encoding: PhantomData,
inner: self.inner.to_owned(),
}
}
pub fn is_absolute(&self) -> bool {
self.components().is_absolute()
}
#[inline]
pub fn is_relative(&self) -> bool {
!self.is_absolute()
}
pub fn is_valid(&self) -> bool {
self.components().all(|c| c.is_valid())
}
#[inline]
pub fn has_root(&self) -> bool {
self.components().has_root()
}
pub fn parent(&self) -> Option<&Self> {
let mut comps = self.components();
let comp = comps.next_back();
comp.and_then(|p| {
if !p.is_root() {
Some(Self::new(comps.as_str()))
} else {
None
}
})
}
#[inline]
pub fn ancestors(&self) -> Utf8Ancestors<'_, T> {
Utf8Ancestors { next: Some(self) }
}
pub fn file_name(&self) -> Option<&str> {
match self.components().next_back() {
Some(p) => {
if p.is_normal() {
Some(p.as_str())
} else {
None
}
}
None => None,
}
}
pub fn strip_prefix<P>(&self, base: P) -> Result<&Utf8Path<T>, StripPrefixError>
where
P: AsRef<Utf8Path<T>>,
{
self._strip_prefix(base.as_ref())
}
fn _strip_prefix(&self, base: &Utf8Path<T>) -> Result<&Utf8Path<T>, StripPrefixError> {
match helpers::iter_after(self.components(), base.components()) {
Some(c) => Ok(Utf8Path::new(c.as_str())),
None => Err(StripPrefixError(())),
}
}
pub fn starts_with<P>(&self, base: P) -> bool
where
P: AsRef<Utf8Path<T>>,
{
self._starts_with(base.as_ref())
}
fn _starts_with(&self, base: &Utf8Path<T>) -> bool {
helpers::iter_after(self.components(), base.components()).is_some()
}
pub fn ends_with<P>(&self, child: P) -> bool
where
P: AsRef<Utf8Path<T>>,
{
self._ends_with(child.as_ref())
}
fn _ends_with(&self, child: &Utf8Path<T>) -> bool {
helpers::iter_after(self.components().rev(), child.components().rev()).is_some()
}
pub fn file_stem(&self) -> Option<&str> {
self.file_name()
.map(helpers::rsplit_file_at_dot)
.and_then(|(before, after)| before.or(after))
}
pub fn extension(&self) -> Option<&str> {
self.file_name()
.map(helpers::rsplit_file_at_dot)
.and_then(|(before, after)| before.and(after))
}
pub fn normalize(&self) -> Utf8PathBuf<T> {
let mut components = Vec::new();
for component in self.components() {
if !component.is_current() && !component.is_parent() {
components.push(component);
} else if component.is_parent() {
if let Some(last) = components.last() {
if last.is_normal() {
components.pop();
}
}
}
}
let mut path = Utf8PathBuf::<T>::new();
for component in components {
path.push(component.as_str());
}
path
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
pub fn absolutize(&self) -> std::io::Result<Utf8PathBuf<T>> {
if self.is_absolute() {
Ok(self.normalize())
} else {
let cwd = crate::utils::utf8_current_dir()?.with_encoding();
Ok(cwd.join(self).normalize())
}
}
pub fn join<P: AsRef<Utf8Path<T>>>(&self, path: P) -> Utf8PathBuf<T> {
self._join(path.as_ref())
}
fn _join(&self, path: &Utf8Path<T>) -> Utf8PathBuf<T> {
let mut buf = self.to_path_buf();
buf.push(path);
buf
}
pub fn join_checked<P: AsRef<Utf8Path<T>>>(
&self,
path: P,
) -> Result<Utf8PathBuf<T>, CheckedPathError> {
self._join_checked(path.as_ref())
}
fn _join_checked(&self, path: &Utf8Path<T>) -> Result<Utf8PathBuf<T>, CheckedPathError> {
let mut buf = self.to_path_buf();
buf.push_checked(path)?;
Ok(buf)
}
pub fn with_file_name<S: AsRef<str>>(&self, file_name: S) -> Utf8PathBuf<T> {
self._with_file_name(file_name.as_ref())
}
fn _with_file_name(&self, file_name: &str) -> Utf8PathBuf<T> {
let mut buf = self.to_path_buf();
buf.set_file_name(file_name);
buf
}
pub fn with_extension<S: AsRef<str>>(&self, extension: S) -> Utf8PathBuf<T> {
self._with_extension(extension.as_ref())
}
fn _with_extension(&self, extension: &str) -> Utf8PathBuf<T> {
let mut buf = self.to_path_buf();
buf.set_extension(extension);
buf
}
pub fn components(&self) -> <T as Utf8Encoding>::Components<'_> {
T::components(&self.inner)
}
#[inline]
pub fn iter(&self) -> Utf8Iter<'_, T> {
Utf8Iter::new(self.components())
}
pub fn with_encoding<U>(&self) -> Utf8PathBuf<U>
where
U: Utf8Encoding,
{
if T::label() == U::label() {
Utf8Path::new(self.as_str()).to_path_buf()
} else {
let mut path = Utf8PathBuf::new();
for component in self.components() {
if component.is_root() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::root().as_str());
} else if component.is_current() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::current().as_str());
} else if component.is_parent() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::parent().as_str());
} else {
path.push(component.as_str());
}
}
path
}
}
pub fn with_encoding_checked<U>(&self) -> Result<Utf8PathBuf<U>, CheckedPathError>
where
U: Utf8Encoding,
{
let mut path = Utf8PathBuf::new();
for component in self.components() {
if component.is_root() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::root().as_str());
} else if component.is_current() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::current().as_str());
} else if component.is_parent() {
path.push(<
<<U as Utf8Encoding>::Components<'_> as Utf8Components>::Component
as Utf8Component
>::parent().as_str());
} else {
path.push_checked(component.as_str())?;
}
}
Ok(path)
}
pub fn into_path_buf(self: Box<Utf8Path<T>>) -> Utf8PathBuf<T> {
let rw = Box::into_raw(self) as *mut str;
let inner = unsafe { Box::from_raw(rw) };
Utf8PathBuf {
_encoding: PhantomData,
inner: inner.into_string(),
}
}
pub fn from_bytes_path<U>(path: &Path<U>) -> Result<&Self, Utf8Error>
where
U: Encoding,
{
Ok(Self::new(core::str::from_utf8(path.as_bytes())?))
}
pub unsafe fn from_bytes_path_unchecked<U>(path: &Path<U>) -> &Self
where
U: Encoding,
{
Self::new(core::str::from_utf8_unchecked(path.as_bytes()))
}
pub fn as_bytes_path<U>(&self) -> &Path<U>
where
U: Encoding,
{
Path::new(self.as_str())
}
}
impl<T> Clone for Box<Utf8Path<T>>
where
T: Utf8Encoding,
{
fn clone(&self) -> Self {
self.to_path_buf().into_boxed_path()
}
}
impl<T> AsRef<[u8]> for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &[u8] {
self.inner.as_bytes()
}
}
impl<T> AsRef<str> for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &str {
&self.inner
}
}
impl<T> AsRef<Utf8Path<T>> for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &Utf8Path<T> {
self
}
}
impl<T> AsRef<Utf8Path<T>> for str
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &Utf8Path<T> {
Utf8Path::new(self)
}
}
impl<T> AsRef<Utf8Path<T>> for String
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &Utf8Path<T> {
Utf8Path::new(self)
}
}
impl<T> fmt::Debug for Utf8Path<T>
where
T: Utf8Encoding,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Utf8Path")
.field("_encoding", &T::label())
.field("inner", &&self.inner)
.finish()
}
}
impl<T> fmt::Display for Utf8Path<T>
where
T: Utf8Encoding,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.inner, formatter)
}
}
impl<T> cmp::PartialEq for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn eq(&self, other: &Utf8Path<T>) -> bool {
self.components() == other.components()
}
}
impl<T> cmp::Eq for Utf8Path<T> where T: Utf8Encoding {}
impl<T> Hash for Utf8Path<T>
where
T: Utf8Encoding,
{
fn hash<H: Hasher>(&self, h: &mut H) {
T::hash(self.as_str(), h)
}
}
impl<T> cmp::PartialOrd for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &Utf8Path<T>) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> cmp::Ord for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn cmp(&self, other: &Utf8Path<T>) -> cmp::Ordering {
self.components().cmp(other.components())
}
}
impl<T> From<&Utf8Path<T>> for Box<Utf8Path<T>>
where
T: Utf8Encoding,
{
fn from(path: &Utf8Path<T>) -> Self {
let boxed: Box<str> = path.inner.into();
let rw = Box::into_raw(boxed) as *mut Utf8Path<T>;
unsafe { Box::from_raw(rw) }
}
}
impl<T> From<Cow<'_, Utf8Path<T>>> for Box<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(cow: Cow<'_, Utf8Path<T>>) -> Box<Utf8Path<T>> {
match cow {
Cow::Borrowed(path) => Box::from(path),
Cow::Owned(path) => Box::from(path),
}
}
}
impl<T> From<Utf8PathBuf<T>> for Box<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(p: Utf8PathBuf<T>) -> Box<Utf8Path<T>> {
p.into_boxed_path()
}
}
impl<'a, T> From<&'a Utf8Path<T>> for Cow<'a, Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(s: &'a Utf8Path<T>) -> Self {
Cow::Borrowed(s)
}
}
impl<T> From<Utf8PathBuf<T>> for Cow<'_, Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(s: Utf8PathBuf<T>) -> Self {
Cow::Owned(s)
}
}
impl<'a, T> From<&'a Utf8PathBuf<T>> for Cow<'a, Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(p: &'a Utf8PathBuf<T>) -> Self {
Cow::Borrowed(p.as_path())
}
}
#[cfg(target_has_atomic = "ptr")]
impl<T> From<Utf8PathBuf<T>> for Arc<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(path_buf: Utf8PathBuf<T>) -> Self {
let arc: Arc<str> = Arc::from(path_buf.into_string());
unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Utf8Path<T>) }
}
}
#[cfg(target_has_atomic = "ptr")]
impl<T> From<&Utf8Path<T>> for Arc<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(path: &Utf8Path<T>) -> Self {
let arc: Arc<str> = Arc::from(path.as_str().to_string());
unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Utf8Path<T>) }
}
}
impl<T> From<Utf8PathBuf<T>> for Rc<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(path_buf: Utf8PathBuf<T>) -> Self {
let rc: Rc<str> = Rc::from(path_buf.into_string());
unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Utf8Path<T>) }
}
}
impl<T> From<&Utf8Path<T>> for Rc<Utf8Path<T>>
where
T: Utf8Encoding,
{
#[inline]
fn from(path: &Utf8Path<T>) -> Self {
let rc: Rc<str> = Rc::from(path.as_str());
unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Utf8Path<T>) }
}
}
impl<'a, T> IntoIterator for &'a Utf8Path<T>
where
T: Utf8Encoding,
{
type IntoIter = Utf8Iter<'a, T>;
type Item = &'a str;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<T> ToOwned for Utf8Path<T>
where
T: Utf8Encoding,
{
type Owned = Utf8PathBuf<T>;
#[inline]
fn to_owned(&self) -> Self::Owned {
self.to_path_buf()
}
}
macro_rules! impl_cmp {
($($lt:lifetime),* ; $lhs:ty, $rhs: ty) => {
impl<$($lt,)* T> PartialEq<$rhs> for $lhs
where
T: Utf8Encoding,
{
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Utf8Path<T> as PartialEq>::eq(self, other)
}
}
impl<$($lt,)* T> PartialEq<$lhs> for $rhs
where
T: Utf8Encoding,
{
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Utf8Path<T> as PartialEq>::eq(self, other)
}
}
impl<$($lt,)* T> PartialOrd<$rhs> for $lhs
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
<Utf8Path<T> as PartialOrd>::partial_cmp(self, other)
}
}
impl<$($lt,)* T> PartialOrd<$lhs> for $rhs
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
<Utf8Path<T> as PartialOrd>::partial_cmp(self, other)
}
}
};
}
impl_cmp!(; Utf8PathBuf<T>, Utf8Path<T>);
impl_cmp!('a; Utf8PathBuf<T>, &'a Utf8Path<T>);
impl_cmp!('a; Cow<'a, Utf8Path<T>>, Utf8Path<T>);
impl_cmp!('a, 'b; Cow<'a, Utf8Path<T>>, &'b Utf8Path<T>);
impl_cmp!('a; Cow<'a, Utf8Path<T>>, Utf8PathBuf<T>);
macro_rules! impl_cmp_bytes {
($($lt:lifetime),* ; $lhs:ty, $rhs: ty) => {
impl<$($lt,)* T> PartialEq<$rhs> for $lhs
where
T: Utf8Encoding,
{
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Utf8Path<T> as PartialEq>::eq(self, other.as_ref())
}
}
impl<$($lt,)* T> PartialEq<$lhs> for $rhs
where
T: Utf8Encoding,
{
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Utf8Path<T> as PartialEq>::eq(self.as_ref(), other)
}
}
impl<$($lt,)* T> PartialOrd<$rhs> for $lhs
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
<Utf8Path<T> as PartialOrd>::partial_cmp(self, other.as_ref())
}
}
impl<$($lt,)* T> PartialOrd<$lhs> for $rhs
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
<Utf8Path<T> as PartialOrd>::partial_cmp(self.as_ref(), other)
}
}
};
}
impl_cmp_bytes!(; Utf8PathBuf<T>, str);
impl_cmp_bytes!('a; Utf8PathBuf<T>, &'a str);
impl_cmp_bytes!(; Utf8PathBuf<T>, String);
impl_cmp_bytes!(; Utf8Path<T>, str);
impl_cmp_bytes!('a; Utf8Path<T>, &'a str);
impl_cmp_bytes!(; Utf8Path<T>, String);
impl_cmp_bytes!('a; &'a Utf8Path<T>, str);
impl_cmp_bytes!('a; &'a Utf8Path<T>, String);
mod helpers {
use super::*;
pub fn rsplit_file_at_dot(file: &str) -> (Option<&str>, Option<&str>) {
if file == ".." {
return (Some(file), None);
}
let mut iter = file.rsplitn(2, '.');
let after = iter.next();
let before = iter.next();
if before == Some("") {
(Some(file), None)
} else {
(before, after)
}
}
pub fn iter_after<'a, 'b, T, U, I, J>(mut iter: I, mut prefix: J) -> Option<I>
where
T: Utf8Component<'a>,
U: Utf8Component<'b>,
I: Iterator<Item = T> + Clone,
J: Iterator<Item = U>,
{
loop {
let mut iter_next = iter.clone();
match (iter_next.next(), prefix.next()) {
(Some(ref x), Some(ref y)) if x.as_str() == y.as_str() => (),
(Some(_), Some(_)) => return None,
(Some(_), None) => return Some(iter),
(None, None) => return Some(iter),
(None, Some(_)) => return None,
}
iter = iter_next;
}
}
}
#[cfg(any(
unix,
all(target_vendor = "fortanix", target_env = "sgx"),
target_os = "solid_asp3",
target_os = "hermit",
target_os = "wasi"
))]
#[cfg(feature = "std")]
mod std_conversions {
use std::ffi::{OsStr, OsString};
#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
use std::os::fortanix_sgx as os;
#[cfg(target_os = "solid_asp3")]
use std::os::solid as os;
#[cfg(any(target_os = "hermit", unix))]
use std::os::unix as os;
#[cfg(target_os = "wasi")]
use std::os::wasi as os;
use os::ffi::OsStrExt;
use super::*;
use crate::common::TryAsRef;
impl<T> TryAsRef<Utf8Path<T>> for OsStr
where
T: Utf8Encoding,
{
#[inline]
fn try_as_ref(&self) -> Option<&Utf8Path<T>> {
std::str::from_utf8(self.as_bytes()).ok().map(Utf8Path::new)
}
}
impl<T> TryAsRef<Utf8Path<T>> for OsString
where
T: Utf8Encoding,
{
#[inline]
fn try_as_ref(&self) -> Option<&Utf8Path<T>> {
std::str::from_utf8(self.as_bytes()).ok().map(Utf8Path::new)
}
}
impl<T> AsRef<OsStr> for Utf8Path<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &OsStr {
OsStrExt::from_bytes(self.as_str().as_bytes())
}
}
}