use {
alloc::{
borrow::Cow,
collections::BTreeMap,
string::String,
vec::Vec,
},
core::{
fmt::{self, Display, Formatter},
str::FromStr,
},
};
use crate::Error;
const ANY: char = '*';
const ONE_CHAR: char = '?';
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
enum Part<'a> {
Str(Cow<'a, str>),
Any,
OneChar,
}
impl<'a> Part<'a> {
fn parse<'b, F>(mut s: &'b str, str_fn: F) -> Vec<Self> where F: Fn(&'b str) -> Cow<'a, str> {
let mut result = alloc::vec![];
loop {
match s.find(|c| match c { self::ANY | self::ONE_CHAR => true, _ => false }) {
Some(i) => {
if i > 0 {
result.push(Part::Str(str_fn(&s[..i])));
}
match s.as_bytes()[i] as char {
self::ANY => if result.last() != Some(&Part::Any) {
result.push(Part::Any);
},
self::ONE_CHAR => result.push(Part::OneChar),
_ => {},
};
if i + 1 == s.len() {
break;
}
s = &s[i + 1..];
},
None => {
if s.is_empty() == false {
result.push(Part::Str(str_fn(s)));
}
break;
},
};
}
result
}
}
impl<'a> Part<'a> {
fn parse_str(s: &'a str) -> Vec<Self> {
Part::parse(s, |s| Cow::from(s))
}
fn parse_string(s: String) -> Vec<Self> {
Part::parse(&s, |s| Cow::from(String::from(s)))
}
}
#[derive(Debug)]
struct SubStr {
fixed: bool,
idx: usize,
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Glob<'a> {
parts: Vec<Part<'a>>,
}
impl<'a> Glob<'a> {
const fn new(parts: Vec<Part<'a>>) -> Self {
Self {
parts,
}
}
pub fn matches<S>(&self, s: S) -> bool where S: AsRef<str> {
let s = s.as_ref();
if self.parts.is_empty() {
return s.is_empty();
}
let mut map = match self.make_map(s) {
Some(map) => map,
None => return false,
};
loop {
let mut char_count: usize = 0;
for (part_idx, part) in self.parts.iter().enumerate() {
match part {
Part::Any => {},
Part::OneChar => char_count += 1,
Part::Str(sub) => match map.get_mut(&part_idx) {
Some(SubStr { fixed, idx: sub_idx }) => match char_count == 0 || s.chars().take(char_count).count() >= char_count {
true => if *fixed && part_idx + 1 == self.parts.len() && sub.len() + *sub_idx != s.len() {
return false;
},
false => match fixed {
true => return false,
false => match s[*sub_idx..].chars().next() {
Some(c) => match s[*sub_idx + c.len_utf8()..].find(sub.as_ref()) {
Some(i) => {
*sub_idx = i;
break;
},
None => return false,
},
None => return false,
},
},
},
None => return false,
},
};
if part_idx + 1 == self.parts.len() {
return true;
}
}
}
}
fn make_map(&self, s: &str) -> Option<BTreeMap<usize, SubStr>> {
let mut result = BTreeMap::new();
let mut dynamic = false;
let mut idx = 0;
for (part_idx, part) in self.parts.iter().enumerate() {
match part {
Part::Str(sub) => {
let fixed;
let sub_idx;
if part_idx == 0 {
match s.starts_with(sub.as_ref()) {
true => {
fixed = true;
sub_idx = 0;
},
false => return None,
};
} else if part_idx + 1 == self.parts.len() {
match s.ends_with(sub.as_ref()) {
true => {
fixed = true;
sub_idx = s.len() - sub.len();
},
false => return None,
};
} else {
fixed = dynamic == false;
sub_idx = match s[idx..].find(sub.as_ref()) {
Some(i) => {
idx = i + sub.len();
i
},
None => return None,
};
}
result.insert(part_idx, SubStr { fixed, idx: sub_idx });
},
Part::Any => dynamic = true,
Part::OneChar => match s[idx..].chars().next() {
Some(c) => idx += c.len_utf8(),
None => return None,
},
};
}
Some(result)
}
}
impl<'a> From<&'a str> for Glob<'a> {
fn from(src: &'a str) -> Self {
Self::new(Part::parse_str(src))
}
}
impl<'a> From<&'a String> for Glob<'a> {
fn from(src: &'a String) -> Self {
Self::from(src.as_str())
}
}
impl From<String> for Glob<'_> {
fn from(src: String) -> Self {
Self::new(Part::parse_string(src))
}
}
impl<'a> From<Cow<'a, str>> for Glob<'a> {
fn from(s: Cow<'a, str>) -> Self {
match s {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl FromStr for Glob<'_> {
type Err = Error;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
Ok(Self::from(String::from(s)))
}
}
impl Display for Glob<'_> {
fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
use fmt::Write;
for p in self.parts.iter() {
match p {
Part::Str(s) => f.write_str(s)?,
Part::Any => f.write_char(ANY)?,
Part::OneChar => f.write_char(ONE_CHAR)?,
};
}
Ok(())
}
}