use crate::checker::CompositeChecker;
use crate::error::*;
#[cfg(windows)]
use crate::helper::has_executable_extension;
use either::Either;
#[cfg(feature = "regex")]
use regex::Regex;
use std::env;
use std::ffi::OsStr;
#[cfg(feature = "regex")]
use std::fs;
use std::iter;
use std::path::{Path, PathBuf};
pub trait Checker {
fn is_valid(&self, path: &Path) -> bool;
}
trait PathExt {
fn has_separator(&self) -> bool;
fn to_absolute<P>(self, cwd: P) -> PathBuf
where
P: AsRef<Path>;
}
impl PathExt for PathBuf {
fn has_separator(&self) -> bool {
self.components().count() > 1
}
fn to_absolute<P>(self, cwd: P) -> PathBuf
where
P: AsRef<Path>,
{
if self.is_absolute() {
self
} else {
let mut new_path = PathBuf::from(cwd.as_ref());
new_path.push(self);
new_path
}
}
}
pub struct Finder;
impl Finder {
pub fn new() -> Finder {
Finder
}
pub fn find<T, U, V>(
&self,
binary_name: T,
paths: Option<U>,
cwd: V,
binary_checker: CompositeChecker,
) -> Result<impl Iterator<Item = PathBuf>>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<Path>,
{
let path = PathBuf::from(&binary_name);
let binary_path_candidates = if path.has_separator() {
Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
} else {
let p = paths.ok_or(Error::CannotFindBinaryPath)?;
let paths: Vec<_> = env::split_paths(&p).collect();
Either::Right(Self::path_search_candidates(path, paths).into_iter())
};
Ok(binary_path_candidates.filter(move |p| binary_checker.is_valid(p)))
}
#[cfg(feature = "regex")]
pub fn find_re<T>(
&self,
binary_regex: Regex,
paths: Option<T>,
binary_checker: CompositeChecker,
) -> Result<impl Iterator<Item = PathBuf>>
where
T: AsRef<OsStr>,
{
let p = paths.ok_or(Error::CannotFindBinaryPath)?;
let paths: Vec<_> = env::split_paths(&p).collect();
let matching_re = paths
.into_iter()
.flat_map(fs::read_dir)
.flatten()
.flatten()
.map(|e| e.path())
.filter(move |p| {
if let Some(unicode_file_name) = p.file_name().unwrap().to_str() {
binary_regex.is_match(unicode_file_name)
} else {
false
}
})
.filter(move |p| binary_checker.is_valid(p));
Ok(matching_re)
}
fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>
where
C: AsRef<Path>,
{
let path = binary_name.to_absolute(cwd);
Self::append_extension(iter::once(path))
}
fn path_search_candidates<P>(
binary_name: PathBuf,
paths: P,
) -> impl IntoIterator<Item = PathBuf>
where
P: IntoIterator<Item = PathBuf>,
{
let new_paths = paths.into_iter().map(move |p| p.join(binary_name.clone()));
Self::append_extension(new_paths)
}
#[cfg(unix)]
fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
where
P: IntoIterator<Item = PathBuf>,
{
paths
}
#[cfg(windows)]
fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf>
where
P: IntoIterator<Item = PathBuf>,
{
lazy_static! {
static ref PATH_EXTENSIONS: Vec<String> =
env::var("PATHEXT")
.map(|pathext| {
pathext.split(';')
.filter_map(|s| {
if s.as_bytes()[0] == b'.' {
Some(s.to_owned())
} else {
None
}
})
.collect()
})
.unwrap_or(vec![]);
}
paths
.into_iter()
.flat_map(move |p| -> Box<dyn Iterator<Item = _>> {
if has_executable_extension(&p, &PATH_EXTENSIONS) {
Box::new(iter::once(p))
} else {
Box::new(PATH_EXTENSIONS.iter().map(move |e| {
let mut p = p.clone().into_os_string();
p.push(e);
PathBuf::from(p)
}))
}
})
}
}