use std::{
collections::HashMap,
sync::{Arc, LazyLock, Mutex},
};
use crate::selector::{CmdMatcher, FlagMatcher};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MatcherKind {
AllowCmds,
ExcludeCmds,
AllowCmdsContaining,
ExcludeCmdsContaining,
AllowFlags,
ExcludeFlags,
NoFlags,
}
#[derive(Debug, Clone)]
pub(crate) struct MatcherSpec {
pub kind: MatcherKind,
pub args: Vec<String>,
}
type LivenessCheck = Box<dyn Fn() -> bool + Send + Sync>;
static MATCHER_REGISTRY: LazyLock<Mutex<HashMap<usize, (LivenessCheck, MatcherSpec)>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
fn register<T: ?Sized + Send + Sync + 'static>(arc: &Arc<T>, spec: MatcherSpec) {
let key = Arc::as_ptr(arc).cast::<()>() as usize;
let weak = Arc::downgrade(arc);
let alive: LivenessCheck = Box::new(move || weak.strong_count() > 0);
MATCHER_REGISTRY
.lock()
.expect("MATCHER_REGISTRY mutex poisoned")
.insert(key, (alive, spec));
}
pub(crate) fn lookup<T: ?Sized>(arc: &Arc<T>) -> Option<MatcherSpec> {
let key = Arc::as_ptr(arc).cast::<()>() as usize;
let mut registry = MATCHER_REGISTRY
.lock()
.expect("MATCHER_REGISTRY mutex poisoned");
if let Some((alive, spec)) = registry.get(&key) {
if alive() {
return Some(spec.clone());
}
registry.remove(&key);
}
None
}
#[must_use]
pub fn allow_cmds<I, S>(paths: I) -> CmdMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = paths.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: CmdMatcher = Arc::new(move |path: &str| owned.iter().any(|p| p == path));
register(
&arc,
MatcherSpec {
kind: MatcherKind::AllowCmds,
args,
},
);
arc
}
#[must_use]
pub fn exclude_cmds<I, S>(paths: I) -> CmdMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = paths.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: CmdMatcher = Arc::new(move |path: &str| !owned.iter().any(|p| p == path));
register(
&arc,
MatcherSpec {
kind: MatcherKind::ExcludeCmds,
args,
},
);
arc
}
#[must_use]
pub fn allow_cmds_containing<I, S>(needles: I) -> CmdMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = needles.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: CmdMatcher =
Arc::new(move |path: &str| owned.iter().any(|n| path.contains(n.as_str())));
register(
&arc,
MatcherSpec {
kind: MatcherKind::AllowCmdsContaining,
args,
},
);
arc
}
#[must_use]
pub fn exclude_cmds_containing<I, S>(needles: I) -> CmdMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = needles.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: CmdMatcher =
Arc::new(move |path: &str| !owned.iter().any(|n| path.contains(n.as_str())));
register(
&arc,
MatcherSpec {
kind: MatcherKind::ExcludeCmdsContaining,
args,
},
);
arc
}
#[must_use]
pub fn allow_flags<I, S>(names: I) -> FlagMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = names.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: FlagMatcher =
Arc::new(move |arg: &clap::Arg| owned.iter().any(|n| n == arg.get_id().as_str()));
register(
&arc,
MatcherSpec {
kind: MatcherKind::AllowFlags,
args,
},
);
arc
}
#[must_use]
pub fn exclude_flags<I, S>(names: I) -> FlagMatcher
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let args: Vec<String> = names.into_iter().map(Into::into).collect();
let owned = args.clone();
let arc: FlagMatcher =
Arc::new(move |arg: &clap::Arg| !owned.iter().any(|n| n == arg.get_id().as_str()));
register(
&arc,
MatcherSpec {
kind: MatcherKind::ExcludeFlags,
args,
},
);
arc
}
#[must_use]
pub fn no_flags() -> FlagMatcher {
let arc: FlagMatcher = Arc::new(|_arg: &clap::Arg| false);
register(
&arc,
MatcherSpec {
kind: MatcherKind::NoFlags,
args: Vec::new(),
},
);
arc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allow_cmds_registers_spec() {
let m = allow_cmds(["a", "b"]);
let spec = lookup(&m).expect("allow_cmds must register a spec");
assert_eq!(spec.kind, MatcherKind::AllowCmds);
assert_eq!(spec.args, vec!["a", "b"]);
}
#[test]
fn exclude_cmds_registers_spec() {
let m = exclude_cmds(["x", "y", "z"]);
let spec = lookup(&m).expect("exclude_cmds must register a spec");
assert_eq!(spec.kind, MatcherKind::ExcludeCmds);
assert_eq!(spec.args, vec!["x", "y", "z"]);
}
#[test]
fn allow_cmds_containing_registers_spec() {
let m = allow_cmds_containing(["get", "list"]);
let spec = lookup(&m).expect("allow_cmds_containing must register a spec");
assert_eq!(spec.kind, MatcherKind::AllowCmdsContaining);
assert_eq!(spec.args, vec!["get", "list"]);
}
#[test]
fn exclude_cmds_containing_registers_spec() {
let m = exclude_cmds_containing(["delete"]);
let spec = lookup(&m).expect("exclude_cmds_containing must register a spec");
assert_eq!(spec.kind, MatcherKind::ExcludeCmdsContaining);
assert_eq!(spec.args, vec!["delete"]);
}
#[test]
fn allow_flags_registers_spec() {
let m = allow_flags(["namespace", "output"]);
let spec = lookup(&m).expect("allow_flags must register a spec");
assert_eq!(spec.kind, MatcherKind::AllowFlags);
assert_eq!(spec.args, vec!["namespace", "output"]);
}
#[test]
fn exclude_flags_registers_spec() {
let m = exclude_flags(["token", "password"]);
let spec = lookup(&m).expect("exclude_flags must register a spec");
assert_eq!(spec.kind, MatcherKind::ExcludeFlags);
assert_eq!(spec.args, vec!["token", "password"]);
}
#[test]
fn no_flags_registers_spec() {
let m = no_flags();
let spec = lookup(&m).expect("no_flags must register a spec");
assert_eq!(spec.kind, MatcherKind::NoFlags);
assert!(spec.args.is_empty(), "no_flags args must be empty");
}
#[test]
fn arc_clone_shares_registry_entry() {
let m = allow_cmds(["shared"]);
let m2 = Arc::clone(&m);
let spec1 = lookup(&m).expect("original must have spec");
let spec2 = lookup(&m2).expect("clone must have spec");
assert_eq!(spec1.kind, spec2.kind);
assert_eq!(spec1.args, spec2.args);
}
#[test]
fn hand_written_closure_returns_none() {
let m: CmdMatcher = Arc::new(|_path: &str| true);
assert!(
lookup(&m).is_none(),
"hand-written closures must not appear in the registry"
);
}
#[test]
fn dropped_factory_arc_does_not_alias_hand_written_closure() {
for _ in 0..1024 {
let _ = allow_cmds(["transient"]);
}
let hand_written: CmdMatcher = Arc::new(|_path: &str| true);
assert!(
lookup(&hand_written).is_none(),
"hand-written closure must not inherit a stale factory spec \
via pointer reuse",
);
}
#[test]
fn live_arc_survives_intermediate_factory_churn() {
let m = allow_cmds(["sticky"]);
for _ in 0..256 {
let _ = exclude_flags(["churn"]);
}
let spec = lookup(&m).expect("live Arc must still resolve");
assert_eq!(spec.kind, MatcherKind::AllowCmds);
assert_eq!(spec.args, vec!["sticky"]);
}
}