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