use std::ffi::OsStr;
use camino::{Utf8Path, Utf8PathBuf};
#[cfg(any(windows, test))]
use indexmap::IndexSet;
use crate::localization::{self, keys};
use super::super::resolve_error::ResolveError;
#[derive(Clone, Debug)]
pub(super) enum PathEntry {
Dir(Utf8PathBuf),
CurrentDir,
}
pub(super) fn parse_path_entries(
raw: Option<&OsStr>,
cwd: &Utf8Path,
) -> Result<Vec<PathEntry>, ResolveError> {
let mut entries = Vec::new();
let Some(raw_value) = raw else {
return Ok(entries);
};
for (index, component) in std::env::split_paths(raw_value).enumerate() {
if component.as_os_str().is_empty() {
entries.push(PathEntry::CurrentDir);
continue;
}
let utf8 = Utf8PathBuf::from_path_buf(component).map_err(|_| {
ResolveError::args(
localization::message(keys::STDLIB_WHICH_PATH_ENTRY_NON_UTF8)
.with_arg("index", index),
)
})?;
let resolved = if utf8.is_absolute() {
utf8
} else {
cwd.join(utf8)
};
entries.push(PathEntry::Dir(resolved));
}
Ok(entries)
}
#[cfg(any(windows, test))]
pub(in crate::stdlib::which) const DEFAULT_PATHEXT: &[&str] = &[
".com", ".exe", ".bat", ".cmd", ".vbs", ".vbe", ".js", ".jse", ".wsf", ".wsh", ".msc",
];
#[cfg(any(windows, test))]
fn default_pathext() -> Vec<String> {
DEFAULT_PATHEXT.iter().copied().map(String::from).collect()
}
#[cfg(any(windows, test))]
pub(in crate::stdlib::which) fn parse_pathext(raw: Option<&OsStr>) -> Vec<String> {
let mut dedup = IndexSet::new();
let source = raw.map_or_else(
|| DEFAULT_PATHEXT.join(";"),
|value| value.to_string_lossy().into_owned(),
);
for segment in source.split(';') {
let trimmed = segment.trim();
if trimmed.is_empty() {
continue;
}
let mut normalised = trimmed.to_ascii_lowercase();
if !normalised.starts_with('.') {
normalised.insert(0, '.');
}
dedup.insert(normalised);
}
if dedup.is_empty() {
default_pathext()
} else {
dedup.into_iter().collect()
}
}
pub(super) fn current_dir_utf8() -> Result<Utf8PathBuf, ResolveError> {
let cwd = std::env::current_dir().map_err(|source| ResolveError::CwdResolve { source })?;
Utf8PathBuf::from_path_buf(cwd).map_err(|_| ResolveError::CwdNonUtf8)
}
#[cfg(windows)]
pub(in crate::stdlib::which) fn candidate_paths(
dir: &Utf8Path,
command: &str,
pathext: &[String],
) -> Vec<Utf8PathBuf> {
let mut paths = Vec::new();
let base = dir.join(command);
if Utf8Path::new(command).extension().is_some() {
paths.push(base);
return paths;
}
for ext in pathext {
let mut candidate = base.as_str().to_owned();
candidate.push_str(ext);
paths.push(Utf8PathBuf::from(candidate));
}
paths
}