use super::{Shell, ShellType};
use crate::{
app::App,
tools::{canonicalize, warn},
};
use std::path::Path;
pub fn get_path_strings(shell: &Shell, app: &App, using_env_var: bool) -> (String, String) {
let zv_dir = app.path();
let bin_path = app.bin_path();
if using_env_var {
format_absolute_paths(shell, zv_dir, bin_path)
} else {
get_default_path_strings(shell)
}
}
pub fn format_absolute_paths(shell: &Shell, zv_dir: &Path, bin_path: &Path) -> (String, String) {
(
normalize_path_for_shell(shell, zv_dir),
normalize_path_for_shell(shell, bin_path),
)
}
pub fn get_default_path_strings(shell: &Shell) -> (String, String) {
if shell.get_home_dir().is_none() {
warn("Unable to determine home directory. zv may not work correctly.");
}
match shell.shell_type {
ShellType::PowerShell => {
if shell.is_powershell_in_unix() {
if std::env::var("HOME").is_err() {
warn(
"HOME environment variable is not set. PowerShell on Unix requires HOME to be set for zv to work properly.",
);
}
("${HOME}/.zv".to_string(), "${HOME}/.zv/bin".to_string())
} else {
if std::env::var("USERPROFILE").is_ok() {
(
"$env:USERPROFILE\\.zv".to_string(),
"$env:USERPROFILE\\.zv\\bin".to_string(),
)
} else if std::env::var("HOME").is_ok() {
warn(
"USERPROFILE not found, falling back to HOME for PowerShell. This may not work as expected on Windows.",
);
(
"$env:HOME\\.zv".to_string(),
"$env:HOME\\.zv\\bin".to_string(),
)
} else {
warn(
"Neither USERPROFILE nor HOME environment variables are set. PowerShell requires one of these to be set for zv to work properly.",
);
(
"$env:USERPROFILE\\.zv".to_string(),
"$env:USERPROFILE\\.zv\\bin".to_string(),
)
}
}
}
ShellType::Cmd => {
if std::env::var("USERPROFILE").is_err() {
warn(
"USERPROFILE environment variable is not set. CMD requires USERPROFILE to be set for zv to work properly.",
);
}
(
"%USERPROFILE%\\.zv".to_string(),
"%USERPROFILE%\\.zv\\bin".to_string(),
)
}
_ => {
if std::env::var("HOME").is_err() {
warn(
"HOME environment variable is not set. Unix-like shells require HOME to be set for zv to work properly.",
);
}
("${HOME}/.zv".to_string(), "${HOME}/.zv/bin".to_string())
}
}
}
pub fn check_dir_in_path(path: &Path) -> bool {
if !path.is_dir() {
return false;
}
let target_path = match canonicalize(path) {
Ok(p) => p,
Err(_) => return false,
};
let path_var = match std::env::var("PATH") {
Ok(var) => var,
Err(_) => return false,
};
let separator = if cfg!(windows) { ';' } else { ':' };
path_var
.split(separator)
.filter(|p| !p.is_empty()) .map(Path::new)
.filter(|p| p.is_dir()) .filter_map(|p| canonicalize(p).ok()) .any(|candidate_path| candidate_path == target_path)
}
pub fn check_dir_in_path_for_shell(shell: &Shell, path: &Path) -> bool {
if !path.is_dir() {
return false;
}
let target_path = match canonicalize(path) {
Ok(p) => p,
Err(_) => return false,
};
let path_var = match std::env::var("PATH") {
Ok(var) => var,
Err(_) => return false,
};
let separator = shell.get_path_separator();
path_var
.split(separator)
.filter(|p| !p.is_empty()) .map(Path::new)
.filter(|p| p.is_dir()) .filter_map(|p| canonicalize(p).ok()) .any(|candidate_path| candidate_path == target_path)
}
pub fn normalize_path_for_shell(shell: &Shell, path: &Path) -> String {
let path_str = path.to_string_lossy();
if shell.is_windows_shell() && !shell.is_powershell_in_unix() {
path_str.replace('/', "\\")
} else if shell.is_unix_shell_in_windows() {
path_str.replace('\\', "/")
} else {
path_str.replace('\\', "/")
}
}
pub fn escape_path_for_shell(shell: &Shell, path: &str) -> String {
match shell.shell_type {
ShellType::PowerShell => {
if shell.is_powershell_in_unix() {
if path.contains(' ')
|| path.contains('\'')
|| path.contains('\\')
|| path.contains('`')
{
format!("'{}'", path.replace('\'', "'\"'\"'"))
} else {
path.to_string()
}
} else {
if path.contains(' ') || path.contains('\'') || path.contains('`') {
format!("'{}'", path.replace('\'', "''"))
} else {
path.to_string()
}
}
}
ShellType::Cmd => {
if path.contains(' ') {
format!("\"{}\"", path)
} else {
path.to_string()
}
}
ShellType::Fish => {
if path.contains(' ') || path.contains('\'') || path.contains('\\') {
format!("'{}'", path.replace('\'', "\\'"))
} else {
path.to_string()
}
}
_ => {
if path.contains(' ')
|| path.contains('\'')
|| path.contains('\\')
|| path.contains('`')
{
format!("'{}'", path.replace('\'', "'\"'\"'"))
} else {
path.to_string()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shell::{OsFlavor, ShellContext};
use std::path::PathBuf;
fn create_test_shell(
shell_type: ShellType,
target_os: OsFlavor,
is_wsl: bool,
is_emulated: bool,
) -> Shell {
Shell {
shell_type,
context: ShellContext {
target_os,
is_wsl,
is_emulated,
},
}
}
#[test]
fn test_get_path_separator() {
let powershell_unix = create_test_shell(ShellType::PowerShell, OsFlavor::Unix, false, true);
let powershell_win =
create_test_shell(ShellType::PowerShell, OsFlavor::Windows, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
assert_eq!(powershell_unix.get_path_separator(), ':'); assert_eq!(powershell_win.get_path_separator(), ';'); assert_eq!(cmd.get_path_separator(), ';');
assert_eq!(bash.get_path_separator(), ':');
}
#[test]
fn test_path_separator_unix_shell_in_windows_aware() {
let bash_win = create_test_shell(ShellType::Bash, OsFlavor::Windows, false, true);
let zsh_win = create_test_shell(ShellType::Zsh, OsFlavor::Windows, false, true);
let fish_win = create_test_shell(ShellType::Fish, OsFlavor::Windows, false, true);
let powershell_win =
create_test_shell(ShellType::PowerShell, OsFlavor::Windows, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
assert_eq!(bash_win.get_path_separator(), ':');
assert_eq!(zsh_win.get_path_separator(), ':');
assert_eq!(fish_win.get_path_separator(), ':');
assert_eq!(powershell_win.get_path_separator(), ';');
assert_eq!(cmd.get_path_separator(), ';');
}
#[test]
fn test_escape_path_for_shell() {
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let path_with_spaces = "/path with spaces/bin";
assert_eq!(
escape_path_for_shell(&bash, path_with_spaces),
"'/path with spaces/bin'"
);
assert_eq!(
escape_path_for_shell(&cmd, path_with_spaces),
"\"/path with spaces/bin\""
);
let simple_path = "/simple/path";
assert_eq!(escape_path_for_shell(&bash, simple_path), "/simple/path");
}
#[test]
fn test_normalize_path_for_shell() {
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let unix_path = PathBuf::from("/home/user/.zv/bin");
let windows_path = PathBuf::from("C:\\Users\\user\\.zv\\bin");
assert!(normalize_path_for_shell(&bash, &windows_path).contains('/'));
assert!(normalize_path_for_shell(&cmd, &unix_path).contains('\\'));
}
#[test]
fn test_normalize_path_unix_shell_in_windows_aware() {
let bash_win = create_test_shell(ShellType::Bash, OsFlavor::Windows, false, true);
let zsh_win = create_test_shell(ShellType::Zsh, OsFlavor::Windows, false, true);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let powershell_unix = create_test_shell(ShellType::PowerShell, OsFlavor::Unix, false, true);
let powershell_win =
create_test_shell(ShellType::PowerShell, OsFlavor::Windows, false, false);
let mixed_path = PathBuf::from("C:\\Users\\user\\mixed/path\\example");
let bash_result = normalize_path_for_shell(&bash_win, &mixed_path);
assert!(!bash_result.contains('\\'));
assert!(bash_result.contains('/'));
let zsh_result = normalize_path_for_shell(&zsh_win, &mixed_path);
assert!(!zsh_result.contains('\\'));
assert!(zsh_result.contains('/'));
let cmd_result = normalize_path_for_shell(&cmd, &mixed_path);
assert!(cmd_result.contains('\\'));
assert!(!cmd_result.contains('/'));
let ps_unix_result = normalize_path_for_shell(&powershell_unix, &mixed_path);
assert!(ps_unix_result.contains('/'));
assert!(!ps_unix_result.contains('\\'));
let ps_win_result = normalize_path_for_shell(&powershell_win, &mixed_path);
assert!(ps_win_result.contains('\\'));
assert!(!ps_win_result.contains('/'));
}
#[test]
fn test_get_default_path_strings() {
let powershell_unix = create_test_shell(ShellType::PowerShell, OsFlavor::Unix, false, true);
let powershell_win =
create_test_shell(ShellType::PowerShell, OsFlavor::Windows, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
let (zv_dir, zv_bin) = get_default_path_strings(&powershell_unix);
assert!(zv_dir.contains("${HOME}"));
assert!(zv_bin.contains("${HOME}"));
let (zv_dir, zv_bin) = get_default_path_strings(&powershell_win);
assert!(zv_dir.contains("$env:USERPROFILE") || zv_dir.contains("$env:HOME"));
assert!(zv_bin.contains("$env:USERPROFILE") || zv_bin.contains("$env:HOME"));
let (zv_dir, zv_bin) = get_default_path_strings(&cmd);
assert!(zv_dir.contains("%USERPROFILE%"));
assert!(zv_bin.contains("%USERPROFILE%"));
let (zv_dir, zv_bin) = get_default_path_strings(&bash);
assert!(zv_dir.contains("${HOME}"));
assert!(zv_bin.contains("${HOME}"));
}
#[test]
fn test_format_absolute_paths_utilizes_normalize() {
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
let cmd = create_test_shell(ShellType::Cmd, OsFlavor::Windows, false, false);
let unix_path = PathBuf::from("/home/user/.zv");
let unix_bin_path = PathBuf::from("/home/user/.zv/bin");
let (zv_dir, bin_path) = format_absolute_paths(&bash, &unix_path, &unix_bin_path);
assert!(zv_dir.contains('/'));
assert!(bin_path.contains('/'));
let (zv_dir, bin_path) = format_absolute_paths(&cmd, &unix_path, &unix_bin_path);
assert!(zv_dir.contains('\\'));
assert!(bin_path.contains('\\'));
}
#[test]
fn test_check_dir_in_path_for_shell_vs_generic() {
let bash = create_test_shell(ShellType::Bash, OsFlavor::Unix, false, false);
let test_path = PathBuf::from("/nonexistent/path");
let generic_result = check_dir_in_path(&test_path);
let shell_aware_result = check_dir_in_path_for_shell(&bash, &test_path);
assert_eq!(generic_result, shell_aware_result);
}
#[test]
fn test_powershell_on_unix_behavior() {
let shell = create_test_shell(ShellType::PowerShell, OsFlavor::Unix, false, true);
assert_eq!(shell.get_path_separator(), ':');
let unix_path = PathBuf::from("/home/user/.zv");
let normalized = normalize_path_for_shell(&shell, &unix_path);
assert!(normalized.contains('/'));
assert!(!normalized.contains('\\'));
assert_eq!(shell.env_file_name(), "env");
let rc_files = shell.get_rc_files();
assert!(!rc_files.is_empty());
let (zv_dir, _) = get_default_path_strings(&shell);
assert!(zv_dir.contains("${HOME}"));
assert!(zv_dir.contains(".zv"));
assert!(!zv_dir.contains("$env:HOME"));
}
}