executable_finder/
lib.rs

1#[cfg(feature = "rayon")]
2use rayon::prelude::*;
3
4use std::{
5    collections::HashSet,
6    env::{self, split_paths, VarError},
7    hash::Hash,
8    path::PathBuf,
9};
10
11#[cfg(unix)]
12mod unix;
13#[cfg(unix)]
14use unix::search_dir;
15
16#[cfg(windows)]
17mod windows;
18#[cfg(windows)]
19use windows::search_dir;
20
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
22pub struct Executable {
23    pub name: String,
24    pub path: PathBuf,
25}
26
27/// Lists all executables on PATH
28///
29/// **Note:** this does not filter out duplicates
30pub fn executables() -> Result<Vec<Executable>, VarError> {
31    let path = env::var("PATH")?;
32    let paths = split_paths(&path);
33
34    let search_dir = search_dir()?;
35
36    #[cfg(feature = "rayon")]
37    let executables = paths
38        .collect::<Vec<_>>()
39        .into_par_iter()
40        .filter_map(search_dir)
41        .reduce(Vec::new, |mut a, b| {
42            a.extend_from_slice(&b);
43            a
44        });
45    #[cfg(not(feature = "rayon"))]
46    let executables = paths.filter_map(search_dir).fold(Vec::new(), |mut a, b| {
47        a.extend_from_slice(&b);
48        a
49    });
50
51    Ok(executables)
52}
53
54#[derive(Debug, Clone, Eq)]
55struct UniqueExecutable(Executable);
56
57impl PartialEq for UniqueExecutable {
58    fn eq(&self, other: &Self) -> bool {
59        self.0.name == other.0.name
60    }
61}
62
63impl Hash for UniqueExecutable {
64    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
65        self.0.name.hash(state);
66    }
67}
68
69/// Handles precedence i.e. will only return the first entry in PATH for each executable name
70///
71/// **Note:** this will never use rayon
72///
73/// Only partially implemented on Windows. It will choose the correct folder but is not guaranteed
74/// to choose the correct executable based on PATHEXT order (e.g. `.exe` before `.bat` when they
75/// share a folder).
76pub fn unique_executables() -> Result<Vec<Executable>, VarError> {
77    let path = env::var("PATH")?;
78
79    Ok(split_paths(&path)
80        .filter_map(search_dir()?)
81        .flat_map(|dir| dir.into_iter().map(UniqueExecutable))
82        .fold(HashSet::new(), |mut set, executable| {
83            if !set.contains(&executable) {
84                set.insert(executable);
85            };
86            set
87        })
88        .into_iter()
89        .map(|e| e.0)
90        .collect())
91}
92
93#[test]
94fn test() {
95    executables().unwrap();
96}