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