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 RegexI(regex::Regex),
83}
84
85impl StringPattern {
86 pub const fn everything() -> Self {
88 StringPattern::Substring(String::new())
89 }
90
91 pub fn parse(src: &str) -> Result<StringPattern, StringPatternParseError> {
98 if let Some((kind, pat)) = src.split_once(':') {
99 StringPattern::from_str_kind(pat, kind)
100 } else {
101 Ok(StringPattern::exact(src))
102 }
103 }
104
105 pub fn exact(src: impl Into<String>) -> Self {
107 StringPattern::Exact(src.into())
108 }
109
110 pub fn exact_i(src: impl Into<String>) -> Self {
112 StringPattern::ExactI(src.into())
113 }
114
115 pub fn substring(src: impl Into<String>) -> Self {
117 StringPattern::Substring(src.into())
118 }
119
120 pub fn substring_i(src: impl Into<String>) -> Self {
122 StringPattern::SubstringI(src.into())
123 }
124
125 pub fn glob(src: &str) -> Result<Self, StringPatternParseError> {
127 Ok(StringPattern::Glob(parse_glob(src)?))
131 }
132
133 pub fn glob_i(src: &str) -> Result<Self, StringPatternParseError> {
135 Ok(StringPattern::GlobI(parse_glob(src)?))
136 }
137
138 pub fn regex(src: &str) -> Result<Self, StringPatternParseError> {
140 let pattern = regex::Regex::new(src).map_err(StringPatternParseError::Regex)?;
141 Ok(StringPattern::Regex(pattern))
142 }
143
144 pub fn regex_i(src: &str) -> Result<Self, StringPatternParseError> {
146 let pattern = regex::RegexBuilder::new(src)
147 .case_insensitive(true)
148 .build()
149 .map_err(StringPatternParseError::Regex)?;
150 Ok(StringPattern::RegexI(pattern))
151 }
152
153 pub fn from_str_kind(src: &str, kind: &str) -> Result<Self, StringPatternParseError> {
155 match kind {
156 "exact" => Ok(StringPattern::exact(src)),
157 "exact-i" => Ok(StringPattern::exact_i(src)),
158 "substring" => Ok(StringPattern::substring(src)),
159 "substring-i" => Ok(StringPattern::substring_i(src)),
160 "glob" => StringPattern::glob(src),
161 "glob-i" => StringPattern::glob_i(src),
162 "regex" => StringPattern::regex(src),
163 "regex-i" => StringPattern::regex_i(src),
164 _ => Err(StringPatternParseError::InvalidKind(kind.to_owned())),
165 }
166 }
167
168 pub fn is_exact(&self) -> bool {
170 self.as_exact().is_some()
171 }
172
173 pub fn as_exact(&self) -> Option<&str> {
177 match self {
181 StringPattern::Exact(literal) => Some(literal),
182 _ => None,
183 }
184 }
185
186 pub fn as_str(&self) -> &str {
188 match self {
189 StringPattern::Exact(literal) => literal,
190 StringPattern::ExactI(literal) => literal,
191 StringPattern::Substring(needle) => needle,
192 StringPattern::SubstringI(needle) => needle,
193 StringPattern::Glob(pattern) => pattern.as_str(),
194 StringPattern::GlobI(pattern) => pattern.as_str(),
195 StringPattern::Regex(pattern) => pattern.as_str(),
196 StringPattern::RegexI(pattern) => pattern.as_str(),
197 }
198 }
199
200 pub fn to_glob(&self) -> Option<Cow<'_, str>> {
203 match self {
207 StringPattern::Exact(literal) => Some(glob::Pattern::escape(literal).into()),
208 StringPattern::Substring(needle) => {
209 if needle.is_empty() {
210 Some("*".into())
211 } else {
212 Some(format!("*{}*", glob::Pattern::escape(needle)).into())
213 }
214 }
215 StringPattern::Glob(pattern) => Some(pattern.as_str().into()),
216 StringPattern::ExactI(_) => None,
217 StringPattern::SubstringI(_) => None,
218 StringPattern::GlobI(_) => None,
219 StringPattern::Regex(_) => None,
220 StringPattern::RegexI(_) => None,
221 }
222 }
223
224 pub fn matches(&self, haystack: &str) -> bool {
229 match self {
247 StringPattern::Exact(literal) => haystack == literal,
248 StringPattern::ExactI(literal) => haystack.eq_ignore_ascii_case(literal),
249 StringPattern::Substring(needle) => haystack.contains(needle),
250 StringPattern::SubstringI(needle) => haystack
251 .to_ascii_lowercase()
252 .contains(&needle.to_ascii_lowercase()),
253 StringPattern::Glob(pattern) => pattern.0.matches(haystack),
254 StringPattern::GlobI(pattern) => pattern.0.matches_with(
255 haystack,
256 glob::MatchOptions {
257 case_sensitive: false,
258 ..glob::MatchOptions::new()
259 },
260 ),
261 StringPattern::Regex(pattern) => pattern.is_match(haystack),
264 StringPattern::RegexI(pattern) => pattern.is_match(haystack),
265 }
266 }
267
268 pub fn filter_btree_map<'a, 'b, K: Borrow<str> + Ord, V>(
271 &'b self,
272 map: &'a BTreeMap<K, V>,
273 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, V> {
274 self.filter_btree_map_with(map, |key| key, |key| key)
275 }
276
277 pub fn filter_btree_map_as_deref<'a, 'b, K, V>(
283 &'b self,
284 map: &'a BTreeMap<K, V>,
285 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, V>
286 where
287 K: Borrow<K::Target> + Deref + Ord,
288 K::Target: AsRef<str> + Ord,
289 str: AsRef<K::Target>,
290 {
291 self.filter_btree_map_with(map, AsRef::as_ref, AsRef::as_ref)
292 }
293
294 fn filter_btree_map_with<'a, 'b, K, Q, V, FromKey, ToKey>(
295 &'b self,
296 map: &'a BTreeMap<K, V>,
297 from_key: FromKey,
298 to_key: ToKey,
299 ) -> impl Iterator<Item = (&'a K, &'a V)> + use<'a, 'b, K, Q, V, FromKey, ToKey>
303 where
304 K: Borrow<Q> + Ord,
305 Q: Ord + ?Sized,
306 FromKey: Fn(&Q) -> &str,
307 ToKey: Fn(&str) -> &Q,
308 {
309 if let Some(key) = self.as_exact() {
310 Either::Left(map.get_key_value(to_key(key)).into_iter())
311 } else {
312 Either::Right(
313 map.iter()
314 .filter(move |&(key, _)| self.matches(from_key(key.borrow()))),
315 )
316 }
317 }
318}
319
320impl fmt::Display for StringPattern {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 write!(f, "{}", self.as_str())
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use assert_matches::assert_matches;
330
331 use super::*;
332
333 #[test]
334 fn test_string_pattern_to_glob() {
335 assert_eq!(StringPattern::everything().to_glob(), Some("*".into()));
336 assert_eq!(StringPattern::exact("a").to_glob(), Some("a".into()));
337 assert_eq!(StringPattern::exact("*").to_glob(), Some("[*]".into()));
338 assert_eq!(
339 StringPattern::glob("*").unwrap().to_glob(),
340 Some("*".into())
341 );
342 assert_eq!(
343 StringPattern::Substring("a".into()).to_glob(),
344 Some("*a*".into())
345 );
346 assert_eq!(
347 StringPattern::Substring("*".into()).to_glob(),
348 Some("*[*]*".into())
349 );
350 }
351
352 #[test]
353 fn test_parse() {
354 assert_matches!(
356 StringPattern::parse("exact:foo"),
357 Ok(StringPattern::Exact(s)) if s == "foo"
358 );
359 assert_matches!(
360 StringPattern::from_str_kind("foo", "exact"),
361 Ok(StringPattern::Exact(s)) if s == "foo"
362 );
363 assert_matches!(
364 StringPattern::parse("glob:foo*"),
365 Ok(StringPattern::Glob(p)) if p.as_str() == "foo*"
366 );
367 assert_matches!(
368 StringPattern::from_str_kind("foo*", "glob"),
369 Ok(StringPattern::Glob(p)) if p.as_str() == "foo*"
370 );
371 assert_matches!(
372 StringPattern::parse("substring:foo"),
373 Ok(StringPattern::Substring(s)) if s == "foo"
374 );
375 assert_matches!(
376 StringPattern::from_str_kind("foo", "substring"),
377 Ok(StringPattern::Substring(s)) if s == "foo"
378 );
379 assert_matches!(
380 StringPattern::parse("substring-i:foo"),
381 Ok(StringPattern::SubstringI(s)) if s == "foo"
382 );
383 assert_matches!(
384 StringPattern::from_str_kind("foo", "substring-i"),
385 Ok(StringPattern::SubstringI(s)) if s == "foo"
386 );
387 assert_matches!(
388 StringPattern::parse("regex:foo"),
389 Ok(StringPattern::Regex(p)) if p.as_str() == "foo"
390 );
391 assert_matches!(
392 StringPattern::from_str_kind("foo", "regex"),
393 Ok(StringPattern::Regex(p)) if p.as_str() == "foo"
394 );
395 assert_matches!(
396 StringPattern::parse("regex-i:foo"),
397 Ok(StringPattern::RegexI(p)) if p.as_str() == "foo"
398 );
399 assert_matches!(
400 StringPattern::from_str_kind("foo", "regex-i"),
401 Ok(StringPattern::RegexI(p)) if p.as_str() == "foo"
402 );
403
404 assert_matches!(
406 StringPattern::parse("exact:foo:bar"),
407 Ok(StringPattern::Exact(s)) if s == "foo:bar"
408 );
409
410 assert_matches!(
412 StringPattern::parse("foo"),
413 Ok(StringPattern::Exact(s)) if s == "foo"
414 );
415
416 assert_matches!(
418 StringPattern::parse("unknown-prefix:foo"),
419 Err(StringPatternParseError::InvalidKind(_))
420 );
421 }
422}