Skip to content

Commit

Permalink
add support for user provided closure to receive nonfatal errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Xaeroxe committed Nov 1, 2024
1 parent 7c58cf0 commit 66128a7
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 104 deletions.
88 changes: 66 additions & 22 deletions src/checker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::finder::Checker;
use crate::{NonFatalError, NonFatalErrorHandler};
use std::fs;
use std::path::Path;

Expand All @@ -12,16 +13,32 @@ impl ExecutableChecker {

impl Checker for ExecutableChecker {
#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
fn is_valid(&self, path: &Path) -> bool {
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
use std::io;

use rustix::fs as rfs;
let ret = rfs::access(path, rfs::Access::EXEC_OK).is_ok();
let ret = rfs::access(path, rfs::Access::EXEC_OK)
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
e.raw_os_error(),
)))
})
.is_ok();
#[cfg(feature = "tracing")]
tracing::trace!("{} EXEC_OK = {ret}", path.display());
ret
}

#[cfg(windows)]
fn is_valid(&self, _path: &Path) -> bool {
fn is_valid<F: NonFatalErrorHandler>(
&self,
_path: &Path,
_nonfatal_error_handler: &mut F,
) -> bool {
true
}
}
Expand All @@ -36,7 +53,11 @@ impl ExistedChecker {

impl Checker for ExistedChecker {
#[cfg(target_os = "windows")]
fn is_valid(&self, path: &Path) -> bool {
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
let ret = fs::symlink_metadata(path)
.map(|metadata| {
let file_type = metadata.file_type();
Expand All @@ -49,8 +70,11 @@ impl Checker for ExistedChecker {
);
file_type.is_file() || file_type.is_symlink()
})
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(e));
})
.unwrap_or(false)
&& (path.extension().is_some() || matches_arch(path));
&& (path.extension().is_some() || matches_arch(path, nonfatal_error_handler));
#[cfg(feature = "tracing")]
tracing::trace!(
"{} has_extension = {}, ExistedChecker::is_valid() = {ret}",
Expand All @@ -61,43 +85,63 @@ impl Checker for ExistedChecker {
}

#[cfg(not(target_os = "windows"))]
fn is_valid(&self, path: &Path) -> bool {
let ret = fs::metadata(path)
.map(|metadata| metadata.is_file())
.unwrap_or(false);
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
let ret = fs::metadata(path).map(|metadata| metadata.is_file());
#[cfg(feature = "tracing")]
tracing::trace!("{} is_file() = {ret}", path.display());
ret
tracing::trace!("{} is_file() = {ret:?}", path.display());
match ret {
Ok(ret) => ret,
Err(e) => {
nonfatal_error_handler.handle(NonFatalError::Io(e));
false
}
}
}
}

#[cfg(target_os = "windows")]
fn matches_arch(path: &Path) -> bool {
let ret = winsafe::GetBinaryType(&path.display().to_string()).is_ok();
fn matches_arch<F: NonFatalErrorHandler>(path: &Path, nonfatal_error_handler: &mut F) -> bool {
use std::io;

let ret = winsafe::GetBinaryType(&path.display().to_string())
.map_err(|e| {
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
e.raw() as i32
)))
})
.is_ok();
#[cfg(feature = "tracing")]
tracing::trace!("{} matches_arch() = {ret}", path.display());
ret
}

pub struct CompositeChecker {
checkers: Vec<Box<dyn Checker>>,
existed_checker: ExistedChecker,
executable_checker: ExecutableChecker,
}

impl CompositeChecker {
pub fn new() -> CompositeChecker {
CompositeChecker {
checkers: Vec::new(),
executable_checker: ExecutableChecker::new(),
existed_checker: ExistedChecker::new(),
}
}

pub fn add_checker(mut self, checker: Box<dyn Checker>) -> CompositeChecker {
self.checkers.push(checker);
self
}
}

impl Checker for CompositeChecker {
fn is_valid(&self, path: &Path) -> bool {
self.checkers.iter().all(|checker| checker.is_valid(path))
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool {
self.existed_checker.is_valid(path, nonfatal_error_handler)
&& self
.executable_checker
.is_valid(path, nonfatal_error_handler)
}
}
18 changes: 17 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, io};

pub type Result<T> = std::result::Result<T, Error>;

Expand Down Expand Up @@ -26,3 +26,19 @@ impl fmt::Display for Error {
}
}
}

#[derive(Debug)]
#[non_exhaustive]
pub enum NonFatalError {
Io(io::Error),
}

impl std::error::Error for NonFatalError {}

impl fmt::Display for NonFatalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "{e}"),
}
}
}
64 changes: 40 additions & 24 deletions src/finder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::checker::CompositeChecker;
use crate::error::*;
#[cfg(windows)]
use crate::helper::has_executable_extension;
use crate::{error::*, NonFatalErrorHandler};
use either::Either;
#[cfg(feature = "regex")]
use regex::Regex;
Expand All @@ -25,7 +25,11 @@ fn home_dir() -> Option<std::path::PathBuf> {
}

pub trait Checker {
fn is_valid(&self, path: &Path) -> bool;
fn is_valid<F: NonFatalErrorHandler>(
&self,
path: &Path,
nonfatal_error_handler: &mut F,
) -> bool;
}

trait PathExt {
Expand Down Expand Up @@ -62,17 +66,18 @@ impl Finder {
Finder
}

pub fn find<T, U, V>(
pub fn find<'a, T, U, V, F: NonFatalErrorHandler + 'a>(
&self,
binary_name: T,
paths: Option<U>,
cwd: Option<V>,
binary_checker: CompositeChecker,
) -> Result<impl Iterator<Item = PathBuf>>
mut nonfatal_error_handler: F,
) -> Result<impl Iterator<Item = PathBuf> + 'a>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<Path>,
V: AsRef<Path> + 'a,
{
let path = PathBuf::from(&binary_name);

Expand All @@ -92,39 +97,40 @@ impl Finder {
path.display()
);
// Search binary in cwd if the path have a path separator.
Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
Either::Left(Self::cwd_search_candidates(path, cwd))
}
_ => {
#[cfg(feature = "tracing")]
tracing::trace!("{} has no path seperators, so only paths in PATH environment variable will be searched.", path.display());
// Search binary in PATHs(defined in environment variable).
let paths =
env::split_paths(&paths.ok_or(Error::CannotGetCurrentDirAndPathListEmpty)?)
.collect::<Vec<_>>();
let paths = paths.ok_or(Error::CannotGetCurrentDirAndPathListEmpty)?;
let paths = env::split_paths(&paths).collect::<Vec<_>>();
if paths.is_empty() {
return Err(Error::CannotGetCurrentDirAndPathListEmpty);
}

Either::Right(Self::path_search_candidates(path, paths).into_iter())
Either::Right(Self::path_search_candidates(path, paths))
}
};
let ret = binary_path_candidates
.filter(move |p| binary_checker.is_valid(p))
.map(correct_casing);
let ret = binary_path_candidates.into_iter().filter_map(move |p| {
binary_checker
.is_valid(&p, &mut nonfatal_error_handler)
.then(|| correct_casing(p, &mut nonfatal_error_handler))
});
#[cfg(feature = "tracing")]
let ret = ret.map(|p| {
let ret = ret.inspect(|p| {
tracing::debug!("found path {}", p.display());
p
});
Ok(ret)
}

#[cfg(feature = "regex")]
pub fn find_re<T>(
pub fn find_re<T, F: NonFatalErrorHandler>(
&self,
binary_regex: impl Borrow<Regex>,
paths: Option<T>,
binary_checker: CompositeChecker,
mut nonfatal_error_handler: F,
) -> Result<impl Iterator<Item = PathBuf>>
where
T: AsRef<OsStr>,
Expand All @@ -148,7 +154,7 @@ impl Finder {
false
}
})
.filter(move |p| binary_checker.is_valid(p));
.filter(move |p| binary_checker.is_valid(p, &mut nonfatal_error_handler));

Ok(matching_re)
}
Expand Down Expand Up @@ -277,14 +283,24 @@ fn tilde_expansion(p: &PathBuf) -> Cow<'_, PathBuf> {
}

#[cfg(target_os = "windows")]
fn correct_casing(mut p: PathBuf) -> PathBuf {
fn correct_casing<F: NonFatalErrorHandler>(
mut p: PathBuf,
nonfatal_error_handler: &mut F,
) -> PathBuf {
if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) {
if let Ok(iter) = fs::read_dir(parent) {
for e in iter.filter_map(std::result::Result::ok) {
if e.file_name().eq_ignore_ascii_case(file_name) {
p.pop();
p.push(e.file_name());
break;
for e in iter {
match e {
Ok(e) => {
if e.file_name().eq_ignore_ascii_case(file_name) {
p.pop();
p.push(e.file_name());
break;
}
}
Err(e) => {
nonfatal_error_handler.handle(NonFatalError::Io(e));
}
}
}
}
Expand All @@ -293,6 +309,6 @@ fn correct_casing(mut p: PathBuf) -> PathBuf {
}

#[cfg(not(target_os = "windows"))]
fn correct_casing(p: PathBuf) -> PathBuf {
fn correct_casing<F: NonFatalErrorHandler>(p: PathBuf, _nonfatal_error_handler: &mut F) -> PathBuf {
p
}
Loading

0 comments on commit 66128a7

Please sign in to comment.