diff options
Diffstat (limited to 'src/finder.rs')
-rw-r--r-- | src/finder.rs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/finder.rs b/src/finder.rs new file mode 100644 index 0000000..2519aa8 --- /dev/null +++ b/src/finder.rs @@ -0,0 +1,155 @@ +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(ErrorKind::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 + return Err(ErrorKind::CannotFindBinaryPath.into()); + } + + 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) + } + }) + } +} |