aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChih-Hung Hsieh <chh@google.com>2020-04-07 14:24:03 -0700
committerChih-Hung Hsieh <chh@google.com>2020-04-07 14:28:37 -0700
commitfab837700283a7303273b086df93fabf0e67b311 (patch)
treef34f8485c16cb5912c4945ac231f1c937d349517 /src
parent3bdad64f25d96ddc113cd016930637d8820fcd2b (diff)
downloadwhich-fab837700283a7303273b086df93fabf0e67b311.tar.gz
* Add OWNERS Bug: 152884384 Test: make Change-Id: Ic643b3c23001db1124e626548e13e43b27b89c7b
Diffstat (limited to 'src')
-rw-r--r--src/checker.rs70
-rw-r--r--src/error.rs92
-rw-r--r--src/finder.rs155
-rw-r--r--src/helper.rs40
-rw-r--r--src/lib.rs271
5 files changed, 628 insertions, 0 deletions
diff --git a/src/checker.rs b/src/checker.rs
new file mode 100644
index 0000000..6021711
--- /dev/null
+++ b/src/checker.rs
@@ -0,0 +1,70 @@
+use finder::Checker;
+#[cfg(unix)]
+use libc;
+#[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())
+ .and_then(|c| Ok(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 {
+ 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))
+ }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..c75fe4c
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,92 @@
+#[cfg(feature = "failure")]
+use failure::{Backtrace, Context, Fail};
+use std;
+use std::fmt::{self, Display};
+
+#[derive(Debug)]
+pub struct Error {
+ #[cfg(feature = "failure")]
+ inner: Context<ErrorKind>,
+ #[cfg(not(feature = "failure"))]
+ inner: ErrorKind,
+}
+
+// To suppress false positives from cargo-clippy
+#[cfg_attr(feature = "cargo-clippy", allow(empty_line_after_outer_attr))]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum ErrorKind {
+ BadAbsolutePath,
+ BadRelativePath,
+ CannotFindBinaryPath,
+ CannotGetCurrentDir,
+ CannotCanonicalize,
+}
+
+#[cfg(feature = "failure")]
+impl Fail for ErrorKind {}
+
+impl Display for ErrorKind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let display = match *self {
+ ErrorKind::BadAbsolutePath => "Bad absolute path",
+ ErrorKind::BadRelativePath => "Bad relative path",
+ ErrorKind::CannotFindBinaryPath => "Cannot find binary path",
+ ErrorKind::CannotGetCurrentDir => "Cannot get current directory",
+ ErrorKind::CannotCanonicalize => "Cannot canonicalize path",
+ };
+ f.write_str(display)
+ }
+}
+
+#[cfg(feature = "failure")]
+impl Fail for Error {
+ fn cause(&self) -> Option<&dyn Fail> {
+ self.inner.cause()
+ }
+
+ fn backtrace(&self) -> Option<&Backtrace> {
+ self.inner.backtrace()
+ }
+}
+
+#[cfg(not(feature = "failure"))]
+impl std::error::Error for Error {}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.inner, f)
+ }
+}
+
+impl Error {
+ pub fn kind(&self) -> ErrorKind {
+ #[cfg(feature = "failure")]
+ {
+ *self.inner.get_context()
+ }
+ #[cfg(not(feature = "failure"))]
+ {
+ self.inner
+ }
+ }
+}
+
+impl From<ErrorKind> for Error {
+ fn from(kind: ErrorKind) -> Error {
+ Error {
+ #[cfg(feature = "failure")]
+ inner: Context::new(kind),
+ #[cfg(not(feature = "failure"))]
+ inner: kind,
+ }
+ }
+}
+
+#[cfg(feature = "failure")]
+impl From<Context<ErrorKind>> for Error {
+ fn from(inner: Context<ErrorKind>) -> Error {
+ Error { inner }
+ }
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
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)
+ }
+ })
+ }
+}
diff --git a/src/helper.rs b/src/helper.rs
new file mode 100644
index 0000000..71658a0
--- /dev/null
+++ b/src/helper.rs
@@ -0,0 +1,40 @@
+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, exts_vec: &Vec<S>) -> bool {
+ let ext = path.as_ref().extension().and_then(|e| e.to_str());
+ match ext {
+ Some(ext) => exts_vec
+ .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"),
+ &vec![".COM", ".EXE", ".CMD"]
+ ));
+
+ assert!(has_executable_extension(
+ PathBuf::from("foo.CMD"),
+ &vec![".COM", ".EXE", ".CMD"]
+ ));
+ }
+
+ #[test]
+ fn test_extension_not_in_extension_vector() {
+ assert!(!has_executable_extension(
+ PathBuf::from("foo.bar"),
+ &vec![".COM", ".EXE", ".CMD"]
+ ));
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..42a6963
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,271 @@
+//! which
+//!
+//! A Rust equivalent of Unix command `which(1)`.
+//! # Example:
+//!
+//! To find which rustc executable binary is using:
+//!
+//! ``` norun
+//! use which::which;
+//!
+//! let result = which::which("rustc").unwrap();
+//! assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
+//!
+//! ```
+
+#[cfg(feature = "failure")]
+extern crate failure;
+extern crate libc;
+
+#[cfg(feature = "failure")]
+use failure::ResultExt;
+mod checker;
+mod error;
+mod finder;
+#[cfg(windows)]
+mod helper;
+
+use std::env;
+use std::fmt;
+use std::path;
+
+use std::ffi::OsStr;
+
+use checker::CompositeChecker;
+use checker::ExecutableChecker;
+use checker::ExistedChecker;
+pub use error::*;
+use finder::Finder;
+
+/// Find a exectable 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
+///
+/// ``` norun
+/// 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> {
+ #[cfg(feature = "failure")]
+ let cwd = env::current_dir().context(ErrorKind::CannotGetCurrentDir)?;
+ #[cfg(not(feature = "failure"))]
+ let cwd = env::current_dir().map_err(|_| ErrorKind::CannotGetCurrentDir)?;
+
+ which_in(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>,
+{
+ let binary_checker = CompositeChecker::new()
+ .add_checker(Box::new(ExistedChecker::new()))
+ .add_checker(Box::new(ExecutableChecker::new()));
+
+ let finder = Finder::new();
+
+ finder.find(binary_name, paths, cwd, &binary_checker)
+}
+
+/// 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)]
+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 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 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 Eq for Path {}
+
+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)]
+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(|_| ErrorKind::CannotCanonicalize.into())
+ })
+ .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`.
+ 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(|_| ErrorKind::CannotCanonicalize.into())
+ })
+ .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 Eq for CanonicalPath {}
+
+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
+ }
+}