#[cfg(unix)]
use std::os::unix::fs::PermissionsExt as _;
use std::{
borrow::Cow,
ffi::OsStr,
fs::{Metadata, Permissions},
io,
path::{self, Path, PathBuf},
};
#[cfg(unix)]
use pathdiff::diff_paths;
use tokio::{
fs::{self, File, ReadDir},
io::AsyncWriteExt,
};
use super::{fs::FileExt, temp_path::TempPath};
#[trait_variant::make(Send)]
pub trait PathExt {
fn base(&self) -> io::Result<&Path>;
fn with_base(&self, base: impl AsRef<Path>) -> Cow<'_, Path>;
fn file_suffix(&self) -> Option<&OsStr>;
fn absolute(&self) -> io::Result<PathBuf>;
async fn metadata_async(&self) -> io::Result<Metadata>;
async fn symlink_metadata_async(&self) -> io::Result<Metadata>;
async fn canonicalize_async(&self) -> io::Result<PathBuf>;
async fn read_link_async(&self) -> io::Result<PathBuf>;
async fn read_dir_async(&self) -> io::Result<ReadDir>;
async fn exists_async(&self) -> bool;
async fn is_file_async(&self) -> bool;
async fn is_dir_async(&self) -> bool;
async fn is_symlink_async(&self) -> bool;
async fn try_exists_async(&self) -> io::Result<bool>;
async fn try_is_file(&self) -> io::Result<bool>;
async fn try_is_dir(&self) -> io::Result<bool>;
async fn try_is_symlink(&self) -> io::Result<bool>;
async fn metadata_if_exists(&self) -> io::Result<Option<Metadata>>;
async fn symlink_metadata_if_exists(&self) -> io::Result<Option<Metadata>>;
async fn canonicalize_if_exists(&self) -> io::Result<Option<PathBuf>>;
async fn read_link_if_exists(&self) -> io::Result<Option<PathBuf>>;
async fn read_dir_if_exists(&self) -> io::Result<Option<ReadDir>>;
async fn exists_nofollow(&self) -> bool;
async fn is_file_nofollow(&self) -> bool;
async fn is_dir_nofollow(&self) -> bool;
async fn try_exists_nofollow(&self) -> io::Result<bool>;
async fn try_is_file_nofollow(&self) -> io::Result<bool>;
async fn try_is_dir_nofollow(&self) -> io::Result<bool>;
async fn try_is_read_dir_empty(&self) -> io::Result<bool>;
async fn try_is_read_dir_empty_if_exists(&self) -> io::Result<Option<bool>>;
async fn create_new(&self) -> io::Result<File>;
async fn create_new_if_not_exists(&self) -> io::Result<Option<File>>;
async fn create(&self) -> io::Result<File>;
async fn create_if_not_exists(&self) -> io::Result<Option<File>>;
async fn open(&self) -> io::Result<File>;
async fn open_if_exists(&self) -> io::Result<Option<File>>;
async fn read(&self) -> io::Result<Vec<u8>>;
async fn read_if_exists(&self) -> io::Result<Option<Vec<u8>>>;
async fn read_to_string(&self) -> io::Result<String>;
async fn read_to_string_if_exists(&self) -> io::Result<Option<String>>;
async fn create_dir_all(self) -> io::Result<Self>
where
Self: Sized;
async fn create_dir(self) -> io::Result<Self>
where
Self: Sized;
async fn create_dir_if_not_exists(self) -> io::Result<Option<Self>>
where
Self: Sized;
async fn write_new(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self>
where
Self: Sized;
async fn write_new_if_not_exists(
self,
contents: impl AsRef<[u8]> + Send,
) -> io::Result<Option<Self>>
where
Self: Sized;
async fn write(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self>
where
Self: Sized;
async fn write_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<Self>>
where
Self: Sized;
async fn append(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self>
where
Self: Sized;
async fn append_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<Self>>
where
Self: Sized;
async fn copy(self, to: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
async fn copy_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
where
Self: Sized;
async fn rename(self, to: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
async fn rename_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
where
Self: Sized;
async fn remove_file(self) -> io::Result<Self>
where
Self: Sized;
async fn remove_file_if_exists(self) -> io::Result<Option<Self>>
where
Self: Sized;
async fn remove_dir(self) -> io::Result<Self>
where
Self: Sized;
async fn remove_dir_if_exists(self) -> io::Result<Option<Self>>
where
Self: Sized;
async fn remove_dir_all(self) -> io::Result<Self>
where
Self: Sized;
async fn remove_dir_all_if_exists(self) -> io::Result<Option<Self>>
where
Self: Sized;
async fn hard_link(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
async fn hard_link_if_exists(self, link: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
where
Self: Sized;
async fn hard_link_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
async fn hard_link_atomic_if_exists(
self,
link: impl AsRef<Path> + Send,
) -> io::Result<Option<Self>>
where
Self: Sized;
#[cfg(unix)]
async fn symlink(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn symlink_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn symlink_absolute(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn symlink_absolute_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn symlink_relative(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn symlink_relative_atomic(
self,
link: impl AsRef<Path> + Send + Sync,
) -> io::Result<Self>
where
Self: Sized;
async fn set_permissions(self, perm: Permissions) -> io::Result<Self>
where
Self: Sized;
async fn set_permissions_if_exists(self, perm: Permissions) -> io::Result<Option<Self>>
where
Self: Sized;
async fn set_permissions_readonly(self, readonly: bool) -> io::Result<Self>
where
Self: Sized;
async fn set_permissions_readonly_if_exists(self, readonly: bool) -> io::Result<Option<Self>>
where
Self: Sized;
#[cfg(unix)]
async fn set_permissions_mode(self, mode: u32) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn set_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
where
Self: Sized;
#[cfg(unix)]
async fn add_permissions_mode(self, mode: u32) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn add_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
where
Self: Sized;
#[cfg(unix)]
async fn remove_permissions_mode(self, mode: u32) -> io::Result<Self>
where
Self: Sized;
#[cfg(unix)]
async fn remove_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
where
Self: Sized;
}
impl<T: AsRef<Path> + Send + Sync> PathExt for T {
#[inline]
fn base(&self) -> io::Result<&Path> {
self.as_ref()
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Path has no parent"))
}
#[inline]
fn with_base(&self, base: impl AsRef<Path>) -> Cow<'_, Path> {
if self.as_ref().is_relative() {
let path = base.as_ref().join(self);
Cow::Owned(path)
} else {
Cow::Borrowed(self.as_ref())
}
}
#[inline]
fn file_suffix(&self) -> Option<&OsStr> {
let file_name = self.as_ref().file_name()?;
let file_prefix = self.as_ref().file_prefix()?;
let bytes = file_name.as_encoded_bytes();
let start = file_prefix.as_encoded_bytes().len() + 1;
if start <= bytes.len() {
let slice = &bytes[start..];
let os_str = unsafe { OsStr::from_encoded_bytes_unchecked(slice) };
Some(os_str)
} else {
None
}
}
#[inline]
fn absolute(&self) -> io::Result<PathBuf> {
path::absolute(self)
}
#[inline]
async fn metadata_async(&self) -> io::Result<Metadata> {
fs::metadata(self).await
}
#[inline]
async fn symlink_metadata_async(&self) -> io::Result<Metadata> {
fs::symlink_metadata(self).await
}
#[inline]
async fn canonicalize_async(&self) -> io::Result<PathBuf> {
fs::canonicalize(self).await
}
#[inline]
async fn read_link_async(&self) -> io::Result<PathBuf> {
fs::read_link(self).await
}
#[inline]
async fn read_dir_async(&self) -> io::Result<ReadDir> {
fs::read_dir(self).await
}
#[inline]
async fn exists_async(&self) -> bool {
self.metadata_async().await.is_ok()
}
#[inline]
async fn is_file_async(&self) -> bool {
self.metadata_async()
.await
.map(|meta| meta.is_file())
.unwrap_or(false)
}
#[inline]
async fn is_dir_async(&self) -> bool {
self.metadata_async()
.await
.map(|meta| meta.is_dir())
.unwrap_or(false)
}
#[inline]
async fn is_symlink_async(&self) -> bool {
self.symlink_metadata_async()
.await
.map(|meta| meta.is_symlink())
.unwrap_or(false)
}
#[inline]
async fn try_exists_async(&self) -> io::Result<bool> {
fs::try_exists(self).await
}
#[inline]
async fn try_is_file(&self) -> io::Result<bool> {
self.metadata_if_exists()
.await
.map(|meta_opt| meta_opt.map(|meta| meta.is_file()).unwrap_or(false))
}
#[inline]
async fn try_is_dir(&self) -> io::Result<bool> {
self.metadata_if_exists()
.await
.map(|meta_opt| meta_opt.map(|meta| meta.is_dir()).unwrap_or(false))
}
#[inline]
async fn try_is_symlink(&self) -> io::Result<bool> {
self.symlink_metadata_if_exists()
.await
.map(|meta_opt| meta_opt.map(|meta| meta.is_symlink()).unwrap_or(false))
}
#[inline]
async fn metadata_if_exists(&self) -> io::Result<Option<Metadata>> {
match self.metadata_async().await {
Ok(meta) => Ok(Some(meta)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn symlink_metadata_if_exists(&self) -> io::Result<Option<Metadata>> {
match self.symlink_metadata_async().await {
Ok(meta) => Ok(Some(meta)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn canonicalize_if_exists(&self) -> io::Result<Option<PathBuf>> {
match self.canonicalize_async().await {
Ok(path) => Ok(Some(path)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn read_link_if_exists(&self) -> io::Result<Option<PathBuf>> {
match self.read_link_async().await {
Ok(path) => Ok(Some(path)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn read_dir_if_exists(&self) -> io::Result<Option<ReadDir>> {
match self.read_dir_async().await {
Ok(entries) => Ok(Some(entries)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn exists_nofollow(&self) -> bool {
self.symlink_metadata_async().await.is_ok()
}
#[inline]
async fn is_file_nofollow(&self) -> bool {
self.symlink_metadata_async()
.await
.map(|meta| meta.is_file())
.unwrap_or(false)
}
#[inline]
async fn is_dir_nofollow(&self) -> bool {
self.symlink_metadata_async()
.await
.map(|meta| meta.is_dir())
.unwrap_or(false)
}
#[inline]
async fn try_exists_nofollow(&self) -> io::Result<bool> {
self.symlink_metadata_if_exists()
.await
.map(|meta_opt| meta_opt.is_some())
}
#[inline]
async fn try_is_file_nofollow(&self) -> io::Result<bool> {
self.symlink_metadata_if_exists()
.await
.map(|meta_opt| meta_opt.map(|meta| meta.is_file()).unwrap_or(false))
}
#[inline]
async fn try_is_dir_nofollow(&self) -> io::Result<bool> {
self.symlink_metadata_if_exists()
.await
.map(|meta_opt| meta_opt.map(|meta| meta.is_dir()).unwrap_or(false))
}
#[inline]
async fn try_is_read_dir_empty(&self) -> io::Result<bool> {
let mut entries = self.read_dir_async().await?;
entries
.next_entry()
.await
.map(|entry_opt| entry_opt.is_none())
}
#[inline]
async fn try_is_read_dir_empty_if_exists(&self) -> io::Result<Option<bool>> {
match self.try_is_read_dir_empty().await {
Ok(is_empty) => Ok(Some(is_empty)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn create_new(&self) -> io::Result<File> {
File::create_new(self).await
}
#[inline]
async fn create_new_if_not_exists(&self) -> io::Result<Option<File>> {
File::create_new_if_not_exists(self).await
}
#[inline]
async fn create(&self) -> io::Result<File> {
File::create(self).await
}
#[inline]
async fn create_if_not_exists(&self) -> io::Result<Option<File>> {
File::create_if_not_exists(self).await
}
#[inline]
async fn open(&self) -> io::Result<File> {
File::open(self).await
}
#[inline]
async fn open_if_exists(&self) -> io::Result<Option<File>> {
File::open_if_exists(self).await
}
#[inline]
async fn read(&self) -> io::Result<Vec<u8>> {
fs::read(self).await
}
#[inline]
async fn read_if_exists(&self) -> io::Result<Option<Vec<u8>>> {
match self.read().await {
Ok(bytes) => Ok(Some(bytes)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn read_to_string(&self) -> io::Result<String> {
fs::read_to_string(self).await
}
#[inline]
async fn read_to_string_if_exists(&self) -> io::Result<Option<String>> {
match self.read_to_string().await {
Ok(string) => Ok(Some(string)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn create_dir_all(self) -> io::Result<Self> {
fs::create_dir_all(self.as_ref()).await?;
Ok(self)
}
#[inline]
async fn create_dir(self) -> io::Result<Self> {
fs::create_dir(self.as_ref()).await?;
Ok(self)
}
#[inline]
async fn create_dir_if_not_exists(self) -> io::Result<Option<Self>> {
match self.as_ref().create_dir().await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn write_new(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self> {
let mut options = File::options();
options.write(true).create_new(true);
let mut file = options.open(self.as_ref()).await?;
file.write_all(contents.as_ref()).await?;
Ok(self)
}
#[inline]
async fn write_new_if_not_exists(
self,
contents: impl AsRef<[u8]> + Send,
) -> io::Result<Option<Self>> {
match self.as_ref().write_new(contents).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn write(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self> {
fs::write(self.as_ref(), contents).await?;
Ok(self)
}
#[inline]
async fn write_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<Self>> {
let mut options = File::options();
options.write(true).truncate(true);
match options.open(self.as_ref()).await {
Ok(mut file) => {
file.write_all(contents.as_ref()).await?;
Ok(Some(self))
},
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn append(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Self> {
let mut options = File::options();
options.append(true).create(true);
let mut file = options.open(self.as_ref()).await?;
file.write_all(contents.as_ref()).await?;
Ok(self)
}
#[inline]
async fn append_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<Self>> {
let mut options = File::options();
options.append(true);
match options.open(self.as_ref()).await {
Ok(mut file) => {
file.write_all(contents.as_ref()).await?;
Ok(Some(self))
},
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn copy(self, to: impl AsRef<Path> + Send) -> io::Result<Self> {
fs::copy(self.as_ref(), to).await?;
Ok(self)
}
#[inline]
async fn copy_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
match self.as_ref().copy(to).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
if !self.try_exists_async().await? {
Ok(None)
} else {
Err(err)
}
},
Err(err) => Err(err),
}
}
#[inline]
async fn rename(self, to: impl AsRef<Path> + Send) -> io::Result<Self> {
fs::rename(self.as_ref(), to).await?;
Ok(self)
}
#[inline]
async fn rename_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
match self.as_ref().rename(to).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
if !self.try_exists_nofollow().await? {
Ok(None)
} else {
Err(err)
}
},
Err(err) => Err(err),
}
}
#[inline]
async fn remove_file(self) -> io::Result<Self> {
fs::remove_file(self.as_ref()).await?;
Ok(self)
}
#[inline]
async fn remove_file_if_exists(self) -> io::Result<Option<Self>> {
match self.as_ref().remove_file().await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn remove_dir(self) -> io::Result<Self> {
fs::remove_dir(self.as_ref()).await?;
Ok(self)
}
#[inline]
async fn remove_dir_if_exists(self) -> io::Result<Option<Self>> {
match self.as_ref().remove_dir().await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn remove_dir_all(self) -> io::Result<Self> {
fs::remove_dir_all(self.as_ref()).await?;
Ok(self)
}
#[inline]
async fn remove_dir_all_if_exists(self) -> io::Result<Option<Self>> {
match self.as_ref().remove_dir_all().await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn hard_link(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
fs::hard_link(self.as_ref(), link).await?;
Ok(self)
}
#[inline]
async fn hard_link_if_exists(self, link: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
match self.as_ref().hard_link(link).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
if !self.try_exists_nofollow().await? {
Ok(None)
} else {
Err(err)
}
},
Err(err) => Err(err),
}
}
#[inline]
async fn hard_link_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
let temp = TempPath::try_from_path(link.as_ref())?;
self.as_ref().hard_link(temp.path()).await?;
temp.persist(link).await?;
Ok(self)
}
#[inline]
async fn hard_link_atomic_if_exists(
self,
link: impl AsRef<Path> + Send,
) -> io::Result<Option<Self>> {
match self.as_ref().hard_link_atomic(link).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
if !self.try_exists_nofollow().await? {
Ok(None)
} else {
Err(err)
}
},
Err(err) => Err(err),
}
}
#[cfg(unix)]
#[inline]
async fn symlink(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
fs::symlink(self.as_ref(), link).await?;
Ok(self)
}
#[cfg(unix)]
#[inline]
async fn symlink_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
let temp = TempPath::try_from_path(link.as_ref())?;
self.as_ref().symlink(temp.path()).await?;
temp.persist(link).await?;
Ok(self)
}
#[cfg(unix)]
#[inline]
async fn symlink_absolute(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
#[expect(unstable_name_collisions)]
let absolute = self.as_ref().absolute()?;
absolute.symlink(link).await?;
Ok(self)
}
#[cfg(unix)]
#[inline]
async fn symlink_absolute_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Self> {
#[expect(unstable_name_collisions)]
let absolute = self.as_ref().absolute()?;
absolute.symlink_atomic(link).await?;
Ok(self)
}
#[cfg(unix)]
#[inline]
async fn symlink_relative(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<Self> {
let base = link.base()?;
let relative = diff_paths(self.as_ref(), base)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Failed to diff paths"))?;
relative.symlink(link).await?;
Ok(self)
}
#[cfg(unix)]
#[inline]
async fn symlink_relative_atomic(
self,
link: impl AsRef<Path> + Send + Sync,
) -> io::Result<Self> {
let base = link.base()?;
let relative = diff_paths(self.as_ref(), base)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Failed to diff paths"))?;
relative.symlink_atomic(link).await?;
Ok(self)
}
#[inline]
async fn set_permissions(self, perm: Permissions) -> io::Result<Self> {
fs::set_permissions(self.as_ref(), perm).await?;
Ok(self)
}
#[inline]
async fn set_permissions_if_exists(self, perm: Permissions) -> io::Result<Option<Self>> {
match self.as_ref().set_permissions(perm).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[inline]
async fn set_permissions_readonly(self, readonly: bool) -> io::Result<Self> {
let meta = self.metadata_async().await?;
let mut perm = meta.permissions();
perm.set_readonly(readonly);
self.set_permissions(perm).await
}
#[inline]
async fn set_permissions_readonly_if_exists(self, readonly: bool) -> io::Result<Option<Self>> {
match self.as_ref().set_permissions_readonly(readonly).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[cfg(unix)]
#[inline]
async fn set_permissions_mode(self, mode: u32) -> io::Result<Self> {
let perm = Permissions::from_mode(mode);
self.set_permissions(perm).await
}
#[cfg(unix)]
#[inline]
async fn set_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
match self.as_ref().set_permissions_mode(mode).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[cfg(unix)]
#[inline]
async fn add_permissions_mode(self, mode: u32) -> io::Result<Self> {
let meta = self.metadata_async().await?;
let perm = meta.permissions();
self.set_permissions_mode(perm.mode() | mode).await
}
#[cfg(unix)]
#[inline]
async fn add_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
match self.as_ref().add_permissions_mode(mode).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
#[cfg(unix)]
#[inline]
async fn remove_permissions_mode(self, mode: u32) -> io::Result<Self> {
let meta = self.metadata_async().await?;
let perm = meta.permissions();
self.set_permissions_mode(perm.mode() & !mode).await
}
#[cfg(unix)]
#[inline]
async fn remove_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
match self.as_ref().remove_permissions_mode(mode).await {
Ok(_) => Ok(Some(self)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}
}
macro_rules! define_option_path_ext {
(
$(
$(#[$meta:meta])*
async fn $method:ident(self $(, $arg:ident: $ty:ty)* $(,)*) -> $ret:ty;
)*
) => {
#[trait_variant::make(Send)]
pub trait OptionPathExt<T> {
$(
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret;
)*
}
impl<T: PathExt> OptionPathExt<T> for Option<T> {
$(
#[inline]
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret {
match self {
Some(path) => path.$method($($arg),*).await.map(Some),
None => Ok(None),
}
}
)*
}
};
}
define_option_path_ext! {
async fn metadata_async(self) -> io::Result<Option<Metadata>>;
async fn symlink_metadata_async(self) -> io::Result<Option<Metadata>>;
async fn canonicalize_async(self) -> io::Result<Option<PathBuf>>;
async fn read_link_async(self) -> io::Result<Option<PathBuf>>;
async fn read_dir_async(self) -> io::Result<Option<ReadDir>>;
async fn try_exists_async(self) -> io::Result<Option<bool>>;
async fn try_is_file(self) -> io::Result<Option<bool>>;
async fn try_is_dir(self) -> io::Result<Option<bool>>;
async fn try_is_symlink(self) -> io::Result<Option<bool>>;
async fn try_exists_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_file_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_dir_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_read_dir_empty(self) -> io::Result<Option<bool>>;
async fn create_new(self) -> io::Result<Option<File>>;
async fn create(self) -> io::Result<Option<File>>;
async fn open(self) -> io::Result<Option<File>>;
async fn read(self) -> io::Result<Option<Vec<u8>>>;
async fn read_to_string(self) -> io::Result<Option<String>>;
async fn create_dir_all(self) -> io::Result<Option<T>>;
async fn create_dir(self) -> io::Result<Option<T>>;
async fn write_new(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn write(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn append(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn copy(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn rename(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn remove_file(self) -> io::Result<Option<T>>;
async fn remove_dir(self) -> io::Result<Option<T>>;
async fn remove_dir_all(self) -> io::Result<Option<T>>;
async fn hard_link(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn hard_link_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_absolute(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_absolute_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_relative(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_relative_atomic(
self,
link: impl AsRef<Path> + Send + Sync,
) -> io::Result<Option<T>>;
async fn set_permissions(self, perm: Permissions) -> io::Result<Option<T>>;
async fn set_permissions_readonly(self, readonly: bool) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn set_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn add_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn remove_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
}
macro_rules! define_async_path_ext {
(
$(
$(#[$meta:meta])*
async fn $method:ident(self $(, $arg:ident: $ty:ty)* $(,)*) -> $ret:ty;
)*
) => {
#[trait_variant::make(Send)]
pub trait AsyncPathExt<T: PathExt>: Future<Output = io::Result<T>> {
$(
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret;
)*
}
impl<T: PathExt, F: Future<Output = io::Result<T>> + Send> AsyncPathExt<T> for F {
$(
#[inline]
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret {
let path = self.await?;
path.$method($($arg),*).await
}
)*
}
};
}
define_async_path_ext! {
async fn metadata_async(self) -> io::Result<Metadata>;
async fn symlink_metadata_async(self) -> io::Result<Metadata>;
async fn canonicalize_async(self) -> io::Result<PathBuf>;
async fn read_link_async(self) -> io::Result<PathBuf>;
async fn read_dir_async(self) -> io::Result<ReadDir>;
async fn try_exists_async(self) -> io::Result<bool>;
async fn try_is_file(self) -> io::Result<bool>;
async fn try_is_dir(self) -> io::Result<bool>;
async fn try_is_symlink(self) -> io::Result<bool>;
async fn metadata_if_exists(self) -> io::Result<Option<Metadata>>;
async fn symlink_metadata_if_exists(self) -> io::Result<Option<Metadata>>;
async fn canonicalize_if_exists(self) -> io::Result<Option<PathBuf>>;
async fn read_link_if_exists(self) -> io::Result<Option<PathBuf>>;
async fn read_dir_if_exists(self) -> io::Result<Option<ReadDir>>;
async fn try_exists_nofollow(self) -> io::Result<bool>;
async fn try_is_file_nofollow(self) -> io::Result<bool>;
async fn try_is_dir_nofollow(self) -> io::Result<bool>;
async fn try_is_read_dir_empty(self) -> io::Result<bool>;
async fn try_is_read_dir_empty_if_exists(self) -> io::Result<Option<bool>>;
async fn create_new(self) -> io::Result<File>;
async fn create_new_if_not_exists(self) -> io::Result<Option<File>>;
async fn create(self) -> io::Result<File>;
async fn create_if_not_exists(self) -> io::Result<Option<File>>;
async fn open(self) -> io::Result<File>;
async fn open_if_exists(self) -> io::Result<Option<File>>;
async fn read(self) -> io::Result<Vec<u8>>;
async fn read_if_exists(self) -> io::Result<Option<Vec<u8>>>;
async fn read_to_string(self) -> io::Result<String>;
async fn read_to_string_if_exists(self) -> io::Result<Option<String>>;
async fn create_dir_all(self) -> io::Result<T>;
async fn create_dir(self) -> io::Result<T>;
async fn create_dir_if_not_exists(self) -> io::Result<Option<T>>;
async fn write_new(self, contents: impl AsRef<[u8]> + Send) -> io::Result<T>;
async fn write_new_if_not_exists(
self,
contents: impl AsRef<[u8]> + Send,
) -> io::Result<Option<T>>;
async fn write(self, contents: impl AsRef<[u8]> + Send) -> io::Result<T>;
async fn write_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn append(self, contents: impl AsRef<[u8]> + Send) -> io::Result<T>;
async fn append_if_exists(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn copy(self, to: impl AsRef<Path> + Send) -> io::Result<T>;
async fn copy_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn rename(self, to: impl AsRef<Path> + Send) -> io::Result<T>;
async fn rename_if_exists(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn remove_file(self) -> io::Result<T>;
async fn remove_file_if_exists(self) -> io::Result<Option<T>>;
async fn remove_dir(self) -> io::Result<T>;
async fn remove_dir_if_exists(self) -> io::Result<Option<T>>;
async fn remove_dir_all(self) -> io::Result<T>;
async fn remove_dir_all_if_exists(self) -> io::Result<Option<T>>;
async fn hard_link(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
async fn hard_link_if_exists(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn hard_link_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
async fn hard_link_atomic_if_exists(
self,
link: impl AsRef<Path> + Send,
) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
#[cfg(unix)]
async fn symlink_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
#[cfg(unix)]
async fn symlink_absolute(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
#[cfg(unix)]
async fn symlink_absolute_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<T>;
#[cfg(unix)]
async fn symlink_relative(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<T>;
#[cfg(unix)]
async fn symlink_relative_atomic(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<T>;
async fn set_permissions(self, perm: Permissions) -> io::Result<T>;
async fn set_permissions_if_exists(self, perm: Permissions) -> io::Result<Option<T>>;
async fn set_permissions_readonly(self, readonly: bool) -> io::Result<T>;
async fn set_permissions_readonly_if_exists(self, readonly: bool) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn set_permissions_mode(self, mode: u32) -> io::Result<T>;
#[cfg(unix)]
async fn set_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn add_permissions_mode(self, mode: u32) -> io::Result<T>;
#[cfg(unix)]
async fn add_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn remove_permissions_mode(self, mode: u32) -> io::Result<T>;
#[cfg(unix)]
async fn remove_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<T>>;
}
macro_rules! define_async_option_path_ext {
(
$(
$(#[$meta:meta])*
async fn $method:ident(self $(, $arg:ident: $ty:ty)* $(,)*) -> $ret:ty;
)*
) => {
#[trait_variant::make(Send)]
pub trait AsyncOptionPathExt<T: PathExt>: Future<Output = io::Result<Option<T>>>
where
Option<T>: OptionPathExt<T>,
{
$(
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret;
)*
}
impl<T: PathExt, F: Future<Output = io::Result<Option<T>>> + Send> AsyncOptionPathExt<T>
for F
where
Option<T>: OptionPathExt<T>,
{
$(
#[inline]
$(#[$meta])*
async fn $method(self $(, $arg: $ty)*) -> $ret {
let path = self.await?;
path.$method($($arg),*).await
}
)*
}
};
}
define_async_option_path_ext! {
async fn metadata_async(self) -> io::Result<Option<Metadata>>;
async fn symlink_metadata_async(self) -> io::Result<Option<Metadata>>;
async fn canonicalize_async(self) -> io::Result<Option<PathBuf>>;
async fn read_link_async(self) -> io::Result<Option<PathBuf>>;
async fn read_dir_async(self) -> io::Result<Option<ReadDir>>;
async fn try_exists_async(self) -> io::Result<Option<bool>>;
async fn try_is_file(self) -> io::Result<Option<bool>>;
async fn try_is_dir(self) -> io::Result<Option<bool>>;
async fn try_is_symlink(self) -> io::Result<Option<bool>>;
async fn try_exists_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_file_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_dir_nofollow(self) -> io::Result<Option<bool>>;
async fn try_is_read_dir_empty(self) -> io::Result<Option<bool>>;
async fn create_new(self) -> io::Result<Option<File>>;
async fn create(self) -> io::Result<Option<File>>;
async fn open(self) -> io::Result<Option<File>>;
async fn read(self) -> io::Result<Option<Vec<u8>>>;
async fn read_to_string(self) -> io::Result<Option<String>>;
async fn create_dir_all(self) -> io::Result<Option<T>>;
async fn create_dir(self) -> io::Result<Option<T>>;
async fn write_new(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn write(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn append(self, contents: impl AsRef<[u8]> + Send) -> io::Result<Option<T>>;
async fn copy(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn rename(self, to: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn remove_file(self) -> io::Result<Option<T>>;
async fn remove_dir(self) -> io::Result<Option<T>>;
async fn remove_dir_all(self) -> io::Result<Option<T>>;
async fn hard_link(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
async fn hard_link_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_absolute(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_absolute_atomic(self, link: impl AsRef<Path> + Send) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_relative(self, link: impl AsRef<Path> + Send + Sync) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn symlink_relative_atomic(
self,
link: impl AsRef<Path> + Send + Sync,
) -> io::Result<Option<T>>;
async fn set_permissions(self, perm: Permissions) -> io::Result<Option<T>>;
async fn set_permissions_readonly(self, readonly: bool) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn set_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn add_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
#[cfg(unix)]
async fn remove_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
}