use std::env;
use std::ffi::OsStr;
#[cfg(windows)]
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use error::*;
#[cfg(windows)]
use helper::has_executable_extension;
pub trait Checker {
fn is_valid(&self, path: &Path) -> bool;
}
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: &Checker,
) -> Result<PathBuf>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<Path>,
{
let path = PathBuf::from(&binary_name);
if path.components().count() > 1 {
if path.is_absolute() {
self.check_with_exe_extension(path, binary_checker)
.ok_or(ErrorKind::BadAbsolutePath.into())
} else {
let mut new_path = PathBuf::from(cwd.as_ref());
new_path.push(path);
self.check_with_exe_extension(new_path, binary_checker)
.ok_or(ErrorKind::BadRelativePath.into())
}
} else {
paths
.and_then(|paths| {
env::split_paths(&paths)
.map(|p| p.join(binary_name.as_ref()))
.map(|p| self.check_with_exe_extension(p, binary_checker))
.skip_while(|res| res.is_none())
.next()
})
.map(|res| res.unwrap())
.ok_or(ErrorKind::CannotFindBinaryPath.into())
}
}
#[cfg(unix)]
pub fn check_with_exe_extension<T: AsRef<Path>>(&self, path: T, binary_checker: &Checker) -> Option<PathBuf> {
if binary_checker.is_valid(path.as_ref()) {
Some(path.as_ref().to_path_buf())
} else {
None
}
}
#[cfg(windows)]
pub fn check_with_exe_extension<T: AsRef<Path>>(&self, path: T, binary_checker: &Checker) -> Option<PathBuf> {
let path_exts = env::var_os("PATHEXT").unwrap_or(OsString::from(env::consts::EXE_EXTENSION));
let exe_extension_vec = env::split_paths(&path_exts)
.filter_map(|e| e.to_str().map(|e| e.to_owned()))
.collect::<Vec<_>>();
if has_executable_extension(&path, &exe_extension_vec) {
if binary_checker.is_valid(path.as_ref()) {
Some(path.as_ref().to_path_buf())
} else {
None
}
} else {
exe_extension_vec.iter()
.map(|e| {
let mut s = path.as_ref().to_path_buf().into_os_string();
s.push(e);
PathBuf::from(s)
})
.skip_while(|p| !(binary_checker.is_valid(p)))
.next()
}
}
}