use std::path::Path;
use crate::domain::sanitize::sanitize_for_display;
use crate::infra::env::HomeDirResolver;
use crate::{CmdOutcome, CmdResult};
pub(crate) fn handle_add(
config_path: &Path,
key: &str,
expand: &str,
when_command_exists: Option<&[String]>,
env: &dyn HomeDirResolver,
) -> CmdResult {
crate::app::config::append_abbr_to_file(config_path, key, expand, when_command_exists)?;
println!(
"Added: {} -> {}",
sanitize_for_display(key),
sanitize_for_display(expand)
);
crate::cmd::init::refresh_existing_caches(config_path, env);
Ok(CmdOutcome::Ok)
}
pub(crate) fn handle_remove(
config_path: &Path,
key: &str,
env: &dyn HomeDirResolver,
) -> CmdResult {
let removed = crate::app::config::remove_abbr_from_file(config_path, key)?;
if removed > 0 {
println!(
"Removed {} rule(s) for '{}'",
removed,
sanitize_for_display(key)
);
crate::cmd::init::refresh_existing_caches(config_path, env);
} else {
println!("No rule found for '{}'", sanitize_for_display(key));
}
Ok(CmdOutcome::Ok)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::infra::env::EnvHomeDir;
use crate::infra::integration_cache::cache_path;
use crate::domain::shell::Shell;
use std::collections::HashMap;
fn env_with(home: &std::path::Path) -> EnvHomeDir<impl Fn(&str) -> Option<String> + Send + Sync> {
let cache = home.join(".cache");
let owned: HashMap<String, String> = HashMap::from([
("HOME".to_string(), home.to_string_lossy().into_owned()),
("XDG_CACHE_HOME".to_string(), cache.to_string_lossy().into_owned()),
]);
EnvHomeDir::new(move |n| owned.get(n).cloned())
}
fn write_seed_config(dir: &std::path::Path, body: &str) -> std::path::PathBuf {
let p = dir.join("config.toml");
std::fs::write(&p, body).unwrap();
p
}
#[test]
fn handle_add_refreshes_existing_bash_cache() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
let env = env_with(home);
let cfg = write_seed_config(
home,
"version = 1\n[keybind.trigger]\ndefault = \"space\"\n",
);
let bash_cache = cache_path(Shell::Bash, &env).unwrap().unwrap();
std::fs::create_dir_all(bash_cache.parent().unwrap()).unwrap();
std::fs::write(&bash_cache, "stale").unwrap();
handle_add(&cfg, "ggg", "git get", None, &env).expect("handle_add");
let refreshed = std::fs::read_to_string(&bash_cache).unwrap();
assert!(
refreshed != "stale",
"bash cache must be regenerated after handle_add"
);
assert!(
refreshed.contains("runex-integration-version:"),
"regenerated cache must contain the version header"
);
}
#[test]
fn handle_add_does_not_create_caches_for_uninitialized_shells() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
let env = env_with(home);
let cfg = write_seed_config(
home,
"version = 1\n[keybind.trigger]\ndefault = \"space\"\n",
);
handle_add(&cfg, "ggg", "git get", None, &env).expect("handle_add");
for shell in [Shell::Bash, Shell::Zsh, Shell::Pwsh, Shell::Nu] {
let p = cache_path(shell, &env).unwrap().unwrap();
assert!(
!p.exists(),
"{:?} cache must not be auto-created by handle_add: {}",
shell,
p.display()
);
}
}
#[test]
fn handle_remove_zero_match_does_not_touch_cache() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
let env = env_with(home);
let cfg = write_seed_config(
home,
"version = 1\n[keybind.trigger]\ndefault = \"space\"\n",
);
let bash_cache = cache_path(Shell::Bash, &env).unwrap().unwrap();
std::fs::create_dir_all(bash_cache.parent().unwrap()).unwrap();
let sentinel = "this-is-the-original-cache-content";
std::fs::write(&bash_cache, sentinel).unwrap();
handle_remove(&cfg, "nonexistent-key", &env).expect("handle_remove");
let after = std::fs::read_to_string(&bash_cache).unwrap();
assert_eq!(
after, sentinel,
"no-op remove must leave the cache untouched"
);
}
}