1use std::{collections::VecDeque, path::PathBuf};
4
5use crate::sys::fs::PathExt;
6
7pub 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 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
92pub 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}