Skip to content

Commit

Permalink
Merge pull request #33 from rneswold/pr-issue7
Browse files Browse the repository at this point in the history
add globbing to simple backend
  • Loading branch information
rneswold authored Nov 30, 2022
2 parents 60fb499 + fafea49 commit 5de7efb
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 1 deletion.
206 changes: 206 additions & 0 deletions backends/drmem-db-simple/src/glob.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use std::{iter::Fuse, str::Chars};

// Defines a finite-state machine that traverses the pattern and tries
// to match a name.

struct Fsm<'a> {
stack: Vec<(Fuse<Chars<'a>>, Fuse<Chars<'a>>)>,

it_pat: Fuse<Chars<'a>>,
it_name: Fuse<Chars<'a>>,

ch_pat: Option<char>,
ch_name: Option<char>,
}

impl Fsm<'_> {
pub fn new<'a>(pat: &'a str, name: &'a str) -> Fsm<'a> {
Fsm {
stack: vec![],
it_pat: pat.chars().fuse(),
it_name: name.chars().fuse(),
ch_pat: None,
ch_name: None,
}
}

fn next_pat_char(&mut self) -> bool {
self.ch_pat = self.it_pat.next();
self.ch_pat.is_some()
}

fn next_name_char(&mut self) {
self.ch_name = self.it_name.next();
}

fn save(&mut self) {
self.stack.push((self.it_pat.clone(), self.it_name.clone()))
}

fn backtrace(&mut self) -> Option<bool> {
if let Some((i_p, i_n)) = self.stack.pop() {
self.it_pat = i_p;
self.it_name = i_n;
self.next_pat_char();
self.next_name_char();
None
} else {
Some(false)
}
}

fn step(&mut self) -> Option<bool> {
match (self.ch_pat, self.ch_name) {
(None, None) | (Some('*'), None) => Some(true),

(None, _) | (_, None) => self.backtrace(),

(Some('?'), Some(_)) => {
self.next_pat_char();
self.next_name_char();
None
}

(Some('*'), Some(_)) => {
// This loop advances the pattern iterator to the
// next literal character. If a '?' character is
// found, it also advances the name iterator.

while self.ch_pat == Some('?') || self.ch_pat == Some('*') {
// If there are no remaining pattern characters,
// then all trailing characters were wildcards and
// the pattern matches.

if !self.next_pat_char() {
return Some(true);
}

// If the next character is a '?', consume, if
// possible, the next name character. Otherwise
// look for another solution.

if let Some('?') = self.ch_pat {
if self.ch_name.is_none() {
return self.backtrace();
}
self.next_name_char();
}
}

while self.ch_name.is_some() {
if self.ch_name == self.ch_pat {
self.save()
}
self.next_name_char()
}

self.backtrace()
}

(Some(a), Some(b)) => {
if a != b {
self.backtrace()
} else {
self.next_pat_char();
self.next_name_char();
None
}
}
}
}

pub fn process(&mut self) -> bool {
self.next_pat_char();
self.next_name_char();

loop {
if let Some(result) = self.step() {
return result;
}
}
}
}

pub struct Pattern {
data: String,
}

impl Pattern {
pub fn create(pat: &str) -> Pattern {
Pattern {
data: String::from(pat),
}
}

pub fn matches(&self, name: &str) -> bool {
let mut fsm = Fsm::new(&self.data, name);

fsm.process()
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_match() {
// Empty input.

assert_eq!(Pattern::create("").matches(""), true);

// Degenerate cases.

assert_eq!(Pattern::create("a").matches(""), false);
assert_eq!(Pattern::create("").matches("a"), false);

// Literal macthing.

assert_eq!(Pattern::create("a").matches("a"), true);
assert_eq!(Pattern::create("a").matches("b"), false);
assert_eq!(Pattern::create("ab").matches("ab"), true);
assert_eq!(Pattern::create("abc").matches("abc"), true);

// Wildcard matching.

assert_eq!(Pattern::create("?").matches("a"), true);
assert_eq!(Pattern::create("?").matches("z"), true);
assert_eq!(Pattern::create("a?").matches("ab"), true);
assert_eq!(Pattern::create("a?").matches("a"), false);
assert_eq!(Pattern::create("?bc").matches("abc"), true);
assert_eq!(Pattern::create("a?c").matches("abc"), true);
assert_eq!(Pattern::create("ab?").matches("abc"), true);
assert_eq!(Pattern::create("??c").matches("abc"), true);
assert_eq!(Pattern::create("?b?").matches("abc"), true);
assert_eq!(Pattern::create("a??").matches("abc"), true);
assert_eq!(Pattern::create("???").matches("abc"), true);
assert_eq!(Pattern::create("a?c?e").matches("abcde"), true);

// Multi-wildcard matching.

assert_eq!(Pattern::create("*").matches(""), true);
assert_eq!(Pattern::create("a*").matches("a"), true);
assert_eq!(Pattern::create("a*").matches("aa"), true);
assert_eq!(Pattern::create("a*b").matches("ab"), true);
assert_eq!(Pattern::create("a*b").matches("azb"), true);
assert_eq!(Pattern::create("a*b").matches("azzzzzzzzb"), true);
assert_eq!(Pattern::create("a*bc").matches("azbc"), true);

// Interspersed wildcards.

assert_eq!(Pattern::create("a***bc").matches("azbc"), true);
assert_eq!(Pattern::create("a?*cd").matches("azbcd"), true);
assert_eq!(Pattern::create("a*?cd").matches("azbcd"), true);
assert_eq!(Pattern::create("a*?*cd").matches("azbcd"), true);
assert_eq!(Pattern::create("a*c*e").matches("abcde"), true);
assert_eq!(Pattern::create("a*?de").matches("abcde"), true);

// Devious names requiring backtracking.

assert_eq!(Pattern::create("a*bc").matches("azbcbc"), true);
assert_eq!(Pattern::create("a*bc").matches("azbdbc"), true);
assert_eq!(Pattern::create("a*bc*").matches("azbcbc"), true);
assert_eq!(Pattern::create("a*bc*").matches("azbcbd"), true);
assert_eq!(Pattern::create("a*bc").matches("azbcd"), false);
}
}
6 changes: 5 additions & 1 deletion backends/drmem-db-simple/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type ReadingState = (
time::SystemTime,
);

mod glob;

struct DeviceInfo {
owner: String,
units: Option<String>,
Expand Down Expand Up @@ -260,7 +262,9 @@ impl Store for SimpleStore {
if let Ok(pattern) = pattern.parse::<device::Name>() {
Box::new(move |(k, _)| pattern == **k)
} else {
Box::new(|_| false)
Box::new(move |(k, _)| {
glob::Pattern::create(pattern).matches(&k.to_string())
})
}
} else {
Box::new(|_| true)
Expand Down

0 comments on commit 5de7efb

Please sign in to comment.