--- /dev/null
+{
+ "git": {
+ "sha1": "13722854453a50002d72faa1d0960f70b68ceea4"
+ },
+ "path_in_vcs": ""
+}
\ No newline at end of file
--- /dev/null
+name: Main workflow
+on:
+ push:
+ pull_request:
+
+jobs:
+ # Run the `rustfmt` code formatter
+ rustfmt:
+ name: Rustfmt [Formatter]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup | Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup | Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ profile: minimal
+ components: rustfmt
+
+ - name: Build | Format
+ run: cargo fmt --all -- --check
+
+ # Run the `clippy` linting tool
+ clippy:
+ name: Clippy [Linter]
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Setup | Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup | Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ profile: minimal
+ components: clippy
+
+ - name: Build | Lint
+ uses: actions-rs/cargo@v1
+ with:
+ command: clippy
+ args: --workspace --all-targets --all-features -- -Dwarnings
+
+ # Ensure that the project could be successfully compiled
+ cargo_check:
+ name: Compile
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup | Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup | Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: true
+
+ - name: Build | Check
+ run: cargo check --workspace
+
+ # Run tests on Linux, macOS, and Windows
+ # On both Rust stable and Rust nightly
+ test:
+ name: Test Suite
+ runs-on: ${{ matrix.os }}
+ needs: cargo_check # First check then run expansive tests
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ rust: [stable, nightly]
+ steps:
+ - name: Setup | Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup | Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.rust }}
+ profile: minimal
+ override: true
+
+ # Run the ignored tests that expect the above setup
+ - name: Build | Test
+ run: cargo test --workspace --all-features -- -Z unstable-options --include-ignored
--- /dev/null
+target
+Cargo.lock
+.vscode/
--- /dev/null
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "which"
+version = "4.4.0"
+authors = ["Harry Fei <tiziyuanfang@gmail.com>"]
+description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms."
+documentation = "https://docs.rs/which/"
+readme = "README.md"
+keywords = [
+ "which",
+ "which-rs",
+ "unix",
+ "command",
+]
+categories = [
+ "os",
+ "filesystem",
+]
+license = "MIT"
+repository = "https://github.com/harryfei/which-rs.git"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[dependencies.either]
+version = "1.6.1"
+
+[dependencies.libc]
+version = "0.2.121"
+
+[dependencies.regex]
+version = "1.5.5"
+optional = true
+
+[dev-dependencies.tempfile]
+version = "3.3.0"
+
+[target."cfg(windows)".dependencies.once_cell]
+version = "1"
--- /dev/null
+[package]
+name = "which"
+version = "4.4.0"
+edition = "2018"
+authors = ["Harry Fei <tiziyuanfang@gmail.com>"]
+repository = "https://github.com/harryfei/which-rs.git"
+documentation = "https://docs.rs/which/"
+license = "MIT"
+description = "A Rust equivalent of Unix command \"which\". Locate installed executable in cross platforms."
+readme = "README.md"
+categories = ["os", "filesystem"]
+keywords = ["which", "which-rs", "unix", "command"]
+
+[dependencies]
+either = "1.6.1"
+libc = "0.2.121"
+regex = { version = "1.5.5", optional = true }
+
+[target.'cfg(windows)'.dependencies]
+once_cell = "1"
+
+[dev-dependencies]
+tempfile = "3.3.0"
+
+[package.metadata.docs.rs]
+all-features = true
--- /dev/null
+Copyright (c) 2015 fangyuanziti
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+[](https://github.com/harryfei/which-rs/actions/workflows/rust.yml)
+
+# which
+
+A Rust equivalent of Unix command "which". Locate installed executable in cross platforms.
+
+## Support platforms
+
+* Linux
+* Windows
+* macOS
+
+## Examples
+
+1) To find which rustc executable binary is using.
+
+ ``` rust
+ use which::which;
+
+ let result = which("rustc").unwrap();
+ assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
+ ```
+
+2. After enabling the `regex` feature, find all cargo subcommand executables on the path:
+
+ ``` rust
+ use which::which_re;
+
+ which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
+ .for_each(|pth| println!("{}", pth.to_string_lossy()));
+ ```
+
+## Documentation
+
+The documentation is [available online](https://docs.rs/which/).
--- /dev/null
+use crate::finder::Checker;
+#[cfg(unix)]
+use std::ffi::CString;
+use std::fs;
+#[cfg(unix)]
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+
+pub struct ExecutableChecker;
+
+impl ExecutableChecker {
+ pub fn new() -> ExecutableChecker {
+ ExecutableChecker
+ }
+}
+
+impl Checker for ExecutableChecker {
+ #[cfg(unix)]
+ fn is_valid(&self, path: &Path) -> bool {
+ CString::new(path.as_os_str().as_bytes())
+ .map(|c| unsafe { libc::access(c.as_ptr(), libc::X_OK) == 0 })
+ .unwrap_or(false)
+ }
+
+ #[cfg(windows)]
+ fn is_valid(&self, _path: &Path) -> bool {
+ true
+ }
+}
+
+pub struct ExistedChecker;
+
+impl ExistedChecker {
+ pub fn new() -> ExistedChecker {
+ ExistedChecker
+ }
+}
+
+impl Checker for ExistedChecker {
+ #[cfg(target_os = "windows")]
+ fn is_valid(&self, path: &Path) -> bool {
+ fs::symlink_metadata(path)
+ .map(|metadata| {
+ let file_type = metadata.file_type();
+ file_type.is_file() || file_type.is_symlink()
+ })
+ .unwrap_or(false)
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ fn is_valid(&self, path: &Path) -> bool {
+ fs::metadata(path)
+ .map(|metadata| metadata.is_file())
+ .unwrap_or(false)
+ }
+}
+
+pub struct CompositeChecker {
+ checkers: Vec<Box<dyn Checker>>,
+}
+
+impl CompositeChecker {
+ pub fn new() -> CompositeChecker {
+ CompositeChecker {
+ checkers: Vec::new(),
+ }
+ }
+
+ pub fn add_checker(mut self, checker: Box<dyn Checker>) -> CompositeChecker {
+ self.checkers.push(checker);
+ self
+ }
+}
+
+impl Checker for CompositeChecker {
+ fn is_valid(&self, path: &Path) -> bool {
+ self.checkers.iter().all(|checker| checker.is_valid(path))
+ }
+}
--- /dev/null
+use std::fmt;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Error {
+ BadAbsolutePath,
+ BadRelativePath,
+ CannotFindBinaryPath,
+ CannotGetCurrentDir,
+ CannotCanonicalize,
+}
+
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::BadAbsolutePath => write!(f, "bad absolute path"),
+ Error::BadRelativePath => write!(f, "bad relative path"),
+ Error::CannotFindBinaryPath => write!(f, "cannot find binary path"),
+ Error::CannotGetCurrentDir => write!(f, "cannot get current directory"),
+ Error::CannotCanonicalize => write!(f, "cannot canonicalize path"),
+ }
+ }
+}
--- /dev/null
+use crate::checker::CompositeChecker;
+use crate::error::*;
+#[cfg(windows)]
+use crate::helper::has_executable_extension;
+use either::Either;
+#[cfg(feature = "regex")]
+use regex::Regex;
+#[cfg(feature = "regex")]
+use std::borrow::Borrow;
+use std::env;
+use std::ffi::OsStr;
+#[cfg(any(feature = "regex", target_os = "windows"))]
+use std::fs;
+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: Option<V>,
+ binary_checker: CompositeChecker,
+ ) -> Result<impl Iterator<Item = PathBuf>>
+ where
+ T: AsRef<OsStr>,
+ U: AsRef<OsStr>,
+ V: AsRef<Path>,
+ {
+ let path = PathBuf::from(&binary_name);
+
+ let binary_path_candidates = match cwd {
+ Some(cwd) if path.has_separator() => {
+ // Search binary in cwd if the path have a path separator.
+ Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
+ }
+ _ => {
+ // Search binary in PATHs(defined in environment variable).
+ let p = paths.ok_or(Error::CannotFindBinaryPath)?;
+ let paths: Vec<_> = env::split_paths(&p).collect();
+
+ Either::Right(Self::path_search_candidates(path, paths).into_iter())
+ }
+ };
+
+ Ok(binary_path_candidates
+ .filter(move |p| binary_checker.is_valid(p))
+ .map(correct_casing))
+ }
+
+ #[cfg(feature = "regex")]
+ pub fn find_re<T>(
+ &self,
+ binary_regex: impl Borrow<Regex>,
+ paths: Option<T>,
+ binary_checker: CompositeChecker,
+ ) -> Result<impl Iterator<Item = PathBuf>>
+ where
+ T: AsRef<OsStr>,
+ {
+ let p = paths.ok_or(Error::CannotFindBinaryPath)?;
+ // Collect needs to happen in order to not have to
+ // change the API to borrow on `paths`.
+ #[allow(clippy::needless_collect)]
+ 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.borrow().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>,
+ {
+ 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>,
+ {
+ use once_cell::sync::Lazy;
+
+ // Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
+ // PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …].
+ // (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it;
+ // hence its retention.)
+ static PATH_EXTENSIONS: Lazy<Vec<String>> = Lazy::new(|| {
+ env::var("PATHEXT")
+ .map(|pathext| {
+ pathext
+ .split(';')
+ .filter_map(|s| {
+ if s.as_bytes().first() == Some(&b'.') {
+ Some(s.to_owned())
+ } else {
+ // Invalid segment; just ignore it.
+ None
+ }
+ })
+ .collect()
+ })
+ // PATHEXT not being set or not being a proper Unicode string is exceedingly
+ // improbable and would probably break Windows badly. Still, don't crash:
+ .unwrap_or_default()
+ });
+
+ paths
+ .into_iter()
+ .flat_map(move |p| -> Box<dyn Iterator<Item = _>> {
+ // Check if path already have executable extension
+ if has_executable_extension(&p, &PATH_EXTENSIONS) {
+ Box::new(iter::once(p))
+ } else {
+ let bare_file = p.extension().map(|_| p.clone());
+ // Appended paths with windows executable extensions.
+ // e.g. path `c:/windows/bin[.ext]` will expand to:
+ // [c:/windows/bin.ext]
+ // c:/windows/bin[.ext].COM
+ // c:/windows/bin[.ext].EXE
+ // c:/windows/bin[.ext].CMD
+ // ...
+ Box::new(
+ bare_file
+ .into_iter()
+ .chain(PATH_EXTENSIONS.iter().map(move |e| {
+ // Append the extension.
+ let mut p = p.clone().into_os_string();
+ p.push(e);
+
+ PathBuf::from(p)
+ })),
+ )
+ }
+ })
+ }
+}
+
+#[cfg(target_os = "windows")]
+fn correct_casing(mut p: PathBuf) -> PathBuf {
+ if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) {
+ if let Ok(iter) = fs::read_dir(parent) {
+ for e in iter.filter_map(std::result::Result::ok) {
+ if e.file_name().eq_ignore_ascii_case(file_name) {
+ p.pop();
+ p.push(e.file_name());
+ break;
+ }
+ }
+ }
+ }
+ p
+}
+
+#[cfg(not(target_os = "windows"))]
+fn correct_casing(p: PathBuf) -> PathBuf {
+ p
+}
--- /dev/null
+use std::path::Path;
+
+/// Check if given path has extension which in the given vector.
+pub fn has_executable_extension<T: AsRef<Path>, S: AsRef<str>>(path: T, pathext: &[S]) -> bool {
+ let ext = path.as_ref().extension().and_then(|e| e.to_str());
+ match ext {
+ Some(ext) => pathext
+ .iter()
+ .any(|e| ext.eq_ignore_ascii_case(&e.as_ref()[1..])),
+ _ => false,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::path::PathBuf;
+
+ #[test]
+ fn test_extension_in_extension_vector() {
+ // Case insensitive
+ assert!(has_executable_extension(
+ PathBuf::from("foo.exe"),
+ &[".COM", ".EXE", ".CMD"]
+ ));
+
+ assert!(has_executable_extension(
+ PathBuf::from("foo.CMD"),
+ &[".COM", ".EXE", ".CMD"]
+ ));
+ }
+
+ #[test]
+ fn test_extension_not_in_extension_vector() {
+ assert!(!has_executable_extension(
+ PathBuf::from("foo.bar"),
+ &[".COM", ".EXE", ".CMD"]
+ ));
+ }
+}
--- /dev/null
+//! which
+//!
+//! A Rust equivalent of Unix command `which(1)`.
+//! # Example:
+//!
+//! To find which rustc executable binary is using:
+//!
+//! ```no_run
+//! use which::which;
+//! use std::path::PathBuf;
+//!
+//! let result = which("rustc").unwrap();
+//! assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
+//!
+//! ```
+
+mod checker;
+mod error;
+mod finder;
+#[cfg(windows)]
+mod helper;
+
+#[cfg(feature = "regex")]
+use std::borrow::Borrow;
+use std::env;
+use std::fmt;
+use std::path;
+
+use std::ffi::{OsStr, OsString};
+
+use crate::checker::{CompositeChecker, ExecutableChecker, ExistedChecker};
+pub use crate::error::*;
+use crate::finder::Finder;
+
+/// Find an executable binary's path by name.
+///
+/// If given an absolute path, returns it if the file exists and is executable.
+///
+/// If given a relative path, returns an absolute path to the file if
+/// it exists and is executable.
+///
+/// If given a string without path separators, looks for a file named
+/// `binary_name` at each directory in `$PATH` and if it finds an executable
+/// file there, returns it.
+///
+/// # Example
+///
+/// ```no_run
+/// use which::which;
+/// use std::path::PathBuf;
+///
+/// let result = which::which("rustc").unwrap();
+/// assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
+///
+/// ```
+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 an executable binary's path by name, ignoring `cwd`.
+///
+/// If given an absolute path, returns it if the file exists and is executable.
+///
+/// Does not resolve relative paths.
+///
+/// If given a string without path separators, looks for a file named
+/// `binary_name` at each directory in `$PATH` and if it finds an executable
+/// file there, returns it.
+///
+/// # Example
+///
+/// ```no_run
+/// use which::which;
+/// use std::path::PathBuf;
+///
+/// let result = which::which_global("rustc").unwrap();
+/// assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
+///
+/// ```
+pub fn which_global<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> {
+ which_all_global(binary_name).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
+}
+
+/// Find all binaries with `binary_name` 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().ok();
+
+ let binary_checker = build_binary_checker();
+
+ let finder = Finder::new();
+
+ finder.find(binary_name, env::var_os("PATH"), cwd, binary_checker)
+}
+
+/// Find all binaries with `binary_name` ignoring `cwd`.
+pub fn which_all_global<T: AsRef<OsStr>>(
+ binary_name: T,
+) -> Result<impl Iterator<Item = path::PathBuf>> {
+ let binary_checker = build_binary_checker();
+
+ let finder = Finder::new();
+
+ finder.find(
+ binary_name,
+ env::var_os("PATH"),
+ Option::<&Path>::None,
+ binary_checker,
+ )
+}
+
+/// Find all binaries matching a regular expression in a the system PATH.
+///
+/// Only available when feature `regex` is enabled.
+///
+/// # Arguments
+///
+/// * `regex` - A regular expression to match binaries with
+///
+/// # Examples
+///
+/// Find Python executables:
+///
+/// ```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);
+/// ```
+///
+/// Find all cargo subcommand executables on the path:
+///
+/// ```
+/// use which::which_re;
+/// use regex::Regex;
+///
+/// which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
+/// .for_each(|pth| println!("{}", pth.to_string_lossy()));
+/// ```
+#[cfg(feature = "regex")]
+pub fn which_re(regex: impl Borrow<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
+ 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 matching a regular expression in a list of paths.
+///
+/// Only available when feature `regex` is enabled.
+///
+/// # 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: impl Borrow<Regex>,
+ paths: Option<T>,
+) -> Result<impl Iterator<Item = path::PathBuf>>
+where
+ T: AsRef<OsStr>,
+{
+ let binary_checker = build_binary_checker();
+
+ 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,
+ paths: Option<U>,
+ cwd: V,
+) -> Result<impl Iterator<Item = path::PathBuf>>
+where
+ T: AsRef<OsStr>,
+ U: AsRef<OsStr>,
+ V: AsRef<path::Path>,
+{
+ let binary_checker = build_binary_checker();
+
+ let finder = Finder::new();
+
+ finder.find(binary_name, paths, Some(cwd), binary_checker)
+}
+
+/// Find all binaries with `binary_name` in the path list `paths`, ignoring `cwd`.
+pub fn which_in_global<T, U>(
+ binary_name: T,
+ paths: Option<U>,
+) -> Result<impl Iterator<Item = path::PathBuf>>
+where
+ T: AsRef<OsStr>,
+ U: AsRef<OsStr>,
+{
+ let binary_checker = build_binary_checker();
+
+ let finder = Finder::new();
+
+ finder.find(binary_name, paths, Option::<&Path>::None, binary_checker)
+}
+
+fn build_binary_checker() -> CompositeChecker {
+ CompositeChecker::new()
+ .add_checker(Box::new(ExistedChecker::new()))
+ .add_checker(Box::new(ExecutableChecker::new()))
+}
+
+/// A wrapper containing all functionality in this crate.
+pub struct WhichConfig {
+ cwd: Option<either::Either<bool, path::PathBuf>>,
+ custom_path_list: Option<OsString>,
+ binary_name: Option<OsString>,
+ #[cfg(feature = "regex")]
+ regex: Option<Regex>,
+}
+
+impl Default for WhichConfig {
+ fn default() -> Self {
+ Self {
+ cwd: Some(either::Either::Left(true)),
+ custom_path_list: None,
+ binary_name: None,
+ #[cfg(feature = "regex")]
+ regex: None,
+ }
+ }
+}
+
+#[cfg(feature = "regex")]
+type Regex = regex::Regex;
+
+#[cfg(not(feature = "regex"))]
+type Regex = ();
+
+impl WhichConfig {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Whether or not to use the current working directory. `true` by default.
+ ///
+ /// # Panics
+ ///
+ /// If regex was set previously, and you've just passed in `use_cwd: true`, this will panic.
+ pub fn system_cwd(mut self, use_cwd: bool) -> Self {
+ #[cfg(feature = "regex")]
+ if self.regex.is_some() && use_cwd {
+ panic!("which can't use regex and cwd at the same time!")
+ }
+ self.cwd = Some(either::Either::Left(use_cwd));
+ self
+ }
+
+ /// Sets a custom path for resolving relative paths.
+ ///
+ /// # Panics
+ ///
+ /// If regex was set previously, this will panic.
+ pub fn custom_cwd(mut self, cwd: path::PathBuf) -> Self {
+ #[cfg(feature = "regex")]
+ if self.regex.is_some() {
+ panic!("which can't use regex and cwd at the same time!")
+ }
+ self.cwd = Some(either::Either::Right(cwd));
+ self
+ }
+
+ /// Sets the path name regex to search for. You ***MUST*** call this, or [`Self::binary_name`] prior to searching.
+ ///
+ /// When `Regex` is disabled this function takes the unit type as a stand in. The parameter will change when
+ /// `Regex` is enabled.
+ ///
+ /// # Panics
+ ///
+ /// If the `regex` feature wasn't turned on for this crate this will always panic. Additionally if a
+ /// `cwd` (aka current working directory) or `binary_name` was set previously, this will panic, as those options
+ /// are incompatible with `regex`.
+ #[allow(unused_variables)]
+ pub fn regex(mut self, regex: Regex) -> Self {
+ #[cfg(not(feature = "regex"))]
+ {
+ panic!("which's regex feature was not enabled in your Cargo.toml!")
+ }
+ #[cfg(feature = "regex")]
+ {
+ if self.cwd != Some(either::Either::Left(false)) && self.cwd.is_some() {
+ panic!("which can't use regex and cwd at the same time!")
+ }
+ if self.binary_name.is_some() {
+ panic!("which can't use `binary_name` and `regex` at the same time!");
+ }
+ self.regex = Some(regex);
+ self
+ }
+ }
+
+ /// Sets the path name to search for. You ***MUST*** call this, or [`Self::regex`] prior to searching.
+ ///
+ /// # Panics
+ ///
+ /// If a `regex` was set previously this will panic as this is not compatible with `regex`.
+ pub fn binary_name(mut self, name: OsString) -> Self {
+ #[cfg(feature = "regex")]
+ if self.regex.is_some() {
+ panic!("which can't use `binary_name` and `regex` at the same time!");
+ }
+ self.binary_name = Some(name);
+ self
+ }
+
+ /// Uses the given string instead of the `PATH` env variable.
+ pub fn custom_path_list(mut self, custom_path_list: OsString) -> Self {
+ self.custom_path_list = Some(custom_path_list);
+ self
+ }
+
+ /// Uses the `PATH` env variable. Enabled by default.
+ pub fn system_path_list(mut self) -> Self {
+ self.custom_path_list = None;
+ self
+ }
+
+ /// Finishes configuring, runs the query and returns the first result.
+ pub fn first_result(self) -> Result<path::PathBuf> {
+ self.all_results()
+ .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
+ }
+
+ /// Finishes configuring, runs the query and returns all results.
+ pub fn all_results(self) -> Result<impl Iterator<Item = path::PathBuf>> {
+ let binary_checker = build_binary_checker();
+
+ let finder = Finder::new();
+
+ let paths = self.custom_path_list.or_else(|| env::var_os("PATH"));
+
+ #[cfg(feature = "regex")]
+ if let Some(regex) = self.regex {
+ return finder
+ .find_re(regex, paths, binary_checker)
+ .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf>>);
+ }
+
+ let cwd = match self.cwd {
+ Some(either::Either::Left(false)) => None,
+ Some(either::Either::Right(custom)) => Some(custom),
+ None | Some(either::Either::Left(true)) => env::current_dir().ok(),
+ };
+
+ finder
+ .find(
+ self.binary_name.expect(
+ "binary_name not set! You must set binary_name or regex before searching!",
+ ),
+ paths,
+ cwd,
+ binary_checker,
+ )
+ .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf>>)
+ }
+}
+
+/// An owned, immutable wrapper around a `PathBuf` containing the path of an executable.
+///
+/// The constructed `PathBuf` is the output of `which` or `which_in`, but `which::Path` has the
+/// advantage of being a type distinct from `std::path::Path` and `std::path::PathBuf`.
+///
+/// It can be beneficial to use `which::Path` instead of `std::path::Path` when you want the type
+/// system to enforce the need for a path that exists and points to a binary that is executable.
+///
+/// Since `which::Path` implements `Deref` for `std::path::Path`, all methods on `&std::path::Path`
+/// are also available to `&which::Path` values.
+#[derive(Clone, PartialEq, Eq)]
+pub struct Path {
+ inner: path::PathBuf,
+}
+
+impl Path {
+ /// Returns the path of an executable binary by name.
+ ///
+ /// This calls `which` and maps the result into a `Path`.
+ pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<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.
+ ///
+ /// This calls `which_in` and maps the result into a `Path`.
+ pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<Path>
+ where
+ T: AsRef<OsStr>,
+ U: AsRef<OsStr>,
+ V: AsRef<path::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()
+ }
+
+ /// Consumes the `which::Path`, yielding its underlying `std::path::PathBuf`.
+ pub fn into_path_buf(self) -> path::PathBuf {
+ self.inner
+ }
+}
+
+impl fmt::Debug for Path {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl std::ops::Deref for Path {
+ type Target = path::Path;
+
+ fn deref(&self) -> &path::Path {
+ self.inner.deref()
+ }
+}
+
+impl AsRef<path::Path> for Path {
+ fn as_ref(&self) -> &path::Path {
+ self.as_path()
+ }
+}
+
+impl AsRef<OsStr> for Path {
+ fn as_ref(&self) -> &OsStr {
+ self.as_os_str()
+ }
+}
+
+impl PartialEq<path::PathBuf> for Path {
+ fn eq(&self, other: &path::PathBuf) -> bool {
+ self.inner == *other
+ }
+}
+
+impl PartialEq<Path> for path::PathBuf {
+ fn eq(&self, other: &Path) -> bool {
+ *self == other.inner
+ }
+}
+
+/// An owned, immutable wrapper around a `PathBuf` containing the _canonical_ path of an
+/// executable.
+///
+/// The constructed `PathBuf` is the result of `which` or `which_in` followed by
+/// `Path::canonicalize`, but `CanonicalPath` has the advantage of being a type distinct from
+/// `std::path::Path` and `std::path::PathBuf`.
+///
+/// It can be beneficial to use `CanonicalPath` instead of `std::path::Path` when you want the type
+/// system to enforce the need for a path that exists, points to a binary that is executable, is
+/// absolute, has all components normalized, and has all symbolic links resolved
+///
+/// Since `CanonicalPath` implements `Deref` for `std::path::Path`, all methods on
+/// `&std::path::Path` are also available to `&CanonicalPath` values.
+#[derive(Clone, PartialEq, Eq)]
+pub struct CanonicalPath {
+ inner: path::PathBuf,
+}
+
+impl CanonicalPath {
+ /// Returns the canonical path of an executable binary by name.
+ ///
+ /// This calls `which` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
+ pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<CanonicalPath> {
+ which(binary_name)
+ .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
+ .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_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>,
+ U: AsRef<OsStr>,
+ V: AsRef<path::Path>,
+ {
+ which_in(binary_name, paths, cwd)
+ .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
+ .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()
+ }
+
+ /// Consumes the `which::CanonicalPath`, yielding its underlying `std::path::PathBuf`.
+ pub fn into_path_buf(self) -> path::PathBuf {
+ self.inner
+ }
+}
+
+impl fmt::Debug for CanonicalPath {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl std::ops::Deref for CanonicalPath {
+ type Target = path::Path;
+
+ fn deref(&self) -> &path::Path {
+ self.inner.deref()
+ }
+}
+
+impl AsRef<path::Path> for CanonicalPath {
+ fn as_ref(&self) -> &path::Path {
+ self.as_path()
+ }
+}
+
+impl AsRef<OsStr> for CanonicalPath {
+ fn as_ref(&self) -> &OsStr {
+ self.as_os_str()
+ }
+}
+
+impl PartialEq<path::PathBuf> for CanonicalPath {
+ fn eq(&self, other: &path::PathBuf) -> bool {
+ self.inner == *other
+ }
+}
+
+impl PartialEq<CanonicalPath> for path::PathBuf {
+ fn eq(&self, other: &CanonicalPath) -> bool {
+ *self == other.inner
+ }
+}
--- /dev/null
+extern crate which;
+
+#[cfg(all(unix, 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 tempfile::TempDir;
+
+struct TestFixture {
+ /// Temp directory.
+ pub tempdir: TempDir,
+ /// $PATH
+ pub paths: OsString,
+ /// Binaries created in $PATH
+ pub bins: Vec<PathBuf>,
+}
+
+const SUBDIRS: &[&str] = &["a", "b", "c"];
+const BIN_NAME: &str = "bin";
+
+#[cfg(unix)]
+fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
+ use std::os::unix::fs::OpenOptionsExt;
+ let bin = dir.join(path).with_extension(extension);
+ fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .mode(0o666 | (libc::S_IXUSR as u32))
+ .open(&bin)
+ .and_then(|_f| bin.canonicalize())
+}
+
+fn touch(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
+ let b = dir.join(path).with_extension(extension);
+ fs::File::create(&b).and_then(|_f| b.canonicalize())
+}
+
+#[cfg(windows)]
+fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
+ touch(dir, path, extension)
+}
+
+impl TestFixture {
+ // tmp/a/bin
+ // tmp/a/bin.exe
+ // tmp/a/bin.cmd
+ // tmp/b/bin
+ // tmp/b/bin.exe
+ // tmp/b/bin.cmd
+ // tmp/c/bin
+ // tmp/c/bin.exe
+ // tmp/c/bin.cmd
+ pub fn new() -> TestFixture {
+ let tempdir = tempfile::tempdir().unwrap();
+ let mut builder = fs::DirBuilder::new();
+ builder.recursive(true);
+ let mut paths = vec![];
+ let mut bins = vec![];
+ for d in SUBDIRS.iter() {
+ let p = tempdir.path().join(d);
+ builder.create(&p).unwrap();
+ bins.push(mk_bin(&p, BIN_NAME, "").unwrap());
+ bins.push(mk_bin(&p, BIN_NAME, "exe").unwrap());
+ bins.push(mk_bin(&p, BIN_NAME, "cmd").unwrap());
+ paths.push(p);
+ }
+ let p = tempdir.path().join("win-bin");
+ builder.create(&p).unwrap();
+ bins.push(mk_bin(&p, "win-bin", "exe").unwrap());
+ paths.push(p);
+ TestFixture {
+ tempdir,
+ paths: env::join_paths(paths).unwrap(),
+ bins,
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn touch(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
+ touch(self.tempdir.path(), path, extension)
+ }
+
+ pub fn mk_bin(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
+ mk_bin(self.tempdir.path(), path, extension)
+ }
+}
+
+fn _which<T: AsRef<OsStr>>(f: &TestFixture, path: T) -> which::Result<which::CanonicalPath> {
+ which::CanonicalPath::new_in(path, Some(f.paths.clone()), f.tempdir.path())
+}
+
+fn _which_all<'a, T: AsRef<OsStr> + 'a>(
+ f: &'a TestFixture,
+ path: T,
+) -> which::Result<impl Iterator<Item = which::Result<which::CanonicalPath>> + '_> {
+ which::CanonicalPath::all_in(path, Some(f.paths.clone()), f.tempdir.path())
+}
+
+#[test]
+#[cfg(unix)]
+fn it_works() {
+ use std::process::Command;
+ let result = which::Path::new("rustc");
+ assert!(result.is_ok());
+
+ let which_result = Command::new("which").arg("rustc").output();
+
+ assert_eq!(
+ String::from(result.unwrap().to_str().unwrap()),
+ String::from_utf8(which_result.unwrap().stdout)
+ .unwrap()
+ .trim()
+ );
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which() {
+ let f = TestFixture::new();
+ assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[0])
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which() {
+ let f = TestFixture::new();
+ 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(all(unix, feature = "regex"))]
+fn test_which_re_accepts_owned_and_borrow() {
+ which::which_re(Regex::new(r".").unwrap())
+ .unwrap()
+ .for_each(drop);
+ which::which_re(&Regex::new(r".").unwrap())
+ .unwrap()
+ .for_each(drop);
+ which::which_re_in(Regex::new(r".").unwrap(), Some("pth"))
+ .unwrap()
+ .for_each(drop);
+ which::which_re_in(&Regex::new(r".").unwrap(), Some("pth"))
+ .unwrap()
+ .for_each(drop);
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_extension() {
+ let f = TestFixture::new();
+ let b = Path::new(&BIN_NAME).with_extension("");
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[0])
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_extension() {
+ let f = TestFixture::new();
+ let b = Path::new(&BIN_NAME).with_extension("cmd");
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[2])
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_no_extension() {
+ let f = TestFixture::new();
+ let b = Path::new("win-bin");
+ let which_result = which::which_in(&b, Some(&f.paths), ".").unwrap();
+ // Make sure the extension is the correct case.
+ assert_eq!(which_result.extension(), f.bins[9].extension());
+ assert_eq!(fs::canonicalize(&which_result).unwrap(), f.bins[9])
+}
+
+#[test]
+fn test_which_not_found() {
+ let f = TestFixture::new();
+ assert!(_which(&f, "a").is_err());
+}
+
+#[test]
+fn test_which_second() {
+ let f = TestFixture::new();
+ let b = f.mk_bin("b/another", env::consts::EXE_EXTENSION).unwrap();
+ 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.file_stem().unwrap() == BIN_NAME);
+ 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() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, &f.bins[3]).unwrap(),
+ f.bins[3].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_absolute() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, &f.bins[4]).unwrap(),
+ f.bins[4].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_absolute_path_case() {
+ // Test that an absolute path with an uppercase extension
+ // is accepted.
+ let f = TestFixture::new();
+ let p = &f.bins[4];
+ assert_eq!(_which(&f, &p).unwrap(), f.bins[4].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_absolute_extension() {
+ let f = TestFixture::new();
+ // Don't append EXE_EXTENSION here.
+ let b = f.bins[3].parent().unwrap().join(&BIN_NAME);
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_absolute_extension() {
+ let f = TestFixture::new();
+ // Don't append EXE_EXTENSION here.
+ let b = f.bins[4].parent().unwrap().join(&BIN_NAME);
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_relative() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, "b/bin").unwrap(),
+ f.bins[3].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_relative() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, "b/bin").unwrap(),
+ f.bins[4].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_relative_extension() {
+ // test_which_relative tests a relative path without an extension,
+ // so test a relative path with an extension here.
+ let f = TestFixture::new();
+ let b = Path::new("b/bin").with_extension(env::consts::EXE_EXTENSION);
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_relative_extension() {
+ // test_which_relative tests a relative path without an extension,
+ // so test a relative path with an extension here.
+ let f = TestFixture::new();
+ let b = Path::new("b/bin").with_extension("cmd");
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[5].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_relative_extension_case() {
+ // Test that a relative path with an uppercase extension
+ // is accepted.
+ let f = TestFixture::new();
+ let b = Path::new("b/bin").with_extension("EXE");
+ assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_relative_leading_dot() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, "./b/bin").unwrap(),
+ f.bins[3].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(windows)]
+fn test_which_relative_leading_dot() {
+ let f = TestFixture::new();
+ assert_eq!(
+ _which(&f, "./b/bin").unwrap(),
+ f.bins[4].canonicalize().unwrap()
+ );
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_non_executable() {
+ // Shouldn't return non-executable files.
+ let f = TestFixture::new();
+ f.touch("b/another", "").unwrap();
+ assert!(_which(&f, "another").is_err());
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_absolute_non_executable() {
+ // Shouldn't return non-executable files, even if given an absolute path.
+ let f = TestFixture::new();
+ let b = f.touch("b/another", "").unwrap();
+ assert!(_which(&f, &b).is_err());
+}
+
+#[test]
+#[cfg(unix)]
+fn test_which_relative_non_executable() {
+ // Shouldn't return non-executable files.
+ let f = TestFixture::new();
+ f.touch("b/another", "").unwrap();
+ assert!(_which(&f, "b/another").is_err());
+}
+
+#[test]
+fn test_failure() {
+ let f = TestFixture::new();
+
+ let run = || -> which::Result<PathBuf> {
+ let p = _which(&f, "./b/bin")?;
+ Ok(p.into_path_buf())
+ };
+
+ let _ = run();
+}