use std::ffi::{OsStr, OsString};
use camino::{Utf8Path, Utf8PathBuf};
use mockable::{DefaultEnv, Env};
use super::{
options::CwdMode,
resolve_error::ResolveError,
workspace_switch::{WORKSPACE_FALLBACK_ENV, WorkspaceSwitch},
};
#[path = "env_path_support.rs"]
mod path_support;
#[cfg(test)]
pub(super) use path_support::DEFAULT_PATHEXT;
#[cfg(windows)]
pub(super) use path_support::candidate_paths;
#[cfg(any(windows, test))]
pub(super) use path_support::parse_pathext;
use path_support::{PathEntry, current_dir_utf8, parse_path_entries};
impl From<Result<String, std::env::VarError>> for WorkspaceSwitch {
fn from(raw: Result<String, std::env::VarError>) -> Self {
match raw {
Ok(value) => Self::Value(value),
Err(std::env::VarError::NotPresent) => Self::Absent,
Err(std::env::VarError::NotUnicode(_)) => Self::NotUnicode,
}
}
}
fn capture_workspace_switch(env: &impl Env) -> WorkspaceSwitch {
let switch = WorkspaceSwitch::from(env.raw(WORKSPACE_FALLBACK_ENV));
if matches!(switch, WorkspaceSwitch::NotUnicode) {
tracing::warn!(
env = WORKSPACE_FALLBACK_ENV,
"workspace fallback disabled because env var is not valid UTF-8",
);
}
switch
}
#[derive(Clone, Debug)]
pub(super) struct EnvSnapshot {
pub(super) cwd: Utf8PathBuf,
pub(super) raw_path: Option<OsString>,
pub(super) raw_pathext: Option<OsString>,
entries: Vec<PathEntry>,
#[cfg(windows)]
pathext: Vec<String>,
workspace_switch: WorkspaceSwitch,
}
impl EnvSnapshot {
#[cfg(any(not(windows), test))]
pub(super) fn capture(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
) -> Result<Self, ResolveError> {
Self::capture_with_env(cwd_override, path_override, &DefaultEnv)
}
#[cfg(any(not(windows), test))]
pub(super) fn capture_with_env(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
) -> Result<Self, ResolveError> {
Self::capture_for_platform(cwd_override, path_override, env)
}
#[cfg(all(windows, test))]
fn capture_for_platform(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
) -> Result<Self, ResolveError> {
Self::capture_impl(cwd_override, path_override, env, None)
}
#[cfg(not(windows))]
fn capture_for_platform(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
) -> Result<Self, ResolveError> {
Self::capture_impl(cwd_override, path_override, env)
}
#[cfg(windows)]
pub(super) fn capture_with_pathext(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
pathext_override: Option<&OsStr>,
) -> Result<Self, ResolveError> {
Self::capture_impl(cwd_override, path_override, &DefaultEnv, pathext_override)
}
#[cfg(not(windows))]
pub(super) fn capture_with_pathext(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
_pathext_override: Option<&OsStr>,
) -> Result<Self, ResolveError> {
Self::capture(cwd_override, path_override)
}
#[cfg(not(windows))]
fn capture_impl(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
) -> Result<Self, ResolveError> {
let (cwd, raw_path, entries) = capture_common(cwd_override, path_override, env)?;
let workspace_switch = capture_workspace_switch(env);
Ok(Self {
cwd,
raw_path,
raw_pathext: None,
entries,
workspace_switch,
})
}
#[cfg(windows)]
fn capture_impl(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
pathext_override: Option<&OsStr>,
) -> Result<Self, ResolveError> {
let (cwd, raw_path, entries) = capture_common(cwd_override, path_override, env)?;
let raw_pathext = pathext_override
.map(OsString::from)
.or_else(|| env.os_string("PATHEXT"));
let pathext = parse_pathext(raw_pathext.as_deref());
let workspace_switch = capture_workspace_switch(env);
Ok(Self {
cwd,
raw_path,
raw_pathext,
entries,
pathext,
workspace_switch,
})
}
pub(super) fn resolved_dirs(&self, mode: CwdMode) -> Vec<&Utf8Path> {
let mut dirs = Vec::new();
let mut cwd_added = matches!(mode, CwdMode::Always);
if cwd_added {
dirs.push(self.cwd.as_path());
}
for entry in &self.entries {
match entry {
PathEntry::Dir(path) => dirs.push(path.as_path()),
PathEntry::CurrentDir if matches!(mode, CwdMode::Auto) && !cwd_added => {
cwd_added = true;
dirs.push(self.cwd.as_path());
}
PathEntry::CurrentDir => {}
}
}
dirs
}
#[cfg(windows)]
pub(super) fn pathext(&self) -> &[String] {
&self.pathext
}
pub(super) fn workspace_fallback_enabled(&self) -> bool {
self.workspace_switch.enabled()
}
#[cfg(test)]
pub(super) fn with_workspace_switch(mut self, switch: WorkspaceSwitch) -> Self {
self.workspace_switch = switch;
self
}
pub(super) const fn workspace_switch(&self) -> &WorkspaceSwitch {
&self.workspace_switch
}
}
fn capture_common(
cwd_override: Option<&Utf8Path>,
path_override: Option<&OsStr>,
env: &impl Env,
) -> Result<(Utf8PathBuf, Option<OsString>, Vec<PathEntry>), ResolveError> {
let cwd = if let Some(override_cwd) = cwd_override {
override_cwd.to_path_buf()
} else {
current_dir_utf8()?
};
let raw_path = path_override
.map(OsString::from)
.or_else(|| env.os_string("PATH"));
let entries = parse_path_entries(raw_path.as_deref(), &cwd)?;
Ok((cwd, raw_path, entries))
}
#[cfg(all(test, not(windows)))]
#[path = "env_tests.rs"]
mod tests;
#[cfg(all(test, windows))]
#[path = "env_windows_tests.rs"]
mod windows_tests;