use indexmap::IndexMap;
use std::sync::OnceLock;
pub type ArrayGetFn = fn(&str) -> Option<Vec<String>>;
pub type AssocGetFn = fn(&str) -> Option<IndexMap<String, String>>;
pub type ArraySetFn = fn(&str, Vec<String>);
pub type AssocSetFn = fn(&str, IndexMap<String, String>);
pub type ScalarUnsetFn = fn(&str);
pub type ArrayUnsetFn = fn(&str);
pub type AssocUnsetFn = fn(&str);
pub type DispatchFunctionCallFn = fn(&str, &[String]) -> Option<i32>;
pub type RunFunctionBodyFn = fn(&str, &[String]) -> Option<i32>;
pub type ExecuteScriptFn = fn(&str) -> Result<i32, String>;
pub type ExecuteScriptZshPipelineFn = fn(&str) -> Result<i32, String>;
pub type RunCommandSubstitutionFn = fn(&str) -> String;
pub type PparamsGetFn = fn() -> Vec<String>;
pub type PparamsSetFn = fn(Vec<String>);
pub type UnregisterFunctionFn = fn(&str) -> bool;
static ARRAY_GET: OnceLock<ArrayGetFn> = OnceLock::new();
static ASSOC_GET: OnceLock<AssocGetFn> = OnceLock::new();
static ARRAY_SET: OnceLock<ArraySetFn> = OnceLock::new();
static ASSOC_SET: OnceLock<AssocSetFn> = OnceLock::new();
static SCALAR_UNSET: OnceLock<ScalarUnsetFn> = OnceLock::new();
static ARRAY_UNSET: OnceLock<ArrayUnsetFn> = OnceLock::new();
static ASSOC_UNSET: OnceLock<AssocUnsetFn> = OnceLock::new();
static DISPATCH_FUNCTION_CALL: OnceLock<DispatchFunctionCallFn> = OnceLock::new();
static RUN_FUNCTION_BODY: OnceLock<RunFunctionBodyFn> = OnceLock::new();
static EXECUTE_SCRIPT: OnceLock<ExecuteScriptFn> = OnceLock::new();
static EXECUTE_SCRIPT_ZSH_PIPELINE: OnceLock<ExecuteScriptZshPipelineFn> = OnceLock::new();
static RUN_COMMAND_SUBSTITUTION: OnceLock<RunCommandSubstitutionFn> = OnceLock::new();
static PPARAMS_GET: OnceLock<PparamsGetFn> = OnceLock::new();
static PPARAMS_SET: OnceLock<PparamsSetFn> = OnceLock::new();
static UNREGISTER_FUNCTION: OnceLock<UnregisterFunctionFn> = OnceLock::new();
pub fn install_array_get(f: ArrayGetFn) {
let _ = ARRAY_GET.set(f);
}
pub fn install_assoc_get(f: AssocGetFn) {
let _ = ASSOC_GET.set(f);
}
pub fn install_array_set(f: ArraySetFn) {
let _ = ARRAY_SET.set(f);
}
pub fn install_assoc_set(f: AssocSetFn) {
let _ = ASSOC_SET.set(f);
}
pub fn install_scalar_unset(f: ScalarUnsetFn) {
let _ = SCALAR_UNSET.set(f);
}
pub fn install_array_unset(f: ArrayUnsetFn) {
let _ = ARRAY_UNSET.set(f);
}
pub fn install_assoc_unset(f: AssocUnsetFn) {
let _ = ASSOC_UNSET.set(f);
}
pub fn install_dispatch_function_call(f: DispatchFunctionCallFn) {
let _ = DISPATCH_FUNCTION_CALL.set(f);
}
pub fn install_run_function_body(f: RunFunctionBodyFn) {
let _ = RUN_FUNCTION_BODY.set(f);
}
pub fn install_execute_script(f: ExecuteScriptFn) {
let _ = EXECUTE_SCRIPT.set(f);
}
pub fn install_execute_script_zsh_pipeline(f: ExecuteScriptZshPipelineFn) {
let _ = EXECUTE_SCRIPT_ZSH_PIPELINE.set(f);
}
pub fn install_run_command_substitution(f: RunCommandSubstitutionFn) {
let _ = RUN_COMMAND_SUBSTITUTION.set(f);
}
pub fn install_pparams_get(f: PparamsGetFn) {
let _ = PPARAMS_GET.set(f);
}
pub fn install_pparams_set(f: PparamsSetFn) {
let _ = PPARAMS_SET.set(f);
}
pub fn install_unregister_function(f: UnregisterFunctionFn) {
let _ = UNREGISTER_FUNCTION.set(f);
}
pub fn array(name: &str) -> Option<Vec<String>> {
if let Some(f) = ARRAY_GET.get() {
if let Some(v) = f(name) {
return Some(v);
}
}
crate::ported::params::getaparam(name)
}
pub fn assoc(name: &str) -> Option<IndexMap<String, String>> {
ASSOC_GET.get().and_then(|f| f(name))
}
pub fn set_array(name: &str, val: Vec<String>) {
if let Some(f) = ARRAY_SET.get() {
f(name, val);
}
}
pub fn set_assoc(name: &str, val: IndexMap<String, String>) {
if let Some(f) = ASSOC_SET.get() {
f(name, val);
}
}
pub fn unset_scalar(name: &str) {
if let Some(f) = SCALAR_UNSET.get() {
f(name);
}
}
pub fn unset_array(name: &str) {
if let Some(f) = ARRAY_UNSET.get() {
f(name);
}
}
pub fn unset_assoc(name: &str) {
if let Some(f) = ASSOC_UNSET.get() {
f(name);
}
}
pub fn dispatch_function_call(name: &str, args: &[String]) -> Option<i32> {
DISPATCH_FUNCTION_CALL.get().and_then(|f| f(name, args))
}
pub fn run_function_body(name: &str, args: &[String]) -> Option<i32> {
RUN_FUNCTION_BODY.get().and_then(|f| f(name, args))
}
pub fn execute_script(src: &str) -> Result<i32, String> {
match EXECUTE_SCRIPT.get() {
Some(f) => f(src),
None => Ok(0),
}
}
pub fn execute_script_zsh_pipeline(src: &str) -> Result<i32, String> {
match EXECUTE_SCRIPT_ZSH_PIPELINE.get() {
Some(f) => f(src),
None => Ok(0),
}
}
pub fn run_command_substitution(cmd: &str) -> String {
match RUN_COMMAND_SUBSTITUTION.get() {
Some(f) => f(cmd),
None => String::new(),
}
}
pub fn pparams() -> Vec<String> {
PPARAMS_GET.get().map(|f| f()).unwrap_or_default()
}
pub fn set_pparams(v: Vec<String>) {
if let Some(f) = PPARAMS_SET.get() {
f(v);
}
}
pub fn unregister_function(name: &str) -> bool {
UNREGISTER_FUNCTION.get().map(|f| f(name)).unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exec_hooks_corpus_dispatch_returns_none_when_not_installed() {
let _g = crate::test_util::global_state_lock();
let _ = dispatch_function_call("__never_a_real_function_zshrs__", &["a".into()]);
}
#[test]
fn exec_hooks_corpus_execute_script_returns_ok_zero_when_not_installed() {
let _g = crate::test_util::global_state_lock();
let r = execute_script("nothing real");
match r {
Ok(_) | Err(_) => {} }
}
#[test]
fn exec_hooks_corpus_run_command_substitution_default_empty_or_real() {
let _g = crate::test_util::global_state_lock();
let _ = run_command_substitution("echo zshrs_hook_test");
}
#[test]
fn exec_hooks_corpus_array_falls_back_to_getaparam() {
let _g = crate::test_util::global_state_lock();
crate::ported::params::unsetparam("EH_FB");
crate::ported::params::setaparam("EH_FB", vec!["x".into(), "y".into(), "z".into()]);
let got = array("EH_FB");
assert_eq!(
got.as_deref(),
Some(&["x".to_string(), "y".to_string(), "z".to_string()][..]),
"array() hook falls back to params::getaparam",
);
crate::ported::params::unsetparam("EH_FB");
}
#[test]
fn exec_hooks_corpus_pparams_returns_empty_when_not_installed() {
let _g = crate::test_util::global_state_lock();
let p = pparams();
let _ = p; }
#[test]
fn exec_hooks_corpus_unregister_function_default_false() {
let _g = crate::test_util::global_state_lock();
let r = unregister_function("__never_registered_xyz__");
let _ = r;
}
#[test]
fn exec_hooks_corpus_set_pparams_does_not_panic() {
let _g = crate::test_util::global_state_lock();
set_pparams(vec!["a".into(), "b".into()]);
}
#[test]
fn exec_hooks_assoc_returns_none_when_no_hook() {
let _g = crate::test_util::global_state_lock();
let _ = assoc("__never_real_assoc_zshrs__");
}
#[test]
fn exec_hooks_set_array_no_hook_does_not_panic() {
let _g = crate::test_util::global_state_lock();
set_array("__never_real_array_zshrs__", vec!["x".into(), "y".into()]);
}
#[test]
fn exec_hooks_set_assoc_no_hook_does_not_panic() {
let _g = crate::test_util::global_state_lock();
let mut m = IndexMap::new();
m.insert("k".to_string(), "v".to_string());
set_assoc("__never_real_assoc_zshrs__", m);
}
#[test]
fn exec_hooks_unset_variants_no_hook_dont_panic() {
let _g = crate::test_util::global_state_lock();
unset_scalar("__never_real_scalar_zshrs__");
unset_array("__never_real_array_zshrs__");
unset_assoc("__never_real_assoc_zshrs__");
}
#[test]
fn exec_hooks_run_function_body_returns_none_when_no_hook() {
let _g = crate::test_util::global_state_lock();
let _ = run_function_body("__never_a_real_fn_zshrs__", &["a".into()]);
}
#[test]
fn exec_hooks_execute_script_zsh_pipeline_default_ok_zero() {
let _g = crate::test_util::global_state_lock();
let r = execute_script_zsh_pipeline("no hook");
let _ = r;
}
#[test]
fn exec_hooks_array_returns_none_for_missing_name() {
let _g = crate::test_util::global_state_lock();
crate::ported::params::unsetparam("__no_such_array_zshrs__");
let got = array("__no_such_array_zshrs__");
let _ = got;
}
#[test]
fn exec_hooks_array_empty_name_does_not_panic() {
let _g = crate::test_util::global_state_lock();
let _ = array("");
}
#[test]
fn exec_hooks_array_is_idempotent() {
let _g = crate::test_util::global_state_lock();
crate::ported::params::unsetparam("EH_IDEMPOTENT");
crate::ported::params::setaparam("EH_IDEMPOTENT", vec!["a".into(), "b".into()]);
let first = array("EH_IDEMPOTENT");
let second = array("EH_IDEMPOTENT");
assert_eq!(first, second);
crate::ported::params::unsetparam("EH_IDEMPOTENT");
}
#[test]
fn exec_hooks_pparams_returns_vec_not_none() {
let _g = crate::test_util::global_state_lock();
let p: Vec<String> = pparams();
let _ = p; }
#[test]
fn exec_hooks_array_set_get_roundtrip_via_params() {
let _g = crate::test_util::global_state_lock();
crate::ported::params::unsetparam("EH_RT");
let vals = vec!["one".to_string(), "two".to_string(), "three".to_string()];
crate::ported::params::setaparam("EH_RT", vals.clone());
let got = array("EH_RT").expect("set then get should hit params fallback");
assert_eq!(got, vals);
crate::ported::params::unsetparam("EH_RT");
}
#[test]
fn exec_hooks_unregister_function_returns_bool() {
let _g = crate::test_util::global_state_lock();
let r: bool = unregister_function("__never_xyz_zshrs__");
let _ = r;
}
#[test]
fn exec_hooks_run_command_substitution_returns_string_type() {
let _g = crate::test_util::global_state_lock();
let _: String = run_command_substitution("anything");
}
#[test]
fn exec_hooks_execute_script_returns_result_type() {
let _g = crate::test_util::global_state_lock();
let _: Result<i32, String> = execute_script("anything");
}
#[test]
fn exec_hooks_execute_script_zsh_pipeline_returns_result_type() {
let _g = crate::test_util::global_state_lock();
let _: Result<i32, String> = execute_script_zsh_pipeline("anything");
}
#[test]
fn exec_hooks_dispatch_function_call_returns_option_i32() {
let _g = crate::test_util::global_state_lock();
let _: Option<i32> = dispatch_function_call("__never_real__", &[]);
}
#[test]
fn exec_hooks_run_function_body_returns_option_i32() {
let _g = crate::test_util::global_state_lock();
let _: Option<i32> = run_function_body("__never_real__", &[]);
}
#[test]
fn exec_hooks_array_returns_option_vec_string() {
let _g = crate::test_util::global_state_lock();
let _: Option<Vec<String>> = array("anything");
}
#[test]
fn exec_hooks_assoc_returns_option_indexmap() {
let _g = crate::test_util::global_state_lock();
let _: Option<IndexMap<String, String>> = assoc("anything");
}
#[test]
fn exec_hooks_dispatch_empty_args_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = dispatch_function_call("", &[]);
let _ = dispatch_function_call("name", &[]);
}
#[test]
fn exec_hooks_run_function_body_empty_args_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = run_function_body("", &[]);
let _ = run_function_body("name", &[]);
}
#[test]
fn exec_hooks_execute_script_empty_src_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = execute_script("");
let _ = execute_script_zsh_pipeline("");
}
#[test]
fn exec_hooks_run_command_substitution_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _: String = run_command_substitution("");
}
#[test]
fn exec_hooks_unregister_function_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _: bool = unregister_function("");
}
#[test]
fn exec_hooks_unset_with_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
unset_scalar("");
unset_array("");
unset_assoc("");
}
#[test]
fn pparams_deterministic_without_changes() {
let _g = crate::test_util::global_state_lock();
let first = pparams();
for _ in 0..3 {
assert_eq!(
pparams(),
first,
"pparams() must be deterministic across reads"
);
}
}
#[test]
fn pparams_returns_vec_string_no_option() {
let _g = crate::test_util::global_state_lock();
let _: Vec<String> = pparams();
}
#[test]
fn array_with_special_chars_in_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = array("name with spaces");
let _ = array("name/with/slashes");
let _ = array("$dollarsigns");
}
#[test]
fn assoc_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = assoc("");
}
#[test]
fn set_array_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
set_array("", vec![]);
}
#[test]
fn set_assoc_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let m = indexmap::IndexMap::new();
set_assoc("", m);
}
#[test]
fn set_pparams_empty_vec_no_panic() {
let _g = crate::test_util::global_state_lock();
set_pparams(vec![]);
}
#[test]
fn pparams_repeated_doesnt_grow_state() {
let _g = crate::test_util::global_state_lock();
let first_len = pparams().len();
for _ in 0..10 {
let n = pparams().len();
assert_eq!(n, first_len, "len must not grow across reads");
}
}
#[test]
fn unregister_function_unknown_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = unregister_function("__never_real_xyz__");
for _ in 0..3 {
assert_eq!(unregister_function("__never_real_xyz__"), first);
}
}
#[test]
fn array_unknown_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = array("__never_real_array_xyz__").is_none();
for _ in 0..3 {
assert_eq!(array("__never_real_array_xyz__").is_none(), first);
}
}
#[test]
fn assoc_unknown_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let first = assoc("__never_real_assoc_xyz__").is_none();
for _ in 0..3 {
assert_eq!(assoc("__never_real_assoc_xyz__").is_none(), first);
}
}
#[test]
fn array_returns_option_vec_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<Vec<String>> = array("any");
}
#[test]
fn assoc_returns_option_indexmap_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<indexmap::IndexMap<String, String>> = assoc("any");
}
#[test]
fn dispatch_function_call_returns_option_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<i32> = dispatch_function_call("__never__", &[]);
}
#[test]
fn run_function_body_returns_option_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<i32> = run_function_body("__never__", &[]);
}
#[test]
fn execute_script_returns_result_type() {
let _g = crate::test_util::global_state_lock();
let _: Result<i32, String> = execute_script("");
}
#[test]
fn execute_script_zsh_pipeline_returns_result_type() {
let _g = crate::test_util::global_state_lock();
let _: Result<i32, String> = execute_script_zsh_pipeline("");
}
#[test]
fn run_command_substitution_returns_string_type() {
let _g = crate::test_util::global_state_lock();
let _: String = run_command_substitution("");
}
#[test]
fn pparams_returns_vec_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Vec<String> = pparams();
}
#[test]
fn unregister_function_returns_bool_type() {
let _g = crate::test_util::global_state_lock();
let _: bool = unregister_function("__never__");
}
#[test]
fn unset_variants_nonexistent_no_panic() {
let _g = crate::test_util::global_state_lock();
unset_scalar("__never_unset_scalar__");
unset_array("__never_unset_array__");
unset_assoc("__never_unset_assoc__");
}
#[test]
fn set_pparams_without_hook_is_silent_noop() {
let _g = crate::test_util::global_state_lock();
set_pparams(vec!["a".into(), "b".into(), "c".into()]);
set_pparams(vec![]);
}
#[test]
fn array_empty_name_no_panic_deterministic() {
let _g = crate::test_util::global_state_lock();
let a = array("");
let b = array("");
assert_eq!(a, b, "array(\"\") must be deterministic");
}
#[test]
fn set_array_then_get_no_hook_safe() {
let _g = crate::test_util::global_state_lock();
set_array("__test_hook_arr__", vec!["a".into(), "b".into()]);
let _ = array("__test_hook_arr__");
}
#[test]
fn set_assoc_then_get_no_hook_safe() {
let _g = crate::test_util::global_state_lock();
let mut m = IndexMap::new();
m.insert("k".to_string(), "v".to_string());
set_assoc("__test_hook_assoc__", m);
let _ = assoc("__test_hook_assoc__");
}
#[test]
fn run_function_body_returns_option_i32_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: Option<i32> = run_function_body("foo", &[]);
}
#[test]
fn run_function_body_no_hook_deterministic() {
let _g = crate::test_util::global_state_lock();
let a = run_function_body("__never__", &[]);
let b = run_function_body("__never__", &[]);
assert_eq!(a, b);
}
#[test]
fn dispatch_function_call_no_hook_deterministic() {
let _g = crate::test_util::global_state_lock();
let a = dispatch_function_call("__never__", &[]);
let b = dispatch_function_call("__never__", &[]);
assert_eq!(a, b);
}
#[test]
fn pparams_returns_vec_string_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: Vec<String> = pparams();
}
#[test]
fn pparams_repeated_reads_are_observable_type() {
let _g = crate::test_util::global_state_lock();
let _a = pparams();
let _b = pparams();
}
#[test]
fn run_command_substitution_returns_string_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: String = run_command_substitution("echo x");
}
#[test]
fn run_command_substitution_empty_cmd_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = run_command_substitution("");
}
#[test]
fn execute_script_returns_result_i32_string_type() {
let _g = crate::test_util::global_state_lock();
let _: Result<i32, String> = execute_script("foo");
}
#[test]
fn unregister_function_empty_name_returns_bool() {
let _g = crate::test_util::global_state_lock();
let _: bool = unregister_function("");
}
}