brush_core/
pathsearch.rs

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