Add support for finding all binaries with said name on path

This commit is contained in:
Jacob Kiesel 2021-01-04 12:21:56 -07:00
parent 3c34de1617
commit 84446f2862
4 changed files with 97 additions and 20 deletions

View File

@ -11,6 +11,7 @@ categories = ["os", "filesystem"]
keywords = ["which", "which-rs", "unix", "command"]
[dependencies]
either = "1.6"
libc = "0.2.65"
thiserror = "1.0"

View File

@ -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<U>,
cwd: V,
binary_checker: &dyn Checker,
) -> Result<PathBuf>
binary_checker: CompositeChecker,
) -> Result<impl Iterator<Item = PathBuf>>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
@ -60,29 +62,21 @@ impl Finder {
{
let path = PathBuf::from(&binary_name);
let binary_path_candidates: Box<dyn Iterator<Item = _>> = 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<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>

View File

@ -14,6 +14,7 @@
//!
//! ```
extern crate either;
extern crate libc;
extern crate thiserror;
@ -57,13 +58,28 @@ use finder::Finder;
///
/// ```
pub fn which<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> {
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<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item=path::PathBuf>> {
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<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<path::PathBuf>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<path::Path>,
{
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<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<impl Iterator<Item=path::PathBuf>>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
@ -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<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item=Path>> {
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<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<impl Iterator<Item=Path>>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<path::Path>,
{
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<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item=Result<CanonicalPath>>> {
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<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<CanonicalPath>
where
T: AsRef<OsStr>,
@ -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<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<impl Iterator<Item=Result<CanonicalPath>>>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
V: AsRef<path::Path>,
{
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()

View File

@ -87,6 +87,10 @@ fn _which<T: AsRef<OsStr>>(f: &TestFixture, path: T) -> which::Result<which::Can
which::CanonicalPath::new_in(path, Some(f.paths.clone()), f.tempdir.path())
}
fn _which_all<T: AsRef<OsStr>>(f: &TestFixture, path: T) -> which::Result<impl Iterator<Item=which::Result<which::CanonicalPath>>> {
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::<Vec<_>>();
let mut expected = f.bins.iter().map(|p| p.canonicalize().unwrap()).collect::<Vec<_>>();
#[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() {