use std::env;
use std::path::PathBuf;
use clap::Parser;
use crate::engine::CommandResult;
#[derive(Parser)]
#[command(name = "cd", about = "Change the current directory")]
struct CdArgs {
path: Option<String>,
}
pub(crate) fn execute(args: &[&str], dir_stack: &mut Vec<PathBuf>) -> CommandResult {
let parsed = match super::parse_args::<CdArgs>("cd", args) {
Ok(a) => a,
Err(result) => return result,
};
let target: PathBuf = if let Some(path) = parsed.path {
PathBuf::from(path)
} else {
match env::var_os("HOME") {
Some(home) => PathBuf::from(home),
None => {
let msg = "jarvish: cd: HOME not set\n".to_string();
eprint!("{msg}");
return CommandResult::error(msg, 1);
}
}
};
let old_pwd = env::var("PWD").ok().or_else(|| {
env::current_dir()
.ok()
.map(|p| p.to_string_lossy().into_owned())
});
match env::set_current_dir(&target) {
Ok(()) => {
if let Some(old) = old_pwd {
dir_stack.push(PathBuf::from(&old));
env::set_var("OLDPWD", &old);
}
if let Ok(new_pwd) = env::current_dir() {
env::set_var("PWD", &new_pwd);
}
CommandResult::success(String::new())
}
Err(e) => {
let msg = format!("jarvish: cd: {}: {e}\n", target.display());
eprint!("{msg}");
CommandResult::error(msg, 1)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::builtins::cwd::test_helpers::CwdGuard;
use crate::engine::LoopAction;
use serial_test::serial;
use std::env;
use std::path::PathBuf;
#[test]
#[serial]
fn cd_to_specified_directory() {
let _guard = CwdGuard::new();
let tmpdir = tempfile::tempdir().expect("failed to create tempdir");
let target = tmpdir.path().to_path_buf();
let result = execute(&[target.to_str().unwrap()], &mut Vec::new());
assert_eq!(result.exit_code, 0);
assert_eq!(result.action, LoopAction::Continue);
let cwd = env::current_dir().unwrap();
assert_eq!(cwd.canonicalize().unwrap(), target.canonicalize().unwrap());
}
#[test]
#[serial]
fn cd_no_args_goes_home() {
let _guard = CwdGuard::new();
if let Some(home) = env::var_os("HOME") {
let result = execute(&[], &mut Vec::new());
assert_eq!(result.exit_code, 0);
let cwd = env::current_dir().unwrap();
assert_eq!(
cwd.canonicalize().unwrap(),
PathBuf::from(&home).canonicalize().unwrap()
);
}
}
#[test]
#[serial]
fn cd_nonexistent_path_returns_error() {
let _guard = CwdGuard::new();
let result = execute(&["/nonexistent_path_that_does_not_exist"], &mut Vec::new());
assert_ne!(result.exit_code, 0);
assert!(result.stderr.contains("cd:"));
}
#[test]
fn cd_help_returns_success() {
let result = execute(&["--help"], &mut Vec::new());
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("cd"));
}
#[test]
#[serial]
fn cd_updates_pwd_env_var() {
let _guard = CwdGuard::new();
let tmpdir = tempfile::tempdir().expect("failed to create tempdir");
let target = tmpdir.path().to_path_buf();
let result = execute(&[target.to_str().unwrap()], &mut Vec::new());
assert_eq!(result.exit_code, 0);
let pwd = env::var("PWD").expect("PWD should be set after cd");
assert_eq!(
PathBuf::from(&pwd).canonicalize().unwrap(),
target.canonicalize().unwrap()
);
}
#[test]
#[serial]
fn cd_updates_oldpwd_env_var() {
let _guard = CwdGuard::new();
let original_pwd = env::current_dir().unwrap();
env::set_var("PWD", &original_pwd);
let tmpdir = tempfile::tempdir().expect("failed to create tempdir");
let target = tmpdir.path().to_path_buf();
let result = execute(&[target.to_str().unwrap()], &mut Vec::new());
assert_eq!(result.exit_code, 0);
let oldpwd = env::var("OLDPWD").expect("OLDPWD should be set after cd");
assert_eq!(
PathBuf::from(&oldpwd).canonicalize().unwrap(),
original_pwd.canonicalize().unwrap()
);
}
}