use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use crate::sys::Runtime;
use super::ShellState;
fn resolve_path(cwd: &Path, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
cwd.join(path)
}
}
pub(super) fn resolve_against_shell_cwd(state: &ShellState, path: &Path) -> PathBuf {
resolve_path(&state.path_state.cwd, path)
}
pub(super) fn resolve_redirection_target(state: &ShellState, path: &Path) -> PathBuf {
resolve_against_shell_cwd(state, path)
}
fn cdpath_applies(path: &str) -> bool {
if path.is_empty() || path.starts_with('/') {
return false;
}
let first = path.split('/').next().unwrap_or(path);
first != "." && first != ".."
}
pub(super) fn resolve_cd_target(state: &ShellState, operand: &str) -> (PathBuf, bool) {
let raw = PathBuf::from(operand);
if !cdpath_applies(operand) {
return (resolve_against_shell_cwd(state, &raw), false);
}
let Some(cdpath) = state.env_get("CDPATH") else {
return (resolve_against_shell_cwd(state, &raw), false);
};
for entry in cdpath.split(':') {
let base = if entry.is_empty() {
state.path_state.cwd.clone()
} else {
resolve_against_shell_cwd(state, Path::new(entry))
};
let candidate = base.join(operand);
if fs::metadata(&candidate).is_ok_and(|meta| meta.is_dir()) {
return (candidate, !entry.is_empty());
}
}
(resolve_against_shell_cwd(state, &raw), false)
}
pub(super) fn resolve_dot_path(state: &ShellState, path_var: &str, name: &str) -> PathBuf {
if name.contains('/') {
return resolve_against_shell_cwd(state, Path::new(name));
}
for dir in path_var.split(':') {
let candidate = resolve_against_shell_cwd(state, Path::new(dir)).join(name);
if fs::metadata(&candidate).is_ok_and(|meta| meta.is_file())
&& fs::File::open(&candidate).is_ok()
{
return candidate;
}
}
resolve_against_shell_cwd(state, Path::new(name))
}
pub(super) fn resolve_command_path<R: Runtime + ?Sized>(
state: &ShellState,
runtime: &R,
program: &str,
path_var: &str,
) -> Result<PathBuf, io::Error> {
runtime.resolve_command_path(program, path_var, &state.path_state.cwd)
}