use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default)]
pub struct CliOverrides {
pub config_home: Option<PathBuf>,
pub config_file: Option<PathBuf>,
pub env_file: Option<PathBuf>,
#[cfg(feature = "plugin")]
pub plugin_file: Option<PathBuf>,
}
impl CliOverrides {
pub fn from_path_strings(
config_home: Option<&str>,
config_file: Option<&str>,
env_file: Option<&str>,
#[cfg(feature = "plugin")] plugin_file: Option<&str>,
cwd: &Path,
) -> Self {
Self {
config_home: config_home.map(|s| absolutize_cli_path(s, cwd)),
config_file: config_file.map(|s| absolutize_cli_path(s, cwd)),
env_file: env_file.map(|s| absolutize_cli_path(s, cwd)),
#[cfg(feature = "plugin")]
plugin_file: plugin_file.map(|s| absolutize_cli_path(s, cwd)),
}
}
}
fn absolutize_cli_path(path: &str, cwd: &Path) -> PathBuf {
nu_path::expand_path_with(path, cwd, true)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Component;
#[test]
fn relative_paths_join_cwd() {
let cwd = PathBuf::from("/tmp/work");
let cli = CliOverrides::from_path_strings(
Some("cfg-home"),
Some("config.nu"),
Some("env.nu"),
#[cfg(feature = "plugin")]
Some("plugin.msgpackz"),
&cwd,
);
assert_eq!(
cli.config_home.as_deref(),
Some(Path::new("/tmp/work/cfg-home"))
);
assert_eq!(
cli.config_file.as_deref(),
Some(Path::new("/tmp/work/config.nu"))
);
assert_eq!(cli.env_file.as_deref(), Some(Path::new("/tmp/work/env.nu")));
#[cfg(feature = "plugin")]
assert_eq!(
cli.plugin_file.as_deref(),
Some(Path::new("/tmp/work/plugin.msgpackz"))
);
}
#[test]
fn absolute_paths_kept() {
let cwd = PathBuf::from("/tmp/work");
let abs = if cfg!(windows) {
r"C:\nushell\config.nu"
} else {
"/etc/nushell/config.nu"
};
let cli = CliOverrides::from_path_strings(
None,
Some(abs),
None,
#[cfg(feature = "plugin")]
None,
&cwd,
);
assert_eq!(cli.config_file.as_deref(), Some(Path::new(abs)));
}
#[test]
fn dot_config_home_is_cwd_without_trailing_curdir() {
let cwd = PathBuf::from("/tmp/work");
let cli = CliOverrides::from_path_strings(
Some("."),
None,
None,
#[cfg(feature = "plugin")]
None,
&cwd,
);
assert_eq!(cli.config_home.as_deref(), Some(cwd.as_path()));
assert!(
!cli.config_home
.as_ref()
.unwrap()
.components()
.any(|c| matches!(c, Component::CurDir))
);
}
#[test]
fn relative_dot_slash_subdir_normalized() {
let cwd = PathBuf::from("/tmp/work");
let cli = CliOverrides::from_path_strings(
Some("./subdir"),
Some("./cfg.nu"),
None,
#[cfg(feature = "plugin")]
None,
&cwd,
);
assert_eq!(
cli.config_home.as_deref(),
Some(Path::new("/tmp/work/subdir"))
);
assert_eq!(
cli.config_file.as_deref(),
Some(Path::new("/tmp/work/cfg.nu"))
);
}
#[test]
fn parent_dir_components_resolved_lexically() {
let cwd = PathBuf::from("/tmp/work/nested");
assert_eq!(
absolutize_cli_path("../sibling", &cwd),
PathBuf::from("/tmp/work/sibling")
);
}
#[test]
fn tilde_config_home_expands_to_user_home_without_cwd_join() {
let Some(home) = dirs::home_dir() else {
return; };
let cwd = PathBuf::from("/tmp/unrelated/cwd");
let cli = CliOverrides::from_path_strings(
Some("~/.config/nushell"),
None,
None,
#[cfg(feature = "plugin")]
None,
&cwd,
);
let expected = home.join(".config").join("nushell");
assert_eq!(
cli.config_home.as_deref(),
Some(expected.as_path()),
"tilde should expand via $HOME, not join under cwd"
);
assert!(
!cli.config_home.as_ref().unwrap().starts_with(&cwd),
"must not resolve under cwd"
);
}
#[test]
fn bare_tilde_expands_to_home() {
let Some(home) = dirs::home_dir() else {
return;
};
let cwd = PathBuf::from("/tmp/unrelated");
assert_eq!(absolutize_cli_path("~", &cwd), home);
}
}