Skip to main content

brush_core/
pathsearch.rs

1//! Path searching utilities.
2
3use std::{
4    collections::VecDeque,
5    path::{Path, PathBuf},
6};
7
8use crate::sys;
9use crate::sys::fs::PathExt;
10
11/// Encapsulates the result of a path search.
12pub struct ExecutablePathSearch<PI, N> {
13    paths: VecDeque<PI>,
14    filename: N,
15}
16
17impl<PI, N> Iterator for ExecutablePathSearch<PI, N>
18where
19    PI: AsRef<Path>,
20    N: AsRef<Path>,
21{
22    type Item = PathBuf;
23
24    fn next(&mut self) -> Option<Self::Item> {
25        while let Some(path) = self.paths.pop_front() {
26            let path = PathBuf::from(path.as_ref()).join(self.filename.as_ref());
27            // Skip directories outright, then ask the platform to resolve
28            // the path to an actual executable file (which, on Windows, may
29            // involve appending a PATHEXT extension). The helper takes
30            // ownership so Unix — where no resolution is needed — can return
31            // the path unchanged without allocating.
32            if path.is_dir() {
33                continue;
34            }
35            if let Some(resolved) = sys::fs::resolve_executable(path) {
36                return Some(resolved);
37            }
38        }
39        None
40    }
41}
42
43pub(crate) struct ExecutablePathPrefixSearch<PI> {
44    paths: VecDeque<PI>,
45    queued_items: VecDeque<PathBuf>,
46    filename_prefix: String,
47    case_insensitive: bool,
48}
49
50impl<PI> Iterator for ExecutablePathPrefixSearch<PI>
51where
52    PI: AsRef<Path>,
53{
54    type Item = PathBuf;
55
56    fn next(&mut self) -> Option<Self::Item> {
57        // If we already found some items and queued them, then yield one now.
58        if let Some(item) = self.queued_items.pop_front() {
59            return Some(item);
60        }
61
62        while let Some(path) = self.paths.pop_front() {
63            let path = PathBuf::from(path.as_ref());
64
65            if let Ok(readdir) = path.read_dir() {
66                for entry in readdir.flatten() {
67                    if let Ok(mut filename) = entry.file_name().into_string() {
68                        if self.case_insensitive {
69                            filename = filename.to_ascii_lowercase();
70                        }
71
72                        if !filename.starts_with(&self.filename_prefix) {
73                            continue;
74                        }
75                    }
76
77                    let entry_path = entry.path();
78                    if let Ok(file_type) = entry.file_type() {
79                        if file_type.is_file() && entry_path.executable() {
80                            self.queued_items.push_back(entry_path);
81                            continue;
82                        }
83                        if file_type.is_symlink() && entry_path.executable() {
84                            self.queued_items.push_back(entry_path);
85                        }
86                    }
87                }
88            }
89            if let Some(item) = self.queued_items.pop_front() {
90                return Some(item);
91            }
92        }
93
94        None
95    }
96}
97
98/// Search for the given executable name in the provided paths.
99///
100/// # Arguments
101///
102/// * `paths` - An iterator over the paths to search.
103/// * `filename` - The name of the executable file to search for.
104pub fn search_for_executable<P, PI, N>(paths: P, filename: N) -> ExecutablePathSearch<PI, N>
105where
106    P: Iterator<Item = PI>,
107    PI: AsRef<Path>,
108    N: AsRef<Path>,
109{
110    ExecutablePathSearch {
111        paths: paths.collect(),
112        filename,
113    }
114}
115
116pub(crate) fn search_for_executable_with_prefix<P, PI>(
117    paths: P,
118    filename_prefix: &str,
119    case_insensitive: bool,
120) -> ExecutablePathPrefixSearch<PI>
121where
122    P: Iterator<Item = PI>,
123    PI: AsRef<Path>,
124{
125    let stored_prefix = if case_insensitive {
126        filename_prefix.to_ascii_lowercase()
127    } else {
128        filename_prefix.into()
129    };
130
131    ExecutablePathPrefixSearch {
132        paths: paths.collect(),
133        queued_items: VecDeque::new(),
134        filename_prefix: stored_prefix,
135        case_insensitive,
136    }
137}