aboutsummaryrefslogtreecommitdiff
path: root/src/finder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/finder.rs')
-rw-r--r--src/finder.rs155
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)
+ }
+ })
+ }
+}