| use error::*; |
| #[cfg(windows)] |
| use helper::has_executable_extension; |
| use std::env; |
| use std::ffi::OsStr; |
| #[cfg(windows)] |
| use std::ffi::OsString; |
| 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: &dyn Checker, |
| ) -> Result<PathBuf> |
| where |
| T: AsRef<OsStr>, |
| U: AsRef<OsStr>, |
| V: AsRef<Path>, |
| { |
| let path = PathBuf::from(&binary_name); |
| |
| let binary_path_candidates: Box<dyn Iterator<Item = _>> = if path.has_separator() { |
| // Search binary in cwd if the path have a path separator. |
| let candidates = Self::cwd_search_candidates(path, cwd).into_iter(); |
| Box::new(candidates) |
| } else { |
| // Search binary in PATHs(defined in environment variable). |
| let p = paths.ok_or(Error::CannotFindBinaryPath)?; |
| let paths: Vec<_> = env::split_paths(&p).collect(); |
| |
| let candidates = Self::path_search_candidates(path, paths).into_iter(); |
| |
| Box::new(candidates) |
| }; |
| |
| for p in binary_path_candidates { |
| // find a valid binary |
| if binary_checker.is_valid(&p) { |
| return Ok(p); |
| } |
| } |
| |
| // can't find any binary |
| Err(Error::CannotFindBinaryPath) |
| } |
| |
| 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>, |
| { |
| // Read PATHEXT env variable and split it into vector of String |
| 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<_>>(); |
| |
| paths |
| .into_iter() |
| .flat_map(move |p| -> Box<dyn Iterator<Item = _>> { |
| // Check if path already have executable extension |
| if has_executable_extension(&p, &exe_extension_vec) { |
| Box::new(iter::once(p)) |
| } else { |
| // Appended paths with windows executable extensions. |
| // e.g. path `c:/windows/bin` will expend to: |
| // c:/windows/bin.COM |
| // c:/windows/bin.EXE |
| // c:/windows/bin.CMD |
| // ... |
| let ps = exe_extension_vec.clone().into_iter().map(move |e| { |
| // Append the extension. |
| let mut p = p.clone().to_path_buf().into_os_string(); |
| p.push(e); |
| |
| PathBuf::from(p) |
| }); |
| |
| Box::new(ps) |
| } |
| }) |
| } |
| } |