• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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