use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
convert::Infallible,
ffi::{OsStr, OsString},
fmt,
fs::Metadata,
ops::Deref,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
};
use crate::{error::ReportedError, fs, io, prelude::*};
#[derive(Clone)]
#[repr(transparent)]
pub struct Utf8PathBuf(pub camino::Utf8PathBuf);
impl Utf8PathBuf {
#[must_use]
pub fn new() -> Utf8PathBuf {
Utf8PathBuf(camino::Utf8PathBuf::new())
}
#[must_use]
pub fn as_path(&self) -> &Utf8Path {
Utf8Path::new(self.0.as_path())
}
pub fn from_path_buf(path: PathBuf) -> Result<Utf8PathBuf, PathBuf> {
camino::Utf8PathBuf::from_path_buf(path).map(Self)
}
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_std_path_buf(self) -> PathBuf {
self.into()
}
}
impl Deref for Utf8PathBuf {
type Target = Utf8Path;
fn deref(&self) -> &Utf8Path {
self.as_path()
}
}
impl fmt::Debug for Utf8PathBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl fmt::Display for Utf8PathBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
#[repr(transparent)]
pub struct Utf8Path(pub camino::Utf8Path);
impl Utf8Path {
pub fn new(path: &(impl AsRef<str> + ?Sized)) -> &Self {
let path = camino::Utf8Path::new(path);
unsafe { &*(path as *const camino::Utf8Path as *const Utf8Path) }
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn to_path_buf(&self) -> Utf8PathBuf {
Utf8PathBuf(self.0.to_path_buf())
}
}
impl Utf8Path {
#[inline]
#[must_use]
pub fn starts_with(&self, base: impl AsRef<Utf8Path>) -> bool {
self.0.starts_with(base.as_ref())
}
#[inline]
#[must_use]
pub fn extension(&self) -> color_eyre::Result<&str> {
self.0
.extension()
.ok_or(eyre!("Path {} has no extension", self))
}
}
impl Utf8Path {
#[inline]
#[must_use]
pub fn join(&self, path: impl AsRef<Utf8Path>) -> Utf8PathBuf {
Utf8PathBuf(self.0.join(&path.as_ref().0))
}
#[inline]
#[must_use]
pub fn parent(&self) -> Option<&Utf8Path> {
self.0.parent().map(Utf8Path::new)
}
#[inline]
pub fn ancestors(&self) -> Utf8Ancestors<'_> {
Utf8Ancestors(self.0.ancestors())
}
#[inline]
#[must_use]
pub fn file_name(&self) -> color_eyre::Result<&str> {
self.0
.file_name()
.ok_or(eyre!("ystd::path path {self} has no file_name"))
}
}
impl Utf8Path {
pub async fn canonicalize_utf8(&self) -> io::Result<Utf8PathBuf> {
fs::canonicalize_utf8(self).await
}
pub async fn canonicalize(&self) -> io::Result<Utf8PathBuf> {
self.canonicalize_utf8().await
}
pub async fn metadata(&self) -> io::Result<Metadata> {
fs::metadata(self).await
}
pub async fn is_dir(&self) -> bool {
let Ok(metadata) = self.metadata().await else {
return false;
};
metadata.is_dir()
}
pub async fn assert_dir(&self) -> color_eyre::Result<Metadata> {
let metadata = self.metadata().await?;
eyre_assert!(
metadata.is_dir(),
"ystd::path::Utf8Path::assert_dir({}): Path isn't a directory",
self
);
Ok(metadata)
}
pub async fn is_file(&self) -> bool {
let Ok(metadata) = self.metadata().await else {
return false;
};
metadata.is_file()
}
pub async fn assert_file(&self) -> color_eyre::Result<Metadata> {
let metadata = self.metadata().await?;
eyre_assert!(
metadata.is_file(),
"ystd::path::Utf8Path::assert_dir({}): Path isn't a file",
self
);
Ok(metadata)
}
pub async fn file_type_exhaustive(&self) -> color_eyre::Result<FileTypeExhaustive> {
let metadata = self.metadata().await?;
if metadata.is_file() {
Ok(FileTypeExhaustive::File)
} else if metadata.is_dir() {
Ok(FileTypeExhaustive::Dir)
} else {
Err(eyre!("Path {} isn't a file or directory", self)
.wrap_err("ystd::path::file_type_exhaustive"))
}
}
pub async fn read_dir_utf8(&self) -> io::Result<ReadDirUtf8> {
let path = self.0.to_owned();
io::asyncify(move || {
path.read_dir_utf8()
.map(|inner| ReadDirUtf8 { inner })
.map_err_std_io(|io| {
Report::new(io).wrap_err(format!("ystd::path::Utf8Path::read_dir({})", path))
})
})
.await
}
pub async fn read_dir(&self) -> io::Result<ReadDirUtf8> {
self.read_dir_utf8().await
}
}
impl fmt::Display for Utf8Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl fmt::Debug for Utf8Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
pub enum FileTypeExhaustive {
File,
Dir,
}
#[derive(Copy, Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[repr(transparent)]
pub struct Utf8Ancestors<'a>(camino::Utf8Ancestors<'a>);
impl std::fmt::Debug for Utf8Ancestors<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
}
}
impl<'a> Iterator for Utf8Ancestors<'a> {
type Item = &'a Utf8Path;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(Utf8Path::new)
}
}
impl std::iter::FusedIterator for Utf8Ancestors<'_> {}
#[derive(Debug)]
pub struct ReadDirUtf8 {
inner: camino::ReadDirUtf8,
}
impl Iterator for ReadDirUtf8 {
type Item = io::Result<Utf8DirEntry>;
fn next(&mut self) -> Option<io::Result<Utf8DirEntry>> {
self.inner.next().map(|some| {
some.map(Utf8DirEntry)
.map_err_std_io(|io| Report::new(io).wrap_err("ystd::path::ReadDirUtf8::next"))
})
}
}
#[derive(Debug)]
pub struct Utf8DirEntry(camino::Utf8DirEntry);
impl Utf8DirEntry {
#[inline]
pub fn path(&self) -> &Utf8Path {
Utf8Path::new(self.0.path())
}
#[inline]
pub fn file_name(&self) -> &str {
self.path().file_name().unwrap()
}
}
impl From<String> for Utf8PathBuf {
fn from(string: String) -> Utf8PathBuf {
Utf8PathBuf(string.into())
}
}
impl FromStr for Utf8PathBuf {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Utf8PathBuf(s.into()))
}
}
impl<'a> From<&'a str> for &'a Utf8Path {
fn from(s: &'a str) -> &'a Utf8Path {
Utf8Path::new(s)
}
}
impl<T: ?Sized + AsRef<str>> From<&T> for Utf8PathBuf {
fn from(s: &T) -> Utf8PathBuf {
Utf8PathBuf::from(s.as_ref().to_owned())
}
}
impl From<Utf8PathBuf> for String {
fn from(path: Utf8PathBuf) -> String {
path.0.into_string()
}
}
impl From<Utf8PathBuf> for PathBuf {
fn from(path: Utf8PathBuf) -> PathBuf {
path.0.into()
}
}
impl TryFrom<std::path::PathBuf> for Utf8PathBuf {
type Error = FromPathBufError;
fn try_from(path: std::path::PathBuf) -> Result<Utf8PathBuf, Self::Error> {
camino::Utf8PathBuf::try_from(path)
.map(Self)
.map_err(ReportedError::new)
.wrap_reported_err("ystd::path::Utf8PathBuf::from(PathBuf)")
}
}
impl<'a> TryFrom<&'a Path> for &'a Utf8Path {
type Error = FromPathError;
fn try_from(path: &'a Path) -> Result<&'a Utf8Path, Self::Error> {
<&camino::Utf8Path>::try_from(path)
.map(Utf8Path::new)
.map_err(ReportedError::new)
.wrap_reported_err("<&Path>::from(&ystd::path::Utf8Path)")
}
}
pub type FromPathBufError = ReportedError<camino::FromPathBufError>;
pub type FromPathError = ReportedError<camino::FromPathError>;
impl AsRef<Utf8Path> for Utf8Path {
#[inline]
fn as_ref(&self) -> &Utf8Path {
self
}
}
impl AsRef<Utf8Path> for Utf8PathBuf {
#[inline]
fn as_ref(&self) -> &Utf8Path {
self.as_path()
}
}
impl AsRef<Utf8Path> for str {
#[inline]
fn as_ref(&self) -> &Utf8Path {
Utf8Path::new(self)
}
}
impl AsRef<Utf8Path> for String {
#[inline]
fn as_ref(&self) -> &Utf8Path {
Utf8Path::new(self)
}
}
impl AsRef<std::path::Path> for Utf8Path {
#[inline]
fn as_ref(&self) -> &std::path::Path {
self.0.as_ref()
}
}
impl AsRef<std::path::Path> for Utf8PathBuf {
#[inline]
fn as_ref(&self) -> &std::path::Path {
self.0.as_ref()
}
}
impl AsRef<str> for Utf8Path {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Utf8PathBuf {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<Utf8Path> for Utf8PathBuf {
#[inline]
fn borrow(&self) -> &Utf8Path {
self.as_path()
}
}
impl ToOwned for Utf8Path {
type Owned = Utf8PathBuf;
#[inline]
fn to_owned(&self) -> Utf8PathBuf {
self.to_path_buf()
}
}
impl PartialEq for Utf8PathBuf {
#[inline]
fn eq(&self, other: &Utf8PathBuf) -> bool {
self.0 == other.0
}
}
impl Eq for Utf8PathBuf {}
impl std::hash::Hash for Utf8PathBuf {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_path().hash(state)
}
}
impl PartialOrd for Utf8PathBuf {
#[inline]
fn partial_cmp(&self, other: &Utf8PathBuf) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Utf8PathBuf {
fn cmp(&self, other: &Utf8PathBuf) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialEq for Utf8Path {
#[inline]
fn eq(&self, other: &Utf8Path) -> bool {
self.0.eq(&other.0)
}
}
impl Eq for Utf8Path {}
impl std::hash::Hash for Utf8Path {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl PartialOrd for Utf8Path {
#[inline]
fn partial_cmp(&self, other: &Utf8Path) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Utf8Path {
fn cmp(&self, other: &Utf8Path) -> Ordering {
self.0.cmp(&other.0)
}
}
macro_rules! impl_cmp {
($lhs:ty, $rhs: ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Utf8Path as PartialEq>::eq(self, other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Utf8Path as PartialEq>::eq(self, other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> {
<Utf8Path as PartialOrd>::partial_cmp(self, other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<Ordering> {
<Utf8Path as PartialOrd>::partial_cmp(self, other)
}
}
};
}
impl_cmp!(Utf8PathBuf, Utf8Path);
impl_cmp!(Utf8PathBuf, &'a Utf8Path);
impl_cmp!(Cow<'a, Utf8Path>, Utf8Path);
impl_cmp!(Cow<'a, Utf8Path>, &'b Utf8Path);
impl_cmp!(Cow<'a, Utf8Path>, Utf8PathBuf);
macro_rules! impl_cmp_std_path {
($lhs:ty, $rhs: ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Path as PartialEq>::eq(self.as_ref(), other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Path as PartialEq>::eq(self, other.as_ref())
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> {
<Path as PartialOrd>::partial_cmp(self.as_ref(), other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> {
<Path as PartialOrd>::partial_cmp(self, other.as_ref())
}
}
};
}
impl_cmp_std_path!(Utf8PathBuf, Path);
impl_cmp_std_path!(Utf8PathBuf, &'a Path);
impl_cmp_std_path!(Utf8PathBuf, Cow<'a, Path>);
impl_cmp_std_path!(Utf8PathBuf, PathBuf);
impl_cmp_std_path!(Utf8Path, Path);
impl_cmp_std_path!(Utf8Path, &'a Path);
impl_cmp_std_path!(Utf8Path, Cow<'a, Path>);
impl_cmp_std_path!(Utf8Path, PathBuf);
impl_cmp_std_path!(&'a Utf8Path, Path);
impl_cmp_std_path!(&'a Utf8Path, Cow<'b, Path>);
impl_cmp_std_path!(&'a Utf8Path, PathBuf);
macro_rules! impl_cmp_str {
($lhs:ty, $rhs: ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Utf8Path as PartialEq>::eq(self, Utf8Path::new(other))
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Utf8Path as PartialEq>::eq(Utf8Path::new(self), other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> {
<Utf8Path as PartialOrd>::partial_cmp(self, Utf8Path::new(other))
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> {
<Utf8Path as PartialOrd>::partial_cmp(Utf8Path::new(self), other)
}
}
};
}
impl_cmp_str!(Utf8PathBuf, str);
impl_cmp_str!(Utf8PathBuf, &'a str);
impl_cmp_str!(Utf8PathBuf, Cow<'a, str>);
impl_cmp_str!(Utf8PathBuf, String);
impl_cmp_str!(Utf8Path, str);
impl_cmp_str!(Utf8Path, &'a str);
impl_cmp_str!(Utf8Path, Cow<'a, str>);
impl_cmp_str!(Utf8Path, String);
impl_cmp_str!(&'a Utf8Path, str);
impl_cmp_str!(&'a Utf8Path, Cow<'b, str>);
impl_cmp_str!(&'a Utf8Path, String);
macro_rules! impl_cmp_os_str {
($lhs:ty, $rhs: ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
<Path as PartialEq>::eq(self.as_ref(), other.as_ref())
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
<Path as PartialEq>::eq(self.as_ref(), other.as_ref())
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<std::cmp::Ordering> {
<Path as PartialOrd>::partial_cmp(self.as_ref(), other.as_ref())
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<std::cmp::Ordering> {
<Path as PartialOrd>::partial_cmp(self.as_ref(), other.as_ref())
}
}
};
}
impl_cmp_os_str!(Utf8PathBuf, OsStr);
impl_cmp_os_str!(Utf8PathBuf, &'a OsStr);
impl_cmp_os_str!(Utf8PathBuf, Cow<'a, OsStr>);
impl_cmp_os_str!(Utf8PathBuf, OsString);
impl_cmp_os_str!(Utf8Path, OsStr);
impl_cmp_os_str!(Utf8Path, &'a OsStr);
impl_cmp_os_str!(Utf8Path, Cow<'a, OsStr>);
impl_cmp_os_str!(Utf8Path, OsString);
impl_cmp_os_str!(&'a Utf8Path, OsStr);
impl_cmp_os_str!(&'a Utf8Path, Cow<'b, OsStr>);
impl_cmp_os_str!(&'a Utf8Path, OsString);