use std::borrow::Cow;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::path::Path;
use std::path::PathBuf;
pub trait SysReadDirEntry {
fn file_name(&self) -> OsString;
fn path(&self) -> PathBuf;
}
pub trait SysMetadata {
fn is_symlink(&self) -> bool;
fn is_file(&self) -> bool;
}
pub trait Sys {
type ReadDirEntry: SysReadDirEntry;
type Metadata: SysMetadata;
fn is_windows(&self) -> bool;
fn current_dir(&self) -> io::Result<PathBuf>;
fn home_dir(&self) -> Option<PathBuf>;
fn env_split_paths(&self, paths: &OsStr) -> Vec<PathBuf>;
fn env_path(&self) -> Option<OsString>;
fn env_path_ext(&self) -> Option<OsString>;
fn env_windows_path_ext(&self) -> Cow<'static, [String]> {
Cow::Owned(parse_path_ext(self.env_path_ext()))
}
fn metadata(&self, path: &Path) -> io::Result<Self::Metadata>;
fn symlink_metadata(&self, path: &Path) -> io::Result<Self::Metadata>;
fn read_dir(
&self,
path: &Path,
) -> io::Result<Box<dyn Iterator<Item = io::Result<Self::ReadDirEntry>>>>;
fn is_valid_executable(&self, path: &Path) -> io::Result<bool>;
}
impl SysReadDirEntry for std::fs::DirEntry {
fn file_name(&self) -> OsString {
self.file_name()
}
fn path(&self) -> PathBuf {
self.path()
}
}
impl SysMetadata for std::fs::Metadata {
fn is_symlink(&self) -> bool {
self.file_type().is_symlink()
}
fn is_file(&self) -> bool {
self.file_type().is_file()
}
}
#[cfg(feature = "real-sys")]
#[derive(Default, Clone, Copy)]
pub struct RealSys;
#[cfg(feature = "real-sys")]
impl RealSys {
#[inline]
pub(crate) fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
#[allow(clippy::disallowed_methods)] std::fs::canonicalize(path)
}
}
#[cfg(feature = "real-sys")]
impl Sys for RealSys {
type ReadDirEntry = std::fs::DirEntry;
type Metadata = std::fs::Metadata;
#[inline]
fn is_windows(&self) -> bool {
cfg!(windows)
}
#[inline]
fn current_dir(&self) -> io::Result<PathBuf> {
#[allow(clippy::disallowed_methods)] std::env::current_dir()
}
#[inline]
fn home_dir(&self) -> Option<PathBuf> {
#[cfg(any(windows, unix, target_os = "redox"))]
{
env_home::env_home_dir()
}
#[cfg(not(any(windows, unix, target_os = "redox")))]
{
None
}
}
#[inline]
fn env_split_paths(&self, paths: &OsStr) -> Vec<PathBuf> {
#[allow(clippy::disallowed_methods)] std::env::split_paths(paths).collect()
}
fn env_windows_path_ext(&self) -> Cow<'static, [String]> {
use std::sync::OnceLock;
static PATH_EXTENSIONS: OnceLock<Vec<String>> = OnceLock::new();
let path_extensions = PATH_EXTENSIONS.get_or_init(|| parse_path_ext(self.env_path_ext()));
Cow::Borrowed(path_extensions)
}
#[inline]
fn env_path(&self) -> Option<OsString> {
#[allow(clippy::disallowed_methods)] std::env::var_os("PATH")
}
#[inline]
fn env_path_ext(&self) -> Option<OsString> {
#[allow(clippy::disallowed_methods)] std::env::var_os("PATHEXT")
}
#[inline]
fn read_dir(
&self,
path: &Path,
) -> io::Result<Box<dyn Iterator<Item = io::Result<Self::ReadDirEntry>>>> {
#[allow(clippy::disallowed_methods)] let iter = std::fs::read_dir(path)?;
Ok(Box::new(iter))
}
#[inline]
fn metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
#[allow(clippy::disallowed_methods)] std::fs::metadata(path)
}
#[inline]
fn symlink_metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
#[allow(clippy::disallowed_methods)] std::fs::symlink_metadata(path)
}
#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
fn is_valid_executable(&self, path: &Path) -> io::Result<bool> {
use rustix::fs as rfs;
rfs::access(path, rfs::Access::EXEC_OK)
.map(|_| true)
.map_err(|e| io::Error::from_raw_os_error(e.raw_os_error()))
}
#[cfg(windows)]
fn is_valid_executable(&self, path: &Path) -> io::Result<bool> {
winsafe::GetBinaryType(&path.display().to_string())
.map(|_| true)
.map_err(|e| io::Error::from_raw_os_error(e.raw() as i32))
}
}
impl<T> Sys for &T
where
T: Sys,
{
type ReadDirEntry = T::ReadDirEntry;
type Metadata = T::Metadata;
fn is_windows(&self) -> bool {
(*self).is_windows()
}
fn current_dir(&self) -> io::Result<PathBuf> {
(*self).current_dir()
}
fn home_dir(&self) -> Option<PathBuf> {
(*self).home_dir()
}
fn env_split_paths(&self, paths: &OsStr) -> Vec<PathBuf> {
(*self).env_split_paths(paths)
}
fn env_path(&self) -> Option<OsString> {
(*self).env_path()
}
fn env_path_ext(&self) -> Option<OsString> {
(*self).env_path_ext()
}
fn metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
(*self).metadata(path)
}
fn symlink_metadata(&self, path: &Path) -> io::Result<Self::Metadata> {
(*self).symlink_metadata(path)
}
fn read_dir(
&self,
path: &Path,
) -> io::Result<Box<dyn Iterator<Item = io::Result<Self::ReadDirEntry>>>> {
(*self).read_dir(path)
}
fn is_valid_executable(&self, path: &Path) -> io::Result<bool> {
(*self).is_valid_executable(path)
}
}
fn parse_path_ext(pathext: Option<OsString>) -> Vec<String> {
pathext
.and_then(|pathext| {
#[allow(clippy::manual_ok_err)]
match pathext.into_string() {
Ok(pathext) => Some(pathext),
Err(_) => {
#[cfg(feature = "tracing")]
tracing::error!("pathext is not valid unicode");
None
}
}
})
.map(|pathext| {
pathext
.split(';')
.filter_map(|s| {
if s.as_bytes().first() == Some(&b'.') {
Some(s.to_owned())
} else {
#[cfg(feature = "tracing")]
tracing::debug!("PATHEXT segment \"{s}\" missing leading dot, ignoring");
None
}
})
.collect()
})
.unwrap_or_default()
}