From 84446f2862bcca75d4d1bba35c19493f648c5acd Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Mon, 4 Jan 2021 12:21:56 -0700 Subject: [PATCH] Add support for finding all binaries with said name on path --- Cargo.toml | 1 + src/finder.rs | 28 ++++++++------------ src/lib.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++--- tests/basic.rs | 18 +++++++++++++ 4 files changed, 97 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 20f06bf..5784145 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["os", "filesystem"] keywords = ["which", "which-rs", "unix", "command"] [dependencies] +either = "1.6" libc = "0.2.65" thiserror = "1.0" diff --git a/src/finder.rs b/src/finder.rs index d23cbaa..1b80732 100644 --- a/src/finder.rs +++ b/src/finder.rs @@ -1,3 +1,4 @@ +use either::Either; use error::*; #[cfg(windows)] use helper::has_executable_extension; @@ -7,6 +8,7 @@ use std::ffi::OsStr; use std::ffi::OsString; use std::iter; use std::path::{Path, PathBuf}; +use checker::CompositeChecker; pub trait Checker { fn is_valid(&self, path: &Path) -> bool; @@ -51,8 +53,8 @@ impl Finder { binary_name: T, paths: Option, cwd: V, - binary_checker: &dyn Checker, - ) -> Result + binary_checker: CompositeChecker, + ) -> Result> where T: AsRef, U: AsRef, @@ -60,29 +62,21 @@ impl Finder { { let path = PathBuf::from(&binary_name); - let binary_path_candidates: Box> = if path.has_separator() { + let binary_path_candidates = 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) + Either::Left(Self::cwd_search_candidates(path, cwd).into_iter()) } 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) + Either::Right(Self::path_search_candidates(path, paths).into_iter()) }; - 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) + Ok( + binary_path_candidates + .filter(move |p| binary_checker.is_valid(p)) + ) } fn cwd_search_candidates(binary_name: PathBuf, cwd: C) -> impl IntoIterator diff --git a/src/lib.rs b/src/lib.rs index a4c0c45..8d6e145 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ //! //! ``` +extern crate either; extern crate libc; extern crate thiserror; @@ -57,13 +58,28 @@ use finder::Finder; /// /// ``` pub fn which>(binary_name: T) -> Result { + which_all(binary_name).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath)) +} + +/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. +pub fn which_all>(binary_name: T) -> Result> { let cwd = env::current_dir().map_err(|_| Error::CannotGetCurrentDir)?; - which_in(binary_name, env::var_os("PATH"), &cwd) + which_in_all(binary_name, env::var_os("PATH"), cwd) } /// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. pub fn which_in(binary_name: T, paths: Option, cwd: V) -> Result +where + T: AsRef, + U: AsRef, + V: AsRef, +{ + which_in_all(binary_name, paths, cwd).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath)) +} + +/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths. +pub fn which_in_all(binary_name: T, paths: Option, cwd: V) -> Result> where T: AsRef, U: AsRef, @@ -75,7 +91,7 @@ where let finder = Finder::new(); - finder.find(binary_name, paths, cwd, &binary_checker) + finder.find(binary_name, paths, cwd, binary_checker) } /// An owned, immutable wrapper around a `PathBuf` containing the path of an executable. @@ -101,6 +117,13 @@ impl Path { which(binary_name).map(|inner| Path { inner }) } + /// Returns the paths of all executable binaries by a name. + /// + /// this calls `which_all` and maps the results into `Path`s. + pub fn all>(binary_name: T) -> Result> { + which_all(binary_name).map(|inner| inner.map(|inner| Path { inner })) + } + /// Returns the path of an executable binary by name in the path list `paths` and using the /// current working directory `cwd` to resolve relative paths. /// @@ -114,6 +137,19 @@ impl Path { which_in(binary_name, paths, cwd).map(|inner| Path { inner }) } + /// Returns all paths of an executable binary by name in the path list `paths` and using the + /// current working directory `cwd` to resolve relative paths. + /// + /// This calls `which_in_all` and maps the results into a `Path`. + pub fn all_in(binary_name: T, paths: Option, cwd: V) -> Result> + where + T: AsRef, + U: AsRef, + V: AsRef, + { + which_in_all(binary_name, paths, cwd).map(|inner| inner.map(|inner| Path { inner })) + } + /// Returns a reference to a `std::path::Path`. pub fn as_path(&self) -> &path::Path { self.inner.as_path() @@ -193,10 +229,21 @@ impl CanonicalPath { .map(|inner| CanonicalPath { inner }) } + /// Returns the canonical paths of an executable binary by name. + /// + /// This calls `which_all` and `Path::canonicalize` and maps the results into `CanonicalPath`s. + pub fn all>(binary_name: T) -> Result>> { + which_all(binary_name) + .map(|inner| inner.map(|inner| + inner.canonicalize().map_err(|_| Error::CannotCanonicalize) + .map(|inner| CanonicalPath { inner }) + )) + } + /// Returns the canonical path of an executable binary by name in the path list `paths` and /// using the current working directory `cwd` to resolve relative paths. /// - /// This calls `which` and `Path::canonicalize` and maps the result into a `CanonicalPath`. + /// This calls `which_in` and `Path::canonicalize` and maps the result into a `CanonicalPath`. pub fn new_in(binary_name: T, paths: Option, cwd: V) -> Result where T: AsRef, @@ -208,6 +255,23 @@ impl CanonicalPath { .map(|inner| CanonicalPath { inner }) } + /// Returns all of the canonical paths of an executable binary by name in the path list `paths` and + /// using the current working directory `cwd` to resolve relative paths. + /// + /// This calls `which_in_all` and `Path::canonicalize` and maps the result into a `CanonicalPath`. + pub fn all_in(binary_name: T, paths: Option, cwd: V) -> Result>> + where + T: AsRef, + U: AsRef, + V: AsRef, + { + which_in_all(binary_name, paths, cwd) + .map(|inner| inner.map(|inner| + inner.canonicalize().map_err(|_| Error::CannotCanonicalize) + .map(|inner| CanonicalPath { inner }) + )) + } + /// Returns a reference to a `std::path::Path`. pub fn as_path(&self) -> &path::Path { self.inner.as_path() diff --git a/tests/basic.rs b/tests/basic.rs index 24a700d..f3c02c7 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -87,6 +87,10 @@ fn _which>(f: &TestFixture, path: T) -> which::Result>(f: &TestFixture, path: T) -> which::Result>> { + which::CanonicalPath::all_in(path, Some(f.paths.clone()), f.tempdir.path().to_path_buf()) +} + #[test] #[cfg(unix)] fn it_works() { @@ -147,6 +151,20 @@ fn test_which_second() { assert_eq!(_which(&f, "another").unwrap(), b); } +#[test] +fn test_which_all() { + let f = TestFixture::new(); + let actual = _which_all(&f, BIN_NAME).unwrap().map(|c| c.unwrap()).collect::>(); + let mut expected = f.bins.iter().map(|p| p.canonicalize().unwrap()).collect::>(); + #[cfg(windows)] { + expected.retain(|p| p.extension().map(|ext| ext == "exe" || ext == "cmd") == Some(true)); + } + #[cfg(not(windows))] { + expected.retain(|p| p.file_name().unwrap() == BIN_NAME); + } + assert_eq!(actual, expected); +} + #[test] #[cfg(unix)] fn test_which_absolute() {