use std::path::PathBuf;
use zenops_expand::{ExpandError, ExpandLookup, ExpandStr};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to expand executable path from {0:?}: {1}")]
ExpandError(ExpandStr, ExpandError),
#[error("Failed to find executable from {0:?}: {1}")]
Which(String, which::Error),
}
#[derive(Debug, Clone, Default)]
pub struct SearchPath {
dirs: Vec<PathBuf>,
}
impl SearchPath {
pub fn from_env() -> Self {
Self {
dirs: std::env::var_os("PATH")
.map(|p| std::env::split_paths(&p).collect())
.unwrap_or_default(),
}
}
pub fn new(dirs: impl Into<Vec<PathBuf>>) -> Self {
Self { dirs: dirs.into() }
}
fn as_os_string(&self) -> std::ffi::OsString {
std::env::join_paths(&self.dirs)
.expect("SearchPath dirs were validated as joinable at construction")
}
}
pub fn get_path(
binary: impl AsRef<str> + Into<String>,
path: &SearchPath,
) -> Result<Option<PathBuf>, Error> {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
::which::which_in(binary.as_ref(), Some(path.as_os_string()), &cwd)
.map(Some)
.or_else(|e| match e {
which::Error::CannotFindBinaryPath
| which::Error::CannotGetCurrentDirAndPathListEmpty => Ok(None),
which::Error::CannotCanonicalize => Err(Error::Which(binary.into(), e)),
})
}
pub fn exists(binary: impl AsRef<str> + Into<String>, path: &SearchPath) -> Result<bool, Error> {
get_path(binary, path).map(|v| v.is_some())
}
pub fn expand_and_get_path(
binary: &ExpandStr,
lookup: &impl ExpandLookup,
path: &SearchPath,
) -> Result<Option<PathBuf>, Error> {
let b = binary
.expand_to_string(lookup)
.map_err(|e| Error::ExpandError(binary.clone(), e))?;
get_path(b, path)
}
pub fn expand_and_exists(
binary: &ExpandStr,
lookup: &impl ExpandLookup,
path: &SearchPath,
) -> Result<bool, Error> {
expand_and_get_path(binary, lookup, path).map(|v| v.is_some())
}