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, ExecutableChecker, ExistedChecker};
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    let binary_checker = build_binary_checker();
91
92    let finder = Finder::new();
93
94    finder.find(binary_name, env::var_os("PATH"), cwd, binary_checker)
95}
96
97/// Find all binaries with `binary_name` ignoring `cwd`.
98pub fn which_all_global<T: AsRef<OsStr>>(
99    binary_name: T,
100) -> Result<impl Iterator<Item = path::PathBuf>> {
101    let binary_checker = build_binary_checker();
102
103    let finder = Finder::new();
104
105    finder.find(
106        binary_name,
107        env::var_os("PATH"),
108        Option::<&Path>::None,
109        binary_checker,
110    )
111}
112
113/// Find all binaries matching a regular expression in a the system PATH.
114///
115/// Only available when feature `regex` is enabled.
116///
117/// # Arguments
118///
119/// * `regex` - A regular expression to match binaries with
120///
121/// # Examples
122///
123/// Find Python executables:
124///
125/// ```no_run
126/// use regex::Regex;
127/// use which::which;
128/// use std::path::PathBuf;
129///
130/// let re = Regex::new(r"python\d$").unwrap();
131/// let binaries: Vec<PathBuf> = which::which_re(re).unwrap().collect();
132/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
133/// assert_eq!(binaries, python_paths);
134/// ```
135///
136/// Find all cargo subcommand executables on the path:
137///
138/// ```
139/// use which::which_re;
140/// use regex::Regex;
141///
142/// which_re(Regex::new("^cargo-.*").unwrap()).unwrap()
143///     .for_each(|pth| println!("{}", pth.to_string_lossy()));
144/// ```
145#[cfg(feature = "regex")]
146pub fn which_re(regex: impl Borrow<Regex>) -> Result<impl Iterator<Item = path::PathBuf>> {
147    which_re_in(regex, env::var_os("PATH"))
148}
149
150/// Find `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
151pub fn which_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<path::PathBuf>
152where
153    T: AsRef<OsStr>,
154    U: AsRef<OsStr>,
155    V: AsRef<path::Path>,
156{
157    which_in_all(binary_name, paths, cwd)
158        .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
159}
160
161/// Find all binaries matching a regular expression in a list of paths.
162///
163/// Only available when feature `regex` is enabled.
164///
165/// # Arguments
166///
167/// * `regex` - A regular expression to match binaries with
168/// * `paths` - A string containing the paths to search
169///             (separated in the same way as the PATH environment variable)
170///
171/// # Examples
172///
173/// ```no_run
174/// use regex::Regex;
175/// use which::which;
176/// use std::path::PathBuf;
177///
178/// let re = Regex::new(r"python\d$").unwrap();
179/// let paths = Some("/usr/bin:/usr/local/bin");
180/// let binaries: Vec<PathBuf> = which::which_re_in(re, paths).unwrap().collect();
181/// let python_paths = vec![PathBuf::from("/usr/bin/python2"), PathBuf::from("/usr/bin/python3")];
182/// assert_eq!(binaries, python_paths);
183/// ```
184#[cfg(feature = "regex")]
185pub fn which_re_in<T>(
186    regex: impl Borrow<Regex>,
187    paths: Option<T>,
188) -> Result<impl Iterator<Item = path::PathBuf>>
189where
190    T: AsRef<OsStr>,
191{
192    let binary_checker = build_binary_checker();
193
194    let finder = Finder::new();
195
196    finder.find_re(regex, paths, binary_checker)
197}
198
199/// Find all binaries with `binary_name` in the path list `paths`, using `cwd` to resolve relative paths.
200pub fn which_in_all<T, U, V>(
201    binary_name: T,
202    paths: Option<U>,
203    cwd: V,
204) -> Result<impl Iterator<Item = path::PathBuf>>
205where
206    T: AsRef<OsStr>,
207    U: AsRef<OsStr>,
208    V: AsRef<path::Path>,
209{
210    let binary_checker = build_binary_checker();
211
212    let finder = Finder::new();
213
214    finder.find(binary_name, paths, Some(cwd), binary_checker)
215}
216
217/// Find all binaries with `binary_name` in the path list `paths`, ignoring `cwd`.
218pub fn which_in_global<T, U>(
219    binary_name: T,
220    paths: Option<U>,
221) -> Result<impl Iterator<Item = path::PathBuf>>
222where
223    T: AsRef<OsStr>,
224    U: AsRef<OsStr>,
225{
226    let binary_checker = build_binary_checker();
227
228    let finder = Finder::new();
229
230    finder.find(binary_name, paths, Option::<&Path>::None, binary_checker)
231}
232
233fn build_binary_checker() -> CompositeChecker {
234    CompositeChecker::new()
235        .add_checker(Box::new(ExistedChecker::new()))
236        .add_checker(Box::new(ExecutableChecker::new()))
237}
238
239/// A wrapper containing all functionality in this crate.
240pub struct WhichConfig {
241    cwd: Option<either::Either<bool, path::PathBuf>>,
242    custom_path_list: Option<OsString>,
243    binary_name: Option<OsString>,
244    #[cfg(feature = "regex")]
245    regex: Option<Regex>,
246}
247
248impl Default for WhichConfig {
249    fn default() -> Self {
250        Self {
251            cwd: Some(either::Either::Left(true)),
252            custom_path_list: None,
253            binary_name: None,
254            #[cfg(feature = "regex")]
255            regex: None,
256        }
257    }
258}
259
260#[cfg(feature = "regex")]
261type Regex = regex::Regex;
262
263#[cfg(not(feature = "regex"))]
264type Regex = ();
265
266impl WhichConfig {
267    pub fn new() -> Self {
268        Self::default()
269    }
270
271    /// Whether or not to use the current working directory. `true` by default.
272    ///
273    /// # Panics
274    ///
275    /// If regex was set previously, and you've just passed in `use_cwd: true`, this will panic.
276    pub fn system_cwd(mut self, use_cwd: bool) -> Self {
277        #[cfg(feature = "regex")]
278        if self.regex.is_some() && use_cwd {
279            panic!("which can't use regex and cwd at the same time!")
280        }
281        self.cwd = Some(either::Either::Left(use_cwd));
282        self
283    }
284
285    /// Sets a custom path for resolving relative paths.
286    ///
287    /// # Panics
288    ///
289    /// If regex was set previously, this will panic.
290    pub fn custom_cwd(mut self, cwd: path::PathBuf) -> Self {
291        #[cfg(feature = "regex")]
292        if self.regex.is_some() {
293            panic!("which can't use regex and cwd at the same time!")
294        }
295        self.cwd = Some(either::Either::Right(cwd));
296        self
297    }
298
299    /// Sets the path name regex to search for. You ***MUST*** call this, or [`Self::binary_name`] prior to searching.
300    ///
301    /// When `Regex` is disabled this function takes the unit type as a stand in. The parameter will change when
302    /// `Regex` is enabled.
303    ///
304    /// # Panics
305    ///
306    /// If the `regex` feature wasn't turned on for this crate this will always panic. Additionally if a
307    /// `cwd` (aka current working directory) or `binary_name` was set previously, this will panic, as those options
308    /// are incompatible with `regex`.
309    #[allow(unused_variables)]
310    #[allow(unused_mut)]
311    pub fn regex(mut self, regex: Regex) -> Self {
312        #[cfg(not(feature = "regex"))]
313        {
314            panic!("which's regex feature was not enabled in your Cargo.toml!")
315        }
316        #[cfg(feature = "regex")]
317        {
318            if self.cwd != Some(either::Either::Left(false)) && self.cwd.is_some() {
319                panic!("which can't use regex and cwd at the same time!")
320            }
321            if self.binary_name.is_some() {
322                panic!("which can't use `binary_name` and `regex` at the same time!");
323            }
324            self.regex = Some(regex);
325            self
326        }
327    }
328
329    /// Sets the path name to search for. You ***MUST*** call this, or [`Self::regex`] prior to searching.
330    ///
331    /// # Panics
332    ///
333    /// If a `regex` was set previously this will panic as this is not compatible with `regex`.
334    pub fn binary_name(mut self, name: OsString) -> Self {
335        #[cfg(feature = "regex")]
336        if self.regex.is_some() {
337            panic!("which can't use `binary_name` and `regex` at the same time!");
338        }
339        self.binary_name = Some(name);
340        self
341    }
342
343    /// Uses the given string instead of the `PATH` env variable.
344    pub fn custom_path_list(mut self, custom_path_list: OsString) -> Self {
345        self.custom_path_list = Some(custom_path_list);
346        self
347    }
348
349    /// Uses the `PATH` env variable. Enabled by default.
350    pub fn system_path_list(mut self) -> Self {
351        self.custom_path_list = None;
352        self
353    }
354
355    /// Finishes configuring, runs the query and returns the first result.
356    pub fn first_result(self) -> Result<path::PathBuf> {
357        self.all_results()
358            .and_then(|mut i| i.next().ok_or(Error::CannotFindBinaryPath))
359    }
360
361    /// Finishes configuring, runs the query and returns all results.
362    pub fn all_results(self) -> Result<impl Iterator<Item = path::PathBuf>> {
363        let binary_checker = build_binary_checker();
364
365        let finder = Finder::new();
366
367        let paths = self.custom_path_list.or_else(|| env::var_os("PATH"));
368
369        #[cfg(feature = "regex")]
370        if let Some(regex) = self.regex {
371            return finder
372                .find_re(regex, paths, binary_checker)
373                .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf>>);
374        }
375
376        let cwd = match self.cwd {
377            Some(either::Either::Left(false)) => None,
378            Some(either::Either::Right(custom)) => Some(custom),
379            None | Some(either::Either::Left(true)) => env::current_dir().ok(),
380        };
381
382        finder
383            .find(
384                self.binary_name.expect(
385                    "binary_name not set! You must set binary_name or regex before searching!",
386                ),
387                paths,
388                cwd,
389                binary_checker,
390            )
391            .map(|i| Box::new(i) as Box<dyn Iterator<Item = path::PathBuf>>)
392    }
393}
394
395/// An owned, immutable wrapper around a `PathBuf` containing the path of an executable.
396///
397/// The constructed `PathBuf` is the output of `which` or `which_in`, but `which::Path` has the
398/// advantage of being a type distinct from `std::path::Path` and `std::path::PathBuf`.
399///
400/// It can be beneficial to use `which::Path` instead of `std::path::Path` when you want the type
401/// system to enforce the need for a path that exists and points to a binary that is executable.
402///
403/// Since `which::Path` implements `Deref` for `std::path::Path`, all methods on `&std::path::Path`
404/// are also available to `&which::Path` values.
405#[derive(Clone, PartialEq, Eq)]
406pub struct Path {
407    inner: path::PathBuf,
408}
409
410impl Path {
411    /// Returns the path of an executable binary by name.
412    ///
413    /// This calls `which` and maps the result into a `Path`.
414    pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<Path> {
415        which(binary_name).map(|inner| Path { inner })
416    }
417
418    /// Returns the paths of all executable binaries by a name.
419    ///
420    /// this calls `which_all` and maps the results into `Path`s.
421    pub fn all<T: AsRef<OsStr>>(binary_name: T) -> Result<impl Iterator<Item = Path>> {
422        which_all(binary_name).map(|inner| inner.map(|inner| Path { inner }))
423    }
424
425    /// Returns the path of an executable binary by name in the path list `paths` and using the
426    /// current working directory `cwd` to resolve relative paths.
427    ///
428    /// This calls `which_in` and maps the result into a `Path`.
429    pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<Path>
430    where
431        T: AsRef<OsStr>,
432        U: AsRef<OsStr>,
433        V: AsRef<path::Path>,
434    {
435        which_in(binary_name, paths, cwd).map(|inner| Path { inner })
436    }
437
438    /// Returns all paths of an executable binary by name in the path list `paths` and using the
439    /// current working directory `cwd` to resolve relative paths.
440    ///
441    /// This calls `which_in_all` and maps the results into a `Path`.
442    pub fn all_in<T, U, V>(
443        binary_name: T,
444        paths: Option<U>,
445        cwd: V,
446    ) -> Result<impl Iterator<Item = Path>>
447    where
448        T: AsRef<OsStr>,
449        U: AsRef<OsStr>,
450        V: AsRef<path::Path>,
451    {
452        which_in_all(binary_name, paths, cwd).map(|inner| inner.map(|inner| Path { inner }))
453    }
454
455    /// Returns a reference to a `std::path::Path`.
456    pub fn as_path(&self) -> &path::Path {
457        self.inner.as_path()
458    }
459
460    /// Consumes the `which::Path`, yielding its underlying `std::path::PathBuf`.
461    pub fn into_path_buf(self) -> path::PathBuf {
462        self.inner
463    }
464}
465
466impl fmt::Debug for Path {
467    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
468        fmt::Debug::fmt(&self.inner, f)
469    }
470}
471
472impl std::ops::Deref for Path {
473    type Target = path::Path;
474
475    fn deref(&self) -> &path::Path {
476        self.inner.deref()
477    }
478}
479
480impl AsRef<path::Path> for Path {
481    fn as_ref(&self) -> &path::Path {
482        self.as_path()
483    }
484}
485
486impl AsRef<OsStr> for Path {
487    fn as_ref(&self) -> &OsStr {
488        self.as_os_str()
489    }
490}
491
492impl PartialEq<path::PathBuf> for Path {
493    fn eq(&self, other: &path::PathBuf) -> bool {
494        self.inner == *other
495    }
496}
497
498impl PartialEq<Path> for path::PathBuf {
499    fn eq(&self, other: &Path) -> bool {
500        *self == other.inner
501    }
502}
503
504/// An owned, immutable wrapper around a `PathBuf` containing the _canonical_ path of an
505/// executable.
506///
507/// The constructed `PathBuf` is the result of `which` or `which_in` followed by
508/// `Path::canonicalize`, but `CanonicalPath` has the advantage of being a type distinct from
509/// `std::path::Path` and `std::path::PathBuf`.
510///
511/// It can be beneficial to use `CanonicalPath` instead of `std::path::Path` when you want the type
512/// system to enforce the need for a path that exists, points to a binary that is executable, is
513/// absolute, has all components normalized, and has all symbolic links resolved
514///
515/// Since `CanonicalPath` implements `Deref` for `std::path::Path`, all methods on
516/// `&std::path::Path` are also available to `&CanonicalPath` values.
517#[derive(Clone, PartialEq, Eq)]
518pub struct CanonicalPath {
519    inner: path::PathBuf,
520}
521
522impl CanonicalPath {
523    /// Returns the canonical path of an executable binary by name.
524    ///
525    /// This calls `which` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
526    pub fn new<T: AsRef<OsStr>>(binary_name: T) -> Result<CanonicalPath> {
527        which(binary_name)
528            .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
529            .map(|inner| CanonicalPath { inner })
530    }
531
532    /// Returns the canonical paths of an executable binary by name.
533    ///
534    /// This calls `which_all` and `Path::canonicalize` and maps the results into `CanonicalPath`s.
535    pub fn all<T: AsRef<OsStr>>(
536        binary_name: T,
537    ) -> Result<impl Iterator<Item = Result<CanonicalPath>>> {
538        which_all(binary_name).map(|inner| {
539            inner.map(|inner| {
540                inner
541                    .canonicalize()
542                    .map_err(|_| Error::CannotCanonicalize)
543                    .map(|inner| CanonicalPath { inner })
544            })
545        })
546    }
547
548    /// Returns the canonical path of an executable binary by name in the path list `paths` and
549    /// using the current working directory `cwd` to resolve relative paths.
550    ///
551    /// This calls `which_in` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
552    pub fn new_in<T, U, V>(binary_name: T, paths: Option<U>, cwd: V) -> Result<CanonicalPath>
553    where
554        T: AsRef<OsStr>,
555        U: AsRef<OsStr>,
556        V: AsRef<path::Path>,
557    {
558        which_in(binary_name, paths, cwd)
559            .and_then(|p| p.canonicalize().map_err(|_| Error::CannotCanonicalize))
560            .map(|inner| CanonicalPath { inner })
561    }
562
563    /// Returns all of the canonical paths of an executable binary by name in the path list `paths` and
564    /// using the current working directory `cwd` to resolve relative paths.
565    ///
566    /// This calls `which_in_all` and `Path::canonicalize` and maps the result into a `CanonicalPath`.
567    pub fn all_in<T, U, V>(
568        binary_name: T,
569        paths: Option<U>,
570        cwd: V,
571    ) -> Result<impl Iterator<Item = Result<CanonicalPath>>>
572    where
573        T: AsRef<OsStr>,
574        U: AsRef<OsStr>,
575        V: AsRef<path::Path>,
576    {
577        which_in_all(binary_name, paths, cwd).map(|inner| {
578            inner.map(|inner| {
579                inner
580                    .canonicalize()
581                    .map_err(|_| Error::CannotCanonicalize)
582                    .map(|inner| CanonicalPath { inner })
583            })
584        })
585    }
586
587    /// Returns a reference to a `std::path::Path`.
588    pub fn as_path(&self) -> &path::Path {
589        self.inner.as_path()
590    }
591
592    /// Consumes the `which::CanonicalPath`, yielding its underlying `std::path::PathBuf`.
593    pub fn into_path_buf(self) -> path::PathBuf {
594        self.inner
595    }
596}
597
598impl fmt::Debug for CanonicalPath {
599    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
600        fmt::Debug::fmt(&self.inner, f)
601    }
602}
603
604impl std::ops::Deref for CanonicalPath {
605    type Target = path::Path;
606
607    fn deref(&self) -> &path::Path {
608        self.inner.deref()
609    }
610}
611
612impl AsRef<path::Path> for CanonicalPath {
613    fn as_ref(&self) -> &path::Path {
614        self.as_path()
615    }
616}
617
618impl AsRef<OsStr> for CanonicalPath {
619    fn as_ref(&self) -> &OsStr {
620        self.as_os_str()
621    }
622}
623
624impl PartialEq<path::PathBuf> for CanonicalPath {
625    fn eq(&self, other: &path::PathBuf) -> bool {
626        self.inner == *other
627    }
628}
629
630impl PartialEq<CanonicalPath> for path::PathBuf {
631    fn eq(&self, other: &CanonicalPath) -> bool {
632        *self == other.inner
633    }
634}