175 lines
5.1 KiB
Rust
175 lines
5.1 KiB
Rust
//! Canonical dotted-segment glob matcher.
|
|
//!
|
|
//! This is the single implementation of bread's event-name glob semantics,
|
|
//! shared by real event dispatch (`breadd::core::subscriptions`) and the
|
|
//! IPC `--filter` path used by `bread events` (`breadd::ipc`). Previously
|
|
//! these lived as two independently hand-written copies that could drift
|
|
//! out of sync despite the docs claiming identical behavior; this module is
|
|
//! now the one place that logic is implemented and tested.
|
|
//!
|
|
//! Wildcard semantics (see `Documentation.md` / the API reference):
|
|
//! - `*` matches within a single dot-delimited segment (never crosses `.`).
|
|
//! - `**` matches zero or more segments, at any depth.
|
|
//! - `?` matches exactly one character, but never a `.`.
|
|
|
|
/// Returns true if `event_name` matches the dotted glob `pattern`.
|
|
pub fn matches_pattern(pattern: &str, event_name: &str) -> bool {
|
|
if let Some(prefix) = pattern.strip_suffix(".**") {
|
|
if event_name == prefix || event_name.starts_with(&format!("{prefix}.")) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
matches_glob(pattern.as_bytes(), event_name.as_bytes())
|
|
}
|
|
|
|
fn matches_glob(pattern: &[u8], text: &[u8]) -> bool {
|
|
if pattern.is_empty() {
|
|
return text.is_empty();
|
|
}
|
|
|
|
if pattern.len() >= 2 && pattern[0] == b'*' && pattern[1] == b'*' {
|
|
let mut idx = 2;
|
|
while pattern.len() >= idx + 2 && pattern[idx] == b'*' && pattern[idx + 1] == b'*' {
|
|
idx += 2;
|
|
}
|
|
let rest = &pattern[idx..];
|
|
if rest.is_empty() {
|
|
return true;
|
|
}
|
|
for offset in 0..=text.len() {
|
|
if matches_glob(rest, &text[offset..]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
match pattern[0] {
|
|
b'*' => {
|
|
let mut offset = 0;
|
|
loop {
|
|
if matches_glob(&pattern[1..], &text[offset..]) {
|
|
return true;
|
|
}
|
|
if offset == text.len() || text[offset] == b'.' {
|
|
break;
|
|
}
|
|
offset += 1;
|
|
}
|
|
false
|
|
}
|
|
b'?' => {
|
|
if text.is_empty() || text[0] == b'.' {
|
|
return false;
|
|
}
|
|
matches_glob(&pattern[1..], &text[1..])
|
|
}
|
|
ch => {
|
|
if text.first().copied() != Some(ch) {
|
|
return false;
|
|
}
|
|
matches_glob(&pattern[1..], &text[1..])
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn exact_match() {
|
|
assert!(matches_pattern(
|
|
"bread.device.dock.connected",
|
|
"bread.device.dock.connected"
|
|
));
|
|
assert!(!matches_pattern(
|
|
"bread.device.dock.connected",
|
|
"bread.device.dock.disconnected"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn single_segment_wildcard() {
|
|
assert!(matches_pattern("bread.device.*", "bread.device.foo"));
|
|
assert!(!matches_pattern(
|
|
"bread.device.*",
|
|
"bread.device.dock.connected"
|
|
));
|
|
assert!(!matches_pattern("bread.device.*", "bread.device"));
|
|
}
|
|
|
|
#[test]
|
|
fn recursive_wildcard() {
|
|
assert!(matches_pattern(
|
|
"bread.device.**",
|
|
"bread.device.dock.connected"
|
|
));
|
|
assert!(matches_pattern("bread.**", "bread.device.dock.connected"));
|
|
assert!(matches_pattern("bread.**", "bread"));
|
|
}
|
|
|
|
#[test]
|
|
fn single_char_wildcard() {
|
|
assert!(matches_pattern("bread.monitor.?", "bread.monitor.1"));
|
|
assert!(!matches_pattern("bread.monitor.?", "bread.monitor.10"));
|
|
assert!(!matches_pattern("bread.monitor.?", "bread.monitor."));
|
|
}
|
|
|
|
#[test]
|
|
fn star_does_not_cross_dot_segments() {
|
|
assert!(matches_pattern(
|
|
"bread.*.connected",
|
|
"bread.device.connected"
|
|
));
|
|
assert!(!matches_pattern(
|
|
"bread.*.connected",
|
|
"bread.device.dock.connected"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn double_star_matches_zero_or_more_segments() {
|
|
assert!(matches_pattern("bread.**", "bread.a"));
|
|
assert!(matches_pattern("bread.**", "bread.a.b.c.d"));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_pattern_matches_only_empty_text() {
|
|
assert!(matches_pattern("", ""));
|
|
assert!(!matches_pattern("", "bread"));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_text_only_matches_wildcards() {
|
|
assert!(matches_pattern("**", ""));
|
|
assert!(!matches_pattern("bread.*", ""));
|
|
}
|
|
|
|
#[test]
|
|
fn dot_double_star_matches_exact_prefix_with_zero_segments() {
|
|
assert!(matches_pattern("bread.device.**", "bread.device"));
|
|
}
|
|
|
|
#[test]
|
|
fn dot_double_star_does_not_match_sibling_prefix() {
|
|
assert!(!matches_pattern("bread.device.**", "bread.devicex"));
|
|
assert!(!matches_pattern(
|
|
"bread.device.**",
|
|
"bread.network.connected"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn mid_pattern_star_does_not_cross_dots() {
|
|
assert!(matches_pattern(
|
|
"bread.*.connected",
|
|
"bread.alpha.connected"
|
|
));
|
|
assert!(!matches_pattern(
|
|
"bread.*.connected",
|
|
"bread.alpha.beta.connected"
|
|
));
|
|
}
|
|
}
|