which/lib.rs
1//! which
2//!
3//! A Rust equivalent of Unix command `which(1)`.
4//! # Example:
5//!
6//! To find which rustc executable binary is using:
7//!
8//! ```no_run
9//! use which::which;
10//! use std::path::PathBuf;
11//!
12//! let result = which("rustc").unwrap();
13//! assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
14//!
15//! ```
16
17#![forbid(unsafe_code)]
18
19mod checker;
20mod error;
21mod finder;
22#[cfg(windows)]
23mod helper;
24
25#[cfg(feature = "regex")]
26use std::borrow::Borrow;
27use std::env;
28use std::fmt;
29use std::path;
30
31use std::ffi::{OsStr, OsString};
32
33use crate::checker::CompositeChecker;
34pub use crate::error::*;
35use crate::finder::Finder;
36
37/// Find an executable binary's path by name.
38///
39/// If given an absolute path, returns it if the file exists and is executable.
40///
41/// If given a relative path, returns an absolute path to the file if
42/// it exists and is executable.
43///
44/// If given a string without path separators, looks for a file named
45/// `binary_name` at each directory in `$PATH` and if it finds an executable
46/// file there, returns it.
47///
48/// # Example
49///
50/// ```no_run
51/// use which::which;
52/// use std::path::PathBuf;
53///
54/// let result = which::which("rustc").unwrap();
55/// assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
56///
57/// ```
58pub fn which<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> {
59 which_all(binary_name).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
60}
61
62/// Find an executable binary's path by name, ignoring `cwd`.
63///
64/// If given an absolute path, returns it if the file exists and is executable.
65///
66/// Does not resolve relative paths.
67///
68/// If given a string without path separators, looks for a file named
69/// `binary_name` at each directory in `$PATH` and if it finds an executable
70/// file there, returns it.
71///
72/// # Example
73///
74/// ```no_run
75/// use which::which;
76/// use std::path::PathBuf;
77///
78/// let result = which::which_global("rustc").unwrap();
79/// assert_eq!(result, PathBuf::from("/usr/bin/rustc"));
80///
81/// ```
82pub fn which_global<T: AsRef<OsStr>>(binary_name: T) -> Result<path::PathBuf> {
83 which_all_global(binary_name).and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
84}
85
86/// Find all binaries with `binary_name` using `cwd` to resolve relative paths.
87pub fn which_all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = path::PathBuf>> {
88 let cwd = env::current_dir().ok();
89
90 Finder::new().find(
91 binary_name,
92 env::var_os("PATH"),
93 cwd,
94 CompositeChecker::new(),
95 Noop,
96 )
97}
98
99/// Find all binaries with `binary_name` ignoring `cwd`.
100pub fn which_all_global<T: AsRef<OsStr>>(
101 binary_name: T,
102) -> Result<impl Iterator<Item = path::PathBuf>> {
103 Finder::new().find(
104 binary_name,
105 env::var_os("PATH"),
106 Option::<&Path>::None,
107 CompositeChecker::new(),
108 Noop,
109 )
110}
111
112/// Find all binaries matching a regular expression in a the system PATH.
113///
114/// Only available when feature `regex` is enabled.
115///
116/// # Arguments
117///
118/// * `regex` - A regular expression to match binaries with
119///
120/// # Examples
121///
122/// Find Python executables:
123///
124/// ```no_run
125/// use regex::Regex;
126/// use which::which;
127/// use std::path::PathBuf;
128///
129/// let re = Regex::new(r"python\d$").unwrap();
130/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect();
131/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
132/// assert_eq!(binaries, python_paths);
133/// ```
134///
135/// Find all cargo subcommand executables on the path:
136///
137/// ```
138/// use which::which_re;
139/// use regex::Regex;
140///
141/// which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
142/// .for_each(|pth| println!("{}", pth.to_string_lossy()));
143/// ```
144#[cfg(feature = "regex")]
145pub fn which_re(regex: impl Borrow<Regex>) -> Result<impl Iterator<Item = path::PathBuf>> {
146 which_re_in(regex, env::var_os("PATH"))
147}
148
149/// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
150pub fn which_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<path::PathBuf>
151where
152 T: AsRef<OsStr>,
153 U: AsRef<OsStr>,
154 V: AsRef<path::Path>,
155{
156 which_in_all(binary_name, paths, cwd)
157 .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
158}
159
160/// Find all binaries matching a regular expression in a list of paths.
161///
162/// Only available when feature `regex` is enabled.
163///
164/// # Arguments
165///
166/// * `regex` - A regular expression to match binaries with
167/// * `paths` - A string containing the paths to search
168/// (separated in the same way as the PATH environment variable)
169///
170/// # Examples
171///
172/// ```no_run
173/// use regex::Regex;
174/// use which::which;
175/// use std::path::PathBuf;
176///
177/// let re = Regex::new(r"python\d$").unwrap();
178/// let paths = Some("/usr/bin:/usr/local/bin");
179/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect();
180/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
181/// assert_eq!(binaries, python_paths);
182/// ```
183#[cfg(feature = "regex")]
184pub fn which_re_in<T>(
185 regex: impl Borrow<Regex>,
186 paths: Option<T>,
187) -> Result<impl Iterator<Item = path::PathBuf>>
188where
189 T: AsRef<OsStr>,
190{
191 Finder::new().find_re(regex, paths, CompositeChecker::new(), Noop)
192}
193
194/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
195pub fn which_in_all<'a, T, U, V>(
196 binary_name: T,
197 paths: Option<U>,
198 cwd: V,
199) -> Result<impl Iterator<Item = path::PathBuf> + 'a>
200where
201 T: AsRef<OsStr>,
202 U: AsRef<OsStr>,
203 V: AsRef<path::Path> + 'a,
204{
205 Finder::new().find(binary_name, paths, Some(cwd), CompositeChecker::new(), Noop)
206}
207
208/// Find all binaries with `binary_name` in the path list `paths`, ignoring `cwd`.
209pub fn which_in_global<T, U>(
210 binary_name: T,
211 paths: Option<U>,
212) -> Result<impl Iterator<Item = path::PathBuf>>
213where
214 T: AsRef<OsStr>,
215 U: AsRef<OsStr>,
216{
217 Finder::new().find(
218 binary_name,
219 paths,
220 Option::<&Path>::None,
221 CompositeChecker::new(),
222 Noop,
223 )
224}
225
226/// A wrapper containing all functionality in this crate.
227pub struct WhichConfig<F = Noop> {
228 cwd: Option<either::Either<bool, path::PathBuf>>,
229 custom_path_list: Option<OsString>,
230 binary_name: Option<OsString>,
231 nonfatal_error_handler: F,
232 #[cfg(feature = "regex")]
233 regex: Option<Regex>,
234}
235
236/// A handler for non-fatal errors which does nothing with them.
237#[derive(Default, Debug, Clone)]
238pub struct Noop;
239
240/// Defines what should happen when a nonfatal error is encountered. A nonfatal error may represent a problem,
241/// but it doesn't necessarily require `which` to stop its search.
242///
243/// This trait is implemented for any closure or function that takes a single argument which is a [`NonFatalError`].
244/// You may also implement it for your own types.
245pub trait NonFatalErrorHandler {
246 fn handle(&mut self, e: NonFatalError);
247}
248
249impl NonFatalErrorHandler for Noop {
250 fn handle(&mut self, _: NonFatalError) {
251 // Do nothing
252 }
253}
254
255impl<T> NonFatalErrorHandler for T
256where
257 T: FnMut(NonFatalError),
258{
259 fn handle(&mut self, e: NonFatalError) {
260 (self)(e);
261 }
262}
263
264impl<F: Default> Default for WhichConfig<F> {
265 fn default() -> Self {
266 Self {
267 cwd: Some(either::Either::Left(true)),
268 custom_path_list: None,
269 binary_name: None,
270 nonfatal_error_handler: F::default(),
271 #[cfg(feature = "regex")]
272 regex: None,
273 }
274 }
275}
276
277#[cfg(feature = "regex")]
278type Regex = regex::Regex;
279
280#[cfg(not(feature = "regex"))]
281type Regex = ();
282
283impl WhichConfig<Noop> {
284 pub fn new() -> Self {
285 Self::default()
286 }
287}
288
289impl<'a, F: NonFatalErrorHandler + 'a> WhichConfig<F> {
290 /// Whether or not to use the current working directory. `true` by default.
291 ///
292 /// # Panics
293 ///
294 /// If regex was set previously, and you've just passed in `use_cwd: true`, this will panic.
295 pub fn system_cwd(mut self, use_cwd: bool) -> Self {
296 #[cfg(feature = "regex")]
297 if self.regex.is_some() && use_cwd {
298 panic!("which can't use regex and cwd at the same time!")
299 }
300 self.cwd = Some(either::Either::Left(use_cwd));
301 self
302 }
303
304 /// Sets a custom path for resolving relative paths.
305 ///
306 /// # Panics
307 ///
308 /// If regex was set previously, this will panic.
309 pub fn custom_cwd(mut self, cwd: path::PathBuf) -> Self {
310 #[cfg(feature = "regex")]
311 if self.regex.is_some() {
312 panic!("which can't use regex and cwd at the same time!")
313 }
314 self.cwd = Some(either::Either::Right(cwd));
315 self
316 }
317
318 /// Sets the path name regex to search for. You ***MUST*** call this, or [`Self::binary_name`] prior to searching.
319 ///
320 /// When `Regex` is disabled this function takes the unit type as a stand in. The parameter will change when
321 /// `Regex` is enabled.
322 ///
323 /// # Panics
324 ///
325 /// If the `regex` feature wasn't turned on for this crate this will always panic. Additionally if a
326 /// `cwd` (aka current working directory) or `binary_name` was set previously, this will panic, as those options
327 /// are incompatible with `regex`.
328 #[allow(unused_variables)]
329 #[allow(unused_mut)]
330 pub fn regex(mut self, regex: Regex) -> Self {
331 #[cfg(not(feature = "regex"))]
332 {
333 panic!("which's regex feature was not enabled in your Cargo.toml!")
334 }
335 #[cfg(feature = "regex")]
336 {
337 if self.cwd != Some(either::Either::Left(false)) && self.cwd.is_some() {
338 panic!("which can't use regex and cwd at the same time!")
339 }
340 if self.binary_name.is_some() {
341 panic!("which can't use `binary_name` and `regex` at the same time!");
342 }
343 self.regex = Some(regex);
344 self
345 }
346 }
347
348 /// Sets the path name to search for. You ***MUST*** call this, or [`Self::regex`] prior to searching.
349 ///
350 /// # Panics
351 ///
352 /// If a `regex` was set previously this will panic as this is not compatible with `regex`.
353 pub fn binary_name(mut self, name: OsString) -> Self {
354 #[cfg(feature = "regex")]
355 if self.regex.is_some() {
356 panic!("which can't use `binary_name` and `regex` at the same time!");
357 }
358 self.binary_name = Some(name);
359 self
360 }
361
362 /// Uses the given string instead of the `PATH` env variable.
363 pub fn custom_path_list(mut self, custom_path_list: OsString) -> Self {
364 self.custom_path_list = Some(custom_path_list);
365 self
366 }
367
368 /// Uses the `PATH` env variable. Enabled by default.
369 pub fn system_path_list(mut self) -> Self {
370 self.custom_path_list = None;
371 self
372 }
373
374 /// Sets a closure that will receive non-fatal errors. You can also pass in other types
375 /// that implement [`NonFatalErrorHandler`].
376 ///
377 /// # Example
378 /// ```
379 /// # use which::WhichConfig;
380 /// let mut nonfatal_errors = Vec::new();
381 ///
382 /// WhichConfig::new()
383 /// .binary_name("tar".into())
384 /// .nonfatal_error_handler(|e| nonfatal_errors.push(e))
385 /// .all_results()
386 /// .unwrap()
387 /// .collect::<Vec<_>>();
388 ///
389 /// if !nonfatal_errors.is_empty() {
390 /// println!("nonfatal errors encountered: {nonfatal_errors:?}");
391 /// }
392 /// ```
393 ///
394 /// You could also log it if you choose
395 ///
396 /// ```
397 /// # use which::WhichConfig;
398 /// WhichConfig::new()
399 /// .binary_name("tar".into())
400 /// .nonfatal_error_handler(|e| eprintln!("{e}"))
401 /// .all_results()
402 /// .unwrap()
403 /// .collect::<Vec<_>>();
404 /// ```
405 pub fn nonfatal_error_handler<NewF>(self, handler: NewF) -> WhichConfig<NewF> {
406 WhichConfig {
407 custom_path_list: self.custom_path_list,
408 cwd: self.cwd,
409 binary_name: self.binary_name,
410 nonfatal_error_handler: handler,
411 #[cfg(feature = "regex")]
412 regex: self.regex,
413 }
414 }
415
416 /// Finishes configuring, runs the query and returns the first result.
417 pub fn first_result(self) -> Result<path::PathBuf> {
418 self.all_results()
419 .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
420 }
421
422 /// Finishes configuring, runs the query and returns all results.
423 pub fn all_results(self) -> Result<impl Iterator<Item = path::PathBuf> + 'a> {
424 let paths = self.custom_path_list.or_else(|| env::var_os("PATH"));
425
426 #[cfg(feature = "regex")]
427 if let Some(regex) = self.regex {
428 return Finder::new()
429 .find_re(
430 regex,
431 paths,
432 CompositeChecker::new(),
433 self.nonfatal_error_handler,
434 )
435 .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf> + 'a>);
436 }
437
438 let cwd = match self.cwd {
439 Some(either::Either::Left(false)) => None,
440 Some(either::Either::Right(custom)) => Some(custom),
441 None | Some(either::Either::Left(true)) => env::current_dir().ok(),
442 };
443
444 Finder::new()
445 .find(
446 self.binary_name.expect(
447 "binary_name not set! You must set binary_name or regex before searching!",
448 ),
449 paths,
450 cwd,
451 CompositeChecker::new(),
452 self.nonfatal_error_handler,
453 )
454 .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf> + 'a>)
455 }
456}
457
458/// An owned, immutable wrapper around a `PathBuf` containing the path of an executable.
459///
460/// The constructed `PathBuf` is the output of `which` or `which_in`, but `which::Path` has the
461/// advantage of being a type distinct from `std::path::Path` and `std::path::PathBuf`.
462///
463/// It can be beneficial to use `which::Path` instead of `std::path::Path` when you want the type
464/// system to enforce the need for a path that exists and points to a binary that is executable.
465///
466/// Since `which::Path` implements `Deref` for `std::path::Path`, all methods on `&std::path::Path`
467/// are also available to `&which::Path` values.
468#[derive(Clone, PartialEq, Eq)]
469pub struct Path {
470 inner: path::PathBuf,
471}
472
473impl Path {
474 /// Returns the path of an executable binary by name.
475 ///
476 /// This calls `which` and maps the result into a `Path`.
477 pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<Path> {
478 which(binary_name).map(|inner| Path { inner })
479 }
480
481 /// Returns the paths of all executable binaries by a name.
482 ///
483 /// this calls `which_all` and maps the results into `Path`s.
484 pub fn all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = Path>> {
485 which_all(binary_name).map(|inner| inner.map(|inner| Path { inner }))
486 }
487
488 /// Returns the path of an executable binary by name in the path list `paths` and using the
489 /// current working directory `cwd` to resolve relative paths.
490 ///
491 /// This calls `which_in` and maps the result into a `Path`.
492 pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<Path>
493 where
494 T: AsRef<OsStr>,
495 U: AsRef<OsStr>,
496 V: AsRef<path::Path>,
497 {
498 which_in(binary_name, paths, cwd).map(|inner| Path { inner })
499 }
500
501 /// Returns all paths of an executable binary by name in the path list `paths` and using the
502 /// current working directory `cwd` to resolve relative paths.
503 ///
504 /// This calls `which_in_all` and maps the results into a `Path`.
505 pub fn all_in<'a, T, U, V>(
506 binary_name: T,
507 paths: Option<U>,
508 cwd: V,
509 ) -> Result<impl Iterator<Item = Path> + 'a>
510 where
511 T: AsRef<OsStr>,
512 U: AsRef<OsStr>,
513 V: AsRef<path::Path> + 'a,
514 {
515 which_in_all(binary_name, paths, cwd).map(|inner| inner.map(|inner| Path { inner }))
516 }
517
518 /// Returns a reference to a `std::path::Path`.
519 pub fn as_path(&self) -> &path::Path {
520 self.inner.as_path()
521 }
522
523 /// Consumes the `which::Path`, yielding its underlying `std::path::PathBuf`.
524 pub fn into_path_buf(self) -> path::PathBuf {
525 self.inner
526 }
527}
528
529impl fmt::Debug for Path {
530 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
531 fmt::Debug::fmt(&self.inner, f)
532 }
533}
534
535impl std::ops::Deref for Path {
536 type Target = path::Path;
537
538 fn deref(&self) -> &path::Path {
539 self.inner.deref()
540 }
541}
542
543impl AsRef<path::Path> for Path {
544 fn as_ref(&self) -> &path::Path {
545 self.as_path()
546 }
547}
548
549impl AsRef<OsStr> for Path {
550 fn as_ref(&self) -> &OsStr {
551 self.as_os_str()
552 }
553}
554
555impl PartialEq<path::PathBuf> for Path {
556 fn eq(&self, other: &path::PathBuf) -> bool {
557 self.inner == *other
558 }
559}
560
561impl PartialEq<Path> for path::PathBuf {
562 fn eq(&self, other: &Path) -> bool {
563 *self == other.inner
564 }
565}
566
567/// An owned, immutable wrapper around a `PathBuf` containing the _canonical_ path of an
568/// executable.
569///
570/// The constructed `PathBuf` is the result of `which` or `which_in` followed by
571/// `Path::canonicalize`, but `CanonicalPath` has the advantage of being a type distinct from
572/// `std::path::Path` and `std::path::PathBuf`.
573///
574/// It can be beneficial to use `CanonicalPath` instead of `std::path::Path` when you want the type
575/// system to enforce the need for a path that exists, points to a binary that is executable, is
576/// absolute, has all components normalized, and has all symbolic links resolved
577///
578/// Since `CanonicalPath` implements `Deref` for `std::path::Path`, all methods on
579/// `&std::path::Path` are also available to `&CanonicalPath` values.
580#[derive(Clone, PartialEq, Eq)]
581pub struct CanonicalPath {
582 inner: path::PathBuf,
583}
584
585impl CanonicalPath {
586 /// Returns the canonical path of an executable binary by name.
587 ///
588 /// This calls `which` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
589 pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<CanonicalPath> {
590 which(binary_name)
591 .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
592 .map(|inner| CanonicalPath { inner })
593 }
594
595 /// Returns the canonical paths of an executable binary by name.
596 ///
597 /// This calls `which_all` and `Path::canonicalize` and maps the results into `CanonicalPath`s.
598 pub fn all<T: AsRef<OsStr>>(
599 binary_name: T,
600 ) -> Result<impl Iterator<Item = Result<CanonicalPath>>> {
601 which_all(binary_name).map(|inner| {
602 inner.map(|inner| {
603 inner
604 .canonicalize()
605 .map_err(|_| Error::CannotCanonicalize)
606 .map(|inner| CanonicalPath { inner })
607 })
608 })
609 }
610
611 /// Returns the canonical path of an executable binary by name in the path list `paths` and
612 /// using the current working directory `cwd` to resolve relative paths.
613 ///
614 /// This calls `which_in` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
615 pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<CanonicalPath>
616 where
617 T: AsRef<OsStr>,
618 U: AsRef<OsStr>,
619 V: AsRef<path::Path>,
620 {
621 which_in(binary_name, paths, cwd)
622 .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
623 .map(|inner| CanonicalPath { inner })
624 }
625
626 /// Returns all of the canonical paths of an executable binary by name in the path list `paths` and
627 /// using the current working directory `cwd` to resolve relative paths.
628 ///
629 /// This calls `which_in_all` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
630 pub fn all_in<'a, T, U, V>(
631 binary_name: T,
632 paths: Option<U>,
633 cwd: V,
634 ) -> Result<impl Iterator<Item = Result<CanonicalPath>> + 'a>
635 where
636 T: AsRef<OsStr>,
637 U: AsRef<OsStr>,
638 V: AsRef<path::Path> + 'a,
639 {
640 which_in_all(binary_name, paths, cwd).map(|inner| {
641 inner.map(|inner| {
642 inner
643 .canonicalize()
644 .map_err(|_| Error::CannotCanonicalize)
645 .map(|inner| CanonicalPath { inner })
646 })
647 })
648 }
649
650 /// Returns a reference to a `std::path::Path`.
651 pub fn as_path(&self) -> &path::Path {
652 self.inner.as_path()
653 }
654
655 /// Consumes the `which::CanonicalPath`, yielding its underlying `std::path::PathBuf`.
656 pub fn into_path_buf(self) -> path::PathBuf {
657 self.inner
658 }
659}
660
661impl fmt::Debug for CanonicalPath {
662 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
663 fmt::Debug::fmt(&self.inner, f)
664 }
665}
666
667impl std::ops::Deref for CanonicalPath {
668 type Target = path::Path;
669
670 fn deref(&self) -> &path::Path {
671 self.inner.deref()
672 }
673}
674
675impl AsRef<path::Path> for CanonicalPath {
676 fn as_ref(&self) -> &path::Path {
677 self.as_path()
678 }
679}
680
681impl AsRef<OsStr> for CanonicalPath {
682 fn as_ref(&self) -> &OsStr {
683 self.as_os_str()
684 }
685}
686
687impl PartialEq<path::PathBuf> for CanonicalPath {
688 fn eq(&self, other: &path::PathBuf) -> bool {
689 self.inner == *other
690 }
691}
692
693impl PartialEq<CanonicalPath> for path::PathBuf {
694 fn eq(&self, other: &CanonicalPath) -> bool {
695 *self == other.inner
696 }
697}