use std::ffi::OsStr;
use std::path::{Path, PathBuf};
pub(crate) fn find(named: &Path, search: &OsStr) -> Option<PathBuf> {
find_in(named, search, super::proc::can_run)
}
fn find_in(
named: &Path,
search: &OsStr,
mut runnable: impl FnMut(&Path) -> bool,
) -> Option<PathBuf> {
if named.components().count() > 1 {
let named = std::path::absolute(named).ok()?;
return runnable(&named).then_some(named);
}
find_among(named, std::env::split_paths(search), runnable)
}
pub(crate) fn find_among(
named: &Path,
dirs: impl IntoIterator<Item = PathBuf>,
mut runnable: impl FnMut(&Path) -> bool,
) -> Option<PathBuf> {
dirs.into_iter()
.filter(|dir| dir.is_absolute())
.map(|dir| dir.join(named))
.find(|candidate| runnable(candidate))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_directory_named_from_the_root_is_looked_in() {
let mut looked = Vec::new();
let root = std::env::temp_dir().join("bin");
let search = std::env::join_paths([
"".into(),
"bin".into(),
"./node_modules/.bin".into(),
root.clone(),
])
.expect("a search path");
let found = find_in(Path::new("codex"), &search, |candidate| {
looked.push(candidate.to_path_buf());
false
});
assert_eq!(found, None);
assert_eq!(looked, [root.join("codex")]);
}
#[test]
fn a_program_named_relative_to_here_is_found_by_its_full_path() {
let found = find_in(Path::new("./bin/codex"), "".as_ref(), |_| true).expect("found");
assert!(found.is_absolute(), "{}", found.display());
assert_eq!(
found,
std::env::current_dir()
.expect("a working directory")
.join("bin/codex")
);
}
}