1b3ba51a1Sopenharmony_ciuse crate::checker::CompositeChecker; 2b3ba51a1Sopenharmony_ciuse crate::error::*; 3b3ba51a1Sopenharmony_ci#[cfg(windows)] 4b3ba51a1Sopenharmony_ciuse crate::helper::has_executable_extension; 5b3ba51a1Sopenharmony_ciuse either::Either; 6b3ba51a1Sopenharmony_ci#[cfg(feature = "regex")] 7b3ba51a1Sopenharmony_ciuse regex::Regex; 8b3ba51a1Sopenharmony_ci#[cfg(feature = "regex")] 9b3ba51a1Sopenharmony_ciuse std::borrow::Borrow; 10b3ba51a1Sopenharmony_ciuse std::env; 11b3ba51a1Sopenharmony_ciuse std::ffi::OsStr; 12b3ba51a1Sopenharmony_ci#[cfg(any(feature = "regex", target_os = "windows"))] 13b3ba51a1Sopenharmony_ciuse std::fs; 14b3ba51a1Sopenharmony_ciuse std::iter; 15b3ba51a1Sopenharmony_ciuse std::path::{Path, PathBuf}; 16b3ba51a1Sopenharmony_ci 17b3ba51a1Sopenharmony_cipub trait Checker { 18b3ba51a1Sopenharmony_ci fn is_valid(&self, path: &Path) -> bool; 19b3ba51a1Sopenharmony_ci} 20b3ba51a1Sopenharmony_ci 21b3ba51a1Sopenharmony_citrait PathExt { 22b3ba51a1Sopenharmony_ci fn has_separator(&self) -> bool; 23b3ba51a1Sopenharmony_ci 24b3ba51a1Sopenharmony_ci fn to_absolute<P>(self, cwd: P) -> PathBuf 25b3ba51a1Sopenharmony_ci where 26b3ba51a1Sopenharmony_ci P: AsRef<Path>; 27b3ba51a1Sopenharmony_ci} 28b3ba51a1Sopenharmony_ci 29b3ba51a1Sopenharmony_ciimpl PathExt for PathBuf { 30b3ba51a1Sopenharmony_ci fn has_separator(&self) -> bool { 31b3ba51a1Sopenharmony_ci self.components().count() > 1 32b3ba51a1Sopenharmony_ci } 33b3ba51a1Sopenharmony_ci 34b3ba51a1Sopenharmony_ci fn to_absolute<P>(self, cwd: P) -> PathBuf 35b3ba51a1Sopenharmony_ci where 36b3ba51a1Sopenharmony_ci P: AsRef<Path>, 37b3ba51a1Sopenharmony_ci { 38b3ba51a1Sopenharmony_ci if self.is_absolute() { 39b3ba51a1Sopenharmony_ci self 40b3ba51a1Sopenharmony_ci } else { 41b3ba51a1Sopenharmony_ci let mut new_path = PathBuf::from(cwd.as_ref()); 42b3ba51a1Sopenharmony_ci new_path.push(self); 43b3ba51a1Sopenharmony_ci new_path 44b3ba51a1Sopenharmony_ci } 45b3ba51a1Sopenharmony_ci } 46b3ba51a1Sopenharmony_ci} 47b3ba51a1Sopenharmony_ci 48b3ba51a1Sopenharmony_cipub struct Finder; 49b3ba51a1Sopenharmony_ci 50b3ba51a1Sopenharmony_ciimpl Finder { 51b3ba51a1Sopenharmony_ci pub fn new() -> Finder { 52b3ba51a1Sopenharmony_ci Finder 53b3ba51a1Sopenharmony_ci } 54b3ba51a1Sopenharmony_ci 55b3ba51a1Sopenharmony_ci pub fn find<T, U, V>( 56b3ba51a1Sopenharmony_ci &self, 57b3ba51a1Sopenharmony_ci binary_name: T, 58b3ba51a1Sopenharmony_ci paths: Option<U>, 59b3ba51a1Sopenharmony_ci cwd: Option<V>, 60b3ba51a1Sopenharmony_ci binary_checker: CompositeChecker, 61b3ba51a1Sopenharmony_ci ) -> Result<impl Iterator<Item = PathBuf>> 62b3ba51a1Sopenharmony_ci where 63b3ba51a1Sopenharmony_ci T: AsRef<OsStr>, 64b3ba51a1Sopenharmony_ci U: AsRef<OsStr>, 65b3ba51a1Sopenharmony_ci V: AsRef<Path>, 66b3ba51a1Sopenharmony_ci { 67b3ba51a1Sopenharmony_ci let path = PathBuf::from(&binary_name); 68b3ba51a1Sopenharmony_ci 69b3ba51a1Sopenharmony_ci let binary_path_candidates = match cwd { 70b3ba51a1Sopenharmony_ci Some(cwd) if path.has_separator() => { 71b3ba51a1Sopenharmony_ci // Search binary in cwd if the path have a path separator. 72b3ba51a1Sopenharmony_ci Either::Left(Self::cwd_search_candidates(path, cwd).into_iter()) 73b3ba51a1Sopenharmony_ci } 74b3ba51a1Sopenharmony_ci _ => { 75b3ba51a1Sopenharmony_ci // Search binary in PATHs(defined in environment variable). 76b3ba51a1Sopenharmony_ci let p = paths.ok_or(Error::CannotFindBinaryPath)?; 77b3ba51a1Sopenharmony_ci let paths: Vec<_> = env::split_paths(&p).collect(); 78b3ba51a1Sopenharmony_ci 79b3ba51a1Sopenharmony_ci Either::Right(Self::path_search_candidates(path, paths).into_iter()) 80b3ba51a1Sopenharmony_ci } 81b3ba51a1Sopenharmony_ci }; 82b3ba51a1Sopenharmony_ci 83b3ba51a1Sopenharmony_ci Ok(binary_path_candidates 84b3ba51a1Sopenharmony_ci .filter(move |p| binary_checker.is_valid(p)) 85b3ba51a1Sopenharmony_ci .map(correct_casing)) 86b3ba51a1Sopenharmony_ci } 87b3ba51a1Sopenharmony_ci 88b3ba51a1Sopenharmony_ci #[cfg(feature = "regex")] 89b3ba51a1Sopenharmony_ci pub fn find_re<T>( 90b3ba51a1Sopenharmony_ci &self, 91b3ba51a1Sopenharmony_ci binary_regex: impl Borrow<Regex>, 92b3ba51a1Sopenharmony_ci paths: Option<T>, 93b3ba51a1Sopenharmony_ci binary_checker: CompositeChecker, 94b3ba51a1Sopenharmony_ci ) -> Result<impl Iterator<Item = PathBuf>> 95b3ba51a1Sopenharmony_ci where 96b3ba51a1Sopenharmony_ci T: AsRef<OsStr>, 97b3ba51a1Sopenharmony_ci { 98b3ba51a1Sopenharmony_ci let p = paths.ok_or(Error::CannotFindBinaryPath)?; 99b3ba51a1Sopenharmony_ci // Collect needs to happen in order to not have to 100b3ba51a1Sopenharmony_ci // change the API to borrow on `paths`. 101b3ba51a1Sopenharmony_ci #[allow(clippy::needless_collect)] 102b3ba51a1Sopenharmony_ci let paths: Vec<_> = env::split_paths(&p).collect(); 103b3ba51a1Sopenharmony_ci 104b3ba51a1Sopenharmony_ci let matching_re = paths 105b3ba51a1Sopenharmony_ci .into_iter() 106b3ba51a1Sopenharmony_ci .flat_map(fs::read_dir) 107b3ba51a1Sopenharmony_ci .flatten() 108b3ba51a1Sopenharmony_ci .flatten() 109b3ba51a1Sopenharmony_ci .map(|e| e.path()) 110b3ba51a1Sopenharmony_ci .filter(move |p| { 111b3ba51a1Sopenharmony_ci if let Some(unicode_file_name) = p.file_name().unwrap().to_str() { 112b3ba51a1Sopenharmony_ci binary_regex.borrow().is_match(unicode_file_name) 113b3ba51a1Sopenharmony_ci } else { 114b3ba51a1Sopenharmony_ci false 115b3ba51a1Sopenharmony_ci } 116b3ba51a1Sopenharmony_ci }) 117b3ba51a1Sopenharmony_ci .filter(move |p| binary_checker.is_valid(p)); 118b3ba51a1Sopenharmony_ci 119b3ba51a1Sopenharmony_ci Ok(matching_re) 120b3ba51a1Sopenharmony_ci } 121b3ba51a1Sopenharmony_ci 122b3ba51a1Sopenharmony_ci fn cwd_search_candidates<C>(binary_name: PathBuf, cwd: C) -> impl IntoIterator<Item = PathBuf> 123b3ba51a1Sopenharmony_ci where 124b3ba51a1Sopenharmony_ci C: AsRef<Path>, 125b3ba51a1Sopenharmony_ci { 126b3ba51a1Sopenharmony_ci let path = binary_name.to_absolute(cwd); 127b3ba51a1Sopenharmony_ci 128b3ba51a1Sopenharmony_ci Self::append_extension(iter::once(path)) 129b3ba51a1Sopenharmony_ci } 130b3ba51a1Sopenharmony_ci 131b3ba51a1Sopenharmony_ci fn path_search_candidates<P>( 132b3ba51a1Sopenharmony_ci binary_name: PathBuf, 133b3ba51a1Sopenharmony_ci paths: P, 134b3ba51a1Sopenharmony_ci ) -> impl IntoIterator<Item = PathBuf> 135b3ba51a1Sopenharmony_ci where 136b3ba51a1Sopenharmony_ci P: IntoIterator<Item = PathBuf>, 137b3ba51a1Sopenharmony_ci { 138b3ba51a1Sopenharmony_ci let new_paths = paths.into_iter().map(move |p| p.join(binary_name.clone())); 139b3ba51a1Sopenharmony_ci 140b3ba51a1Sopenharmony_ci Self::append_extension(new_paths) 141b3ba51a1Sopenharmony_ci } 142b3ba51a1Sopenharmony_ci 143b3ba51a1Sopenharmony_ci #[cfg(unix)] 144b3ba51a1Sopenharmony_ci fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf> 145b3ba51a1Sopenharmony_ci where 146b3ba51a1Sopenharmony_ci P: IntoIterator<Item = PathBuf>, 147b3ba51a1Sopenharmony_ci { 148b3ba51a1Sopenharmony_ci paths 149b3ba51a1Sopenharmony_ci } 150b3ba51a1Sopenharmony_ci 151b3ba51a1Sopenharmony_ci #[cfg(windows)] 152b3ba51a1Sopenharmony_ci fn append_extension<P>(paths: P) -> impl IntoIterator<Item = PathBuf> 153b3ba51a1Sopenharmony_ci where 154b3ba51a1Sopenharmony_ci P: IntoIterator<Item = PathBuf>, 155b3ba51a1Sopenharmony_ci { 156b3ba51a1Sopenharmony_ci use once_cell::sync::Lazy; 157b3ba51a1Sopenharmony_ci 158b3ba51a1Sopenharmony_ci // Sample %PATHEXT%: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC 159b3ba51a1Sopenharmony_ci // PATH_EXTENSIONS is then [".COM", ".EXE", ".BAT", …]. 160b3ba51a1Sopenharmony_ci // (In one use of PATH_EXTENSIONS we skip the dot, but in the other we need it; 161b3ba51a1Sopenharmony_ci // hence its retention.) 162b3ba51a1Sopenharmony_ci static PATH_EXTENSIONS: Lazy<Vec<String>> = Lazy::new(|| { 163b3ba51a1Sopenharmony_ci env::var("PATHEXT") 164b3ba51a1Sopenharmony_ci .map(|pathext| { 165b3ba51a1Sopenharmony_ci pathext 166b3ba51a1Sopenharmony_ci .split(';') 167b3ba51a1Sopenharmony_ci .filter_map(|s| { 168b3ba51a1Sopenharmony_ci if s.as_bytes().first() == Some(&b'.') { 169b3ba51a1Sopenharmony_ci Some(s.to_owned()) 170b3ba51a1Sopenharmony_ci } else { 171b3ba51a1Sopenharmony_ci // Invalid segment; just ignore it. 172b3ba51a1Sopenharmony_ci None 173b3ba51a1Sopenharmony_ci } 174b3ba51a1Sopenharmony_ci }) 175b3ba51a1Sopenharmony_ci .collect() 176b3ba51a1Sopenharmony_ci }) 177b3ba51a1Sopenharmony_ci // PATHEXT not being set or not being a proper Unicode string is exceedingly 178b3ba51a1Sopenharmony_ci // improbable and would probably break Windows badly. Still, don't crash: 179b3ba51a1Sopenharmony_ci .unwrap_or_default() 180b3ba51a1Sopenharmony_ci }); 181b3ba51a1Sopenharmony_ci 182b3ba51a1Sopenharmony_ci paths 183b3ba51a1Sopenharmony_ci .into_iter() 184b3ba51a1Sopenharmony_ci .flat_map(move |p| -> Box<dyn Iterator<Item = _>> { 185b3ba51a1Sopenharmony_ci // Check if path already have executable extension 186b3ba51a1Sopenharmony_ci if has_executable_extension(&p, &PATH_EXTENSIONS) { 187b3ba51a1Sopenharmony_ci Box::new(iter::once(p)) 188b3ba51a1Sopenharmony_ci } else { 189b3ba51a1Sopenharmony_ci let bare_file = p.extension().map(|_| p.clone()); 190b3ba51a1Sopenharmony_ci // Appended paths with windows executable extensions. 191b3ba51a1Sopenharmony_ci // e.g. path `c:/windows/bin[.ext]` will expand to: 192b3ba51a1Sopenharmony_ci // [c:/windows/bin.ext] 193b3ba51a1Sopenharmony_ci // c:/windows/bin[.ext].COM 194b3ba51a1Sopenharmony_ci // c:/windows/bin[.ext].EXE 195b3ba51a1Sopenharmony_ci // c:/windows/bin[.ext].CMD 196b3ba51a1Sopenharmony_ci // ... 197b3ba51a1Sopenharmony_ci Box::new( 198b3ba51a1Sopenharmony_ci bare_file 199b3ba51a1Sopenharmony_ci .into_iter() 200b3ba51a1Sopenharmony_ci .chain(PATH_EXTENSIONS.iter().map(move |e| { 201b3ba51a1Sopenharmony_ci // Append the extension. 202b3ba51a1Sopenharmony_ci let mut p = p.clone().into_os_string(); 203b3ba51a1Sopenharmony_ci p.push(e); 204b3ba51a1Sopenharmony_ci 205b3ba51a1Sopenharmony_ci PathBuf::from(p) 206b3ba51a1Sopenharmony_ci })), 207b3ba51a1Sopenharmony_ci ) 208b3ba51a1Sopenharmony_ci } 209b3ba51a1Sopenharmony_ci }) 210b3ba51a1Sopenharmony_ci } 211b3ba51a1Sopenharmony_ci} 212b3ba51a1Sopenharmony_ci 213b3ba51a1Sopenharmony_ci#[cfg(target_os = "windows")] 214b3ba51a1Sopenharmony_cifn correct_casing(mut p: PathBuf) -> PathBuf { 215b3ba51a1Sopenharmony_ci if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) { 216b3ba51a1Sopenharmony_ci if let Ok(iter) = fs::read_dir(parent) { 217b3ba51a1Sopenharmony_ci for e in iter.filter_map(std::result::Result::ok) { 218b3ba51a1Sopenharmony_ci if e.file_name().eq_ignore_ascii_case(file_name) { 219b3ba51a1Sopenharmony_ci p.pop(); 220b3ba51a1Sopenharmony_ci p.push(e.file_name()); 221b3ba51a1Sopenharmony_ci break; 222b3ba51a1Sopenharmony_ci } 223b3ba51a1Sopenharmony_ci } 224b3ba51a1Sopenharmony_ci } 225b3ba51a1Sopenharmony_ci } 226b3ba51a1Sopenharmony_ci p 227b3ba51a1Sopenharmony_ci} 228b3ba51a1Sopenharmony_ci 229b3ba51a1Sopenharmony_ci#[cfg(not(target_os = "windows"))] 230b3ba51a1Sopenharmony_cifn correct_casing(p: PathBuf) -> PathBuf { 231b3ba51a1Sopenharmony_ci p 232b3ba51a1Sopenharmony_ci} 233