1use std::borrow::Borrow;
18use std::borrow::Cow;
19use std::collections::BTreeMap;
20use std::fmt;
21use std::fmt::Debug;
22use std::ops::Deref;
23
24use either::Either;
25use thiserror::Error;
26
27#[derive(Debug, Error)]
29pub enum StringPatternParseError {
30 #[error("Invalid string pattern kind `{0}:`")]
32 InvalidKind(String),
33 #[error(transparent)]
35 GlobPattern(glob::PatternError),
36 #[error(transparent)]
38 Regex(regex::Error),
39}
40
41#[derive(Clone)]
43pub struct GlobPattern(pub glob::Pattern);
44
45impl GlobPattern {
46 fn as_str(&self) -> &str {
47 self.0.as_str()
48 }
49}
50
51impl Debug for GlobPattern {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_tuple("GlobPattern").field(&self.as_str()).finish()
54 }
55}
56
57fn parse_glob(src: &str) -> Result<GlobPattern, StringPatternParseError> {
58 glob::Pattern::new(src)
59 .map(GlobPattern)
60 .map_err(StringPatternParseError::GlobPattern)
61}
62
63#[derive(Clone, Debug)]
66pub enum StringPattern {
67 Exact(String),
69 ExactI(String),
71 Substring(String),
73 SubstringI(String),
75 Glob(GlobPattern),
77 GlobI(GlobPattern),
79 Regex(regex::Regex),
81 }
83
84impl StringPattern {
85 pub const fn everything() -> Self {
87 StringPattern::Substring(String::new())
88 }
89
90 pub fn parse(src: &str) -> Result<StringPattern, StringPatternParseError> {
97 if let Some((kind, pat)) = src.split_once(':') {
98 StringPattern::from_str_kind(pat, kind)
99 } else {
100 Ok(StringPattern::exact(src))
101 }
102 }
103
104 pub fn exact(src: impl Into<String>) -> Self {
106 StringPattern::Exact(src.into())
107 }
108
109 pub fn exact_i(src: impl Into<String>) -> Self {
111 StringPattern::ExactI(src.into())
112 }
113
114 pub fn substring(src: impl Into<String>) -> Self {
116 StringPattern::Substring(src.into())
117 }
118
119 pub fn substring_i(src: impl Into<String>) -> Self {
121 StringPattern::SubstringI(src.into())
122 }
123
124 pub fn glob(src: &str) -> Result<Self, StringPatternParseError> {
126 Ok(StringPattern::Glob(parse_glob(src)?))
130 }
131
132 pub fn glob_i(src: &str) -> Result<Self, StringPatternParseError> {
134 Ok(StringPattern::GlobI(parse_glob(src)?))
135 }
136
137 pub fn regex(src: &str) -> Result<Self, StringPatternParseError> {
139 let pattern = regex::Regex::new(src).map_err(StringPatternParseError::Regex)?;
140 Ok(StringPattern::Regex(pattern))
141 }
142
143 pub fn from_str_kind(src: &str, kind: &str) -> Result<Self, StringPatternParseError> {
145 match kind {
146 "exact" => Ok(StringPattern::exact(src)),
147 "exact-i" => Ok(StringPattern::exact_i(src)),
148 "substring" => Ok(StringPattern::substring(src)),
149 "substring-i" => Ok(StringPattern::substring_i(src)),
150 "glob" => StringPattern::glob(src),
151 "glob-i" => StringPattern::glob_i(src),
152 "regex" => StringPattern::regex(src),
153 _ => Err(StringPatternParseError::InvalidKind(kind.to_owned())),
154 }
155 }
156
157 pub fn is_exact(&self) -> bool {
159 self.as_exact().is_some()
160 }
161
162 pub fn as_exact(&self) -> Option<&str> {
166 match self {
170 StringPattern::Exact(literal) => Some(literal),
171 _ => None,
172 }
173 }
174
175 pub fn as_str(&self) -> &str {
177 match self {
178 StringPattern::Exact(literal) => literal,
179 StringPattern::ExactI(literal) => literal,
180 StringPattern::Substring(needle) => needle,
181 StringPattern::SubstringI(needle) => needle,
182 StringPattern::Glob(pattern) => pattern.as_str(),
183 StringPattern::GlobI(pattern) => pattern.as_str(),
184 StringPattern::Regex(pattern) => pattern.as_str(),
185 }
186 }
187
188 pub fn to_glob(&self) -> Option<Cow<'_, str>> {
191 match self {
195 StringPattern::Exact(literal) => Some(glob::Pattern::escape(literal).into()),
196 StringPattern::Substring(needle) => {
197 if needle.is_empty() {
198 Some("*".into())
199 } else {
200 Some(format!("*{}*", glob::Pattern::escape(needle)).into())
201 }
202 }
203 StringPattern::Glob(pattern) => Some(pattern.as_str().into()),
204 StringPattern::ExactI(_) => None,
205 StringPattern::SubstringI(_) => None,
206 StringPattern::GlobI(_) => None,
207 StringPattern::Regex(_) => None,
208 }
209 }
210
211 pub fn matches(&self, haystack: &str) -> bool {
216 match self {
232 StringPattern::Exact(literal) => haystack == literal,
233 StringPattern::ExactI(literal) => haystack.eq_ignore_ascii_case(literal),
234 StringPattern::Substring(needle) => haystack.contains(needle),
235 StringPattern::SubstringI(needle) => haystack
236 .to_ascii_lowercase()
237 .contains(&needle.to_ascii_lowercase()),
238 StringPattern::Glob(pattern) => pattern.0.matches(haystack),
239 StringPattern::GlobI(pattern) => pattern.0.matches_with(
240 haystack,
241 glob::MatchOptions {
242 case_sensitive: false,
243 ..glob::MatchOptions::new()
244 },
245 ),
246 StringPattern::Regex(pattern) => pattern.is_match(haystack),
247 }
248 }
249
250 pub fn filter_btree_map<'a, 'b, K: Borrow<str> + Ord, V>(
253 &'b self,
254 map: &'a BTreeMap<K, V>,
255 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, V> {
256 self.filter_btree_map_with(map, |key| key, |key| key)
257 }
258
259 pub fn filter_btree_map_as_deref<'a, 'b, K, V>(
265 &'b self,
266 map: &'a BTreeMap<K, V>,
267 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, V>
268 where
269 K: Borrow<K::Target> + Deref + Ord,
270 K::Target: AsRef<str> + Ord,
271 str: AsRef<K::Target>,
272 {
273 self.filter_btree_map_with(map, AsRef::as_ref, AsRef::as_ref)
274 }
275
276 fn filter_btree_map_with<'a, 'b, K, Q, V, FromKey, ToKey>(
277 &'b self,
278 map: &'a BTreeMap<K, V>,
279 from_key: FromKey,
280 to_key: ToKey,
281 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, Q, V, FromKey, ToKey>
285 where
286 K: Borrow<Q> + Ord,
287 Q: Ord + ?Sized,
288 FromKey: Fn(&Q) -> &str,
289 ToKey: Fn(&str) -> &Q,
290 {
291 if let Some(key) = self.as_exact() {
292 Either::Left(map.get_key_value(to_key(key)).into_iter())
293 } else {
294 Either::Right(
295 map.iter()
296 .filter(move |&(key, _)| self.matches(from_key(key.borrow()))),
297 )
298 }
299 }
300}
301
302impl fmt::Display for StringPattern {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 write!(f, "{}", self.as_str())
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use assert_matches::assert_matches;
312
313 use super::*;
314
315 #[test]
316 fn test_string_pattern_to_glob() {
317 assert_eq!(StringPattern::everything().to_glob(), Some("*".into()));
318 assert_eq!(StringPattern::exact("a").to_glob(), Some("a".into()));
319 assert_eq!(StringPattern::exact("*").to_glob(), Some("[*]".into()));
320 assert_eq!(
321 StringPattern::glob("*").unwrap().to_glob(),
322 Some("*".into())
323 );
324 assert_eq!(
325 StringPattern::Substring("a".into()).to_glob(),
326 Some("*a*".into())
327 );
328 assert_eq!(
329 StringPattern::Substring("*".into()).to_glob(),
330 Some("*[*]*".into())
331 );
332 }
333
334 #[test]
335 fn test_parse() {
336 assert_matches!(
338 StringPattern::parse("exact:foo"),
339 Ok(StringPattern::Exact(s)) if s == "foo"
340 );
341 assert_matches!(
342 StringPattern::from_str_kind("foo", "exact"),
343 Ok(StringPattern::Exact(s)) if s == "foo"
344 );
345 assert_matches!(
346 StringPattern::parse("glob:foo*"),
347 Ok(StringPattern::Glob(p)) if p.as_str() == "foo*"
348 );
349 assert_matches!(
350 StringPattern::from_str_kind("foo*", "glob"),
351 Ok(StringPattern::Glob(p)) if p.as_str() == "foo*"
352 );
353 assert_matches!(
354 StringPattern::parse("substring:foo"),
355 Ok(StringPattern::Substring(s)) if s == "foo"
356 );
357 assert_matches!(
358 StringPattern::from_str_kind("foo", "substring"),
359 Ok(StringPattern::Substring(s)) if s == "foo"
360 );
361 assert_matches!(
362 StringPattern::parse("substring-i:foo"),
363 Ok(StringPattern::SubstringI(s)) if s == "foo"
364 );
365 assert_matches!(
366 StringPattern::from_str_kind("foo", "substring-i"),
367 Ok(StringPattern::SubstringI(s)) if s == "foo"
368 );
369 assert_matches!(
370 StringPattern::parse("regex:foo"),
371 Ok(StringPattern::Regex(p)) if p.as_str() == "foo"
372 );
373 assert_matches!(
374 StringPattern::from_str_kind("foo", "regex"),
375 Ok(StringPattern::Regex(p)) if p.as_str() == "foo"
376 );
377
378 assert_matches!(
380 StringPattern::parse("exact:foo:bar"),
381 Ok(StringPattern::Exact(s)) if s == "foo:bar"
382 );
383
384 assert_matches!(
386 StringPattern::parse("foo"),
387 Ok(StringPattern::Exact(s)) if s == "foo"
388 );
389
390 assert_matches!(
392 StringPattern::parse("unknown-prefix:foo"),
393 Err(StringPatternParseError::InvalidKind(_))
394 );
395 }
396}