1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use std::path::{Path, PathBuf};
pub trait PathResolve {
// TODO: Once RPITIT lands in stable, change the return types from
// `-> Box<dyn Trait>` to `-> impl Trait`
// See: https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html
// Resolve the path of a file give a set of directories as the `which` unix
// command would do with components of the `PATH` environment variable, and
// return an iterator over all candidates.
// Resulting candidates are files that exist, but no other constraing is
// imposed, in particular this function does not check for the executable bits.
// Further contraints can be added by calling filtering the returned iterator.
fn resolve_in_dirs<'a>(
&self,
dirs: impl IntoIterator<Item = impl AsRef<Path>> + 'a,
) -> Box<dyn Iterator<Item = PathBuf> + 'a>;
fn resolve_in_path(&self) -> Box<dyn Iterator<Item = PathBuf>>;
fn resolve_in_path_or_cwd(&self) -> Box<dyn Iterator<Item = PathBuf>>;
}
// Gets the content of the `PATH` environment variable as an
// iterator over its components
pub fn paths() -> impl Iterator<Item = PathBuf> {
std::env::var_os("PATH")
.as_ref()
.map(std::env::split_paths)
.into_iter()
.flatten()
.collect::<Vec<_>>()
.into_iter()
}
impl<T: AsRef<Path>> PathResolve for T {
fn resolve_in_dirs<'a>(
&self,
dirs: impl IntoIterator<Item = impl AsRef<Path>> + 'a,
) -> Box<dyn Iterator<Item = PathBuf> + 'a> {
let cwd = std::env::current_dir().ok();
let has_separator = self.as_ref().components().count() > 1;
// The seemingly extra complexity here is because we can only have one concrete
// return type even if we return an `impl Iterator<Item = PathBuf>`
let (first, second) = if has_separator {
// file has a separator, we only need to rÂșesolve relative to `cwd`, we must ignore `PATH`
(cwd, None)
} else {
// file is just a binary name, we must not resolve relative to `cwd`, but relative to `PATH` components
let dirs = dirs.into_iter().filter_map(move |p| {
let path = cwd.as_ref()?.join(p.as_ref()).canonicalize().ok()?;
path.is_dir().then_some(path)
});
(None, Some(dirs))
};
let file = self.as_ref().to_owned();
let it = first
.into_iter()
.chain(second.into_iter().flatten())
.filter_map(move |p| {
// skip any paths that are not files
let path = p.join(&file).canonicalize().ok()?;
path.is_file().then_some(path)
});
Box::new(it)
}
// Like `find_in_dirs`, but searches on the entries of `PATH`.
fn resolve_in_path(&self) -> Box<dyn Iterator<Item = PathBuf>> {
self.resolve_in_dirs(paths())
}
// Like `find_in_dirs`, but searches on the entries of `PATH`, and on `cwd`, in that order.
fn resolve_in_path_or_cwd(&self) -> Box<dyn Iterator<Item = PathBuf>> {
self.resolve_in_dirs(paths().chain(std::env::current_dir().ok()))
}
}