use std::collections::BTreeSet;
use crate::McpSurfaceCard;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct McpProfile {
allow_names: BTreeSet<String>,
deny_names: BTreeSet<String>,
}
impl McpProfile {
pub fn all() -> Self {
Self::default()
}
pub fn allow_names<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
allow_names: names.into_iter().map(Into::into).collect(),
deny_names: BTreeSet::new(),
}
}
pub fn deny_names<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
allow_names: BTreeSet::new(),
deny_names: names.into_iter().map(Into::into).collect(),
}
}
pub fn with_allowed_name(mut self, name: impl Into<String>) -> Self {
self.allow_names.insert(name.into());
self
}
pub fn with_denied_name(mut self, name: impl Into<String>) -> Self {
self.deny_names.insert(name.into());
self
}
pub fn allows_name(&self, name: &str) -> bool {
if matches_any(&self.deny_names, name) {
return false;
}
self.allow_names.is_empty() || matches_any(&self.allow_names, name)
}
pub fn allows(&self, row: &McpSurfaceCard) -> bool {
self.allows_name(&row.name)
}
}
fn matches_any(patterns: &BTreeSet<String>, name: &str) -> bool {
patterns.iter().any(|pattern| glob_matches(pattern, name))
}
fn glob_matches(pattern: &str, name: &str) -> bool {
if pattern == "*" || pattern == name {
return true;
}
if !pattern.contains('*') {
return false;
}
let mut rest = name;
let anchored_start = !pattern.starts_with('*');
let anchored_end = !pattern.ends_with('*');
let parts = pattern.split('*').filter(|part| !part.is_empty());
let mut first = true;
for part in parts {
if first && anchored_start {
let Some(tail) = rest.strip_prefix(part) else {
return false;
};
rest = tail;
} else {
let Some(index) = rest.find(part) else {
return false;
};
rest = &rest[index + part.len()..];
}
first = false;
}
!anchored_end || rest.is_empty()
}