use std::collections::BTreeMap;
use std::sync::{OnceLock, RwLock};
pub type NativeCmd = Box<dyn Fn(&[String]) -> i32 + Send + Sync>;
fn table() -> &'static RwLock<BTreeMap<String, NativeCmd>> {
static TABLE: OnceLock<RwLock<BTreeMap<String, NativeCmd>>> = OnceLock::new();
TABLE.get_or_init(|| RwLock::new(BTreeMap::new()))
}
pub fn register<F>(name: &str, f: F)
where
F: Fn(&[String]) -> i32 + Send + Sync + 'static,
{
if let Ok(mut t) = table().write() {
t.insert(name.to_string(), Box::new(f));
}
}
pub fn is_registered(name: &str) -> bool {
table()
.read()
.map(|t| t.contains_key(name))
.unwrap_or(false)
}
pub fn is_enabled(name: &str) -> bool {
is_registered(name)
&& !crate::ported::builtin::BUILTINS_DISABLED
.lock()
.map(|s| s.contains(name))
.unwrap_or(false)
}
pub fn names() -> Vec<String> {
table()
.read()
.map(|t| t.keys().cloned().collect())
.unwrap_or_default()
}
pub fn dispatch(name: &str, argv: &[String]) -> Option<i32> {
let t = table().read().ok()?;
let f = t.get(name)?;
Some(f(argv))
}
#[cfg(test)]
pub(crate) fn unregister(name: &str) {
if let Ok(mut t) = table().write() {
t.remove(name);
}
}
thread_local! {
static FORCED_EXTERNAL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub struct ForcedExternalGuard(bool);
impl Drop for ForcedExternalGuard {
fn drop(&mut self) {
FORCED_EXTERNAL.with(|f| f.set(self.0));
}
}
#[must_use]
pub fn force_external() -> ForcedExternalGuard {
ForcedExternalGuard(FORCED_EXTERNAL.with(|f| f.replace(true)))
}
pub fn is_forced_external() -> bool {
FORCED_EXTERNAL.with(std::cell::Cell::get)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn registered_name_dispatches_with_full_argv() {
static SEEN: AtomicUsize = AtomicUsize::new(0);
register("zshrs_test_native_dispatch", |argv| {
SEEN.store(argv.len(), Ordering::SeqCst);
assert_eq!(argv[0], "zshrs_test_native_dispatch");
7
});
assert!(is_registered("zshrs_test_native_dispatch"));
assert!(!is_registered("zshrs_test_native_never_registered"));
let argv = [
"zshrs_test_native_dispatch".to_string(),
"--flag".to_string(),
];
assert_eq!(dispatch("zshrs_test_native_dispatch", &argv), Some(7));
assert_eq!(SEEN.load(Ordering::SeqCst), 2);
assert_eq!(dispatch("zshrs_test_native_never_registered", &argv), None);
unregister("zshrs_test_native_dispatch");
assert!(!is_registered("zshrs_test_native_dispatch"));
}
#[test]
fn disable_masks_dispatch_and_enable_restores() {
register("zshrs_test_native_disable", |_| 0);
assert!(is_enabled("zshrs_test_native_disable"));
crate::ported::builtin::BUILTINS_DISABLED
.lock()
.unwrap()
.insert("zshrs_test_native_disable".to_string());
assert!(is_registered("zshrs_test_native_disable"));
assert!(!is_enabled("zshrs_test_native_disable"));
crate::ported::builtin::BUILTINS_DISABLED
.lock()
.unwrap()
.remove("zshrs_test_native_disable");
assert!(is_enabled("zshrs_test_native_disable"));
unregister("zshrs_test_native_disable");
}
#[test]
fn forced_external_guard_restores_previous_state() {
assert!(!is_forced_external());
{
let _outer = force_external();
assert!(is_forced_external());
{
let _inner = force_external();
assert!(is_forced_external());
}
assert!(is_forced_external());
}
assert!(!is_forced_external());
}
}