diff --git a/Cargo.toml b/Cargo.toml index accf93a..5ac2dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["algorithms"] repository = "https://github.com/becheran/wildmatch" [dependencies] +bstr = { version = "1.10", optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } [dev-dependencies] @@ -22,6 +23,7 @@ regex-lite = {version = "0.1.5"} rand = {version = "0.8.5"} [features] +bytes = ["dep:bstr"] serde = ["dep:serde"] [[bench]] diff --git a/src/lib.rs b/src/lib.rs index dccc233..028b3ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,10 @@ //! assert!(WildMatchPattern::<'%', '_'>::new("%cat%").matches("dog_cat_dog")); //! ``` -use std::fmt; +use std::{fmt, str::Chars}; + +#[cfg(feature = "bytes")] +use bstr::ByteSlice; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -118,13 +121,25 @@ impl if self.pattern.is_empty() { return input.is_empty(); } - let mut input_chars = input.chars(); + self.matches_inner(input.chars()) + } + + #[cfg(feature = "bytes")] + /// Returns true if pattern applies to the given input bytes + pub fn matches_bytes(&self, input: &[u8]) -> bool { + if self.pattern.is_empty() { + return input.is_empty(); + } + self.matches_inner(input.chars()) + } + #[inline] + fn matches_inner(&self, mut input_chars: C) -> bool { let mut pattern_idx = 0; if let Some(mut input_char) = input_chars.next() { const NONE: usize = usize::MAX; let mut start_idx = NONE; - let mut matched = "".chars(); + let mut matched = C::new(); loop { if pattern_idx < self.pattern.len() && self.pattern[pattern_idx] == MULTI_WILDCARD { @@ -191,6 +206,45 @@ impl<'a, const MULTI_WILDCARD: char, const SINGLE_WILDCARD: char> PartialEq<&'a } } +trait CharsWrapper { + fn next(&mut self) -> Option; + + fn clone(&self) -> Self; + + fn new() -> Self + where + Self: Sized; +} + +impl CharsWrapper for Chars<'_> { + fn next(&mut self) -> Option { + std::iter::Iterator::next(self) + } + + fn clone(&self) -> Self { + std::clone::Clone::clone(self) + } + + fn new() -> Self { + "".chars() + } +} + +#[cfg(feature = "bytes")] +impl CharsWrapper for bstr::Chars<'_> { + fn next(&mut self) -> Option { + std::iter::Iterator::next(self) + } + + fn clone(&self) -> Self { + std::clone::Clone::clone(self) + } + + fn new() -> Self { + b"".chars() + } +} + #[cfg(test)] mod tests { use super::*; @@ -276,6 +330,14 @@ mod tests { assert!(m.matches(input)); } + #[cfg(feature = "bytes")] + #[test] + fn matches_bytes() { + let m = WildMatch::new("c?t"); + assert!(m.matches_bytes(b"c\x00t")); + assert!(m.matches_bytes(b"cat")); + } + #[test_case("*d*")] #[test_case("*d")] #[test_case("d*")]