Merge pull request #40 from marier-nico/master

Allow searching with regular expressions
This commit is contained in:
Harry Fei 2021-07-27 22:54:08 +08:00 committed by GitHub
commit 32c08ce671
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 2 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
target
Cargo.lock
.vscode/

View File

@ -14,6 +14,7 @@ keywords = ["which", "which-rs", "unix", "command"]
[dependencies]
either = "1.6"
libc = "0.2.65"
regex = { version = "1.5.4", optional = true }
[target.'cfg(windows)'.dependencies]
lazy_static = "1"

View File

@ -1,10 +1,13 @@
use crate::checker::CompositeChecker;
use crate::error::*;
use either::Either;
#[cfg(windows)]
use crate::helper::has_executable_extension;
use either::Either;
#[cfg(feature = "regex")]
use regex::Regex;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::iter;
use std::path::{Path, PathBuf};
@ -74,6 +77,37 @@ impl Finder {
Ok(binary_path_candidates.filter(move |p| binary_checker.is_valid(p)))
}
#[cfg(feature = "regex")]
pub fn find_re<T>(
&self,
binary_regex: Regex,
paths: Option<T>,
binary_checker: CompositeChecker,
) -> Result<impl Iterator<Item = PathBuf>>
where
T: AsRef<OsStr>,
{
let p = paths.ok_or(Error::CannotFindBinaryPath)?;
let paths: Vec<_> = env::split_paths(&p).collect();
let matching_re = paths
.into_iter()
.flat_map(fs::read_dir)
.flatten()
.flatten()
.map(|e| e.path())
.filter(move |p| {
if let Some(unicode_file_name) = p.file_name().unwrap().to_str() {
binary_regex.is_match(unicode_file_name)
} else {
false
}
})
.filter(move |p| binary_checker.is_valid(p));
Ok(matching_re)
}
fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf>
where
C: AsRef<Path>,

View File

@ -24,6 +24,8 @@ mod finder;
#[cfg(windows)]
mod helper;
#[cfg(feature = "regex")]
use regex::Regex;
use std::env;
use std::fmt;
use std::path;
@ -66,6 +68,29 @@ pub fn which_all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item =
which_in_all(binary_name, env::var_os("PATH"), cwd)
}
/// Find all binaries matching a regular expression in a the system PATH.
///
/// # Arguments
///
/// * `regex` - A regular expression to match binaries with
///
/// # Examples
///
/// ```no_run
/// use regex::Regex;
/// use which::which;
/// use std::path::PathBuf;
///
/// let re = Regex::new(r"python\d$").unwrap();
/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect();
/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
/// assert_eq!(binaries, python_paths);
/// ```
#[cfg(feature = "regex")]
pub fn which_re(regex: Regex) -> Result<impl Iterator<Item = path::PathBuf>> {
which_re_in(regex, env::var_os("PATH"))
}
/// 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
@ -77,6 +102,41 @@ where
.and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
}
/// Find all binaries matching a regular expression in a list of paths.
///
/// # Arguments
///
/// * `regex` - A regular expression to match binaries with
/// * `paths` - A string containing the paths to search
/// (separated in the same way as the PATH environment variable)
///
/// # Examples
///
/// ```no_run
/// use regex::Regex;
/// use which::which;
/// use std::path::PathBuf;
///
/// let re = Regex::new(r"python\d$").unwrap();
/// let paths = Some("/usr/bin:/usr/local/bin");
/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect();
/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
/// assert_eq!(binaries, python_paths);
/// ```
#[cfg(feature = "regex")]
pub fn which_re_in<T>(regex: Regex, paths: Option<T>) -> Result<impl Iterator<Item = path::PathBuf>>
where
T: AsRef<OsStr>,
{
let binary_checker = CompositeChecker::new()
.add_checker(Box::new(ExistedChecker::new()))
.add_checker(Box::new(ExecutableChecker::new()));
let finder = Finder::new();
finder.find_re(regex, paths, binary_checker)
}
/// 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,

View File

@ -1,11 +1,13 @@
extern crate tempdir;
extern crate which;
use std::env;
#[cfg(feature = "regex")]
use regex::Regex;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::{env, vec};
use tempdir::TempDir;
struct TestFixture {
@ -125,6 +127,38 @@ fn test_which() {
assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[1])
}
#[test]
#[cfg(all(unix, feature = "regex"))]
fn test_which_re_in_with_matches() {
let f = TestFixture::new();
f.mk_bin("a/bin_0", "").unwrap();
f.mk_bin("b/bin_1", "").unwrap();
let re = Regex::new(r"bin_\d").unwrap();
let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
.unwrap()
.into_iter()
.collect();
let temp = f.tempdir;
assert_eq!(result, vec![temp.path().join("a/bin_0"), temp.path().join("b/bin_1")])
}
#[test]
#[cfg(all(unix, feature = "regex"))]
fn test_which_re_in_without_matches() {
let f = TestFixture::new();
let re = Regex::new(r"bi[^n]").unwrap();
let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
.unwrap()
.into_iter()
.collect();
assert_eq!(result, Vec::<PathBuf>::new())
}
#[test]
#[cfg(unix)]
fn test_which_extension() {