1use regex::Regex;
9use std::fmt;
10
11#[derive(Debug, Clone)]
13pub enum NamePattern {
14 Exact(String),
16 Glob(glob::Pattern),
18 Regex(Regex),
20}
21
22impl NamePattern {
23 pub fn parse(name: &str) -> Result<Self, PatternError> {
32 if let Some(pattern) = name.strip_prefix("glob:") {
33 let glob_pattern = glob::Pattern::new(pattern).map_err(|e| PatternError::Glob {
34 pattern: pattern.to_string(),
35 message: e.to_string(),
36 })?;
37 Ok(Self::Glob(glob_pattern))
38 } else if let Some(pattern) = name.strip_prefix("regex:") {
39 let regex = Regex::new(pattern).map_err(|e| PatternError::Regex {
40 pattern: pattern.to_string(),
41 message: e.to_string(),
42 })?;
43 Ok(Self::Regex(regex))
44 } else {
45 Ok(Self::Exact(name.to_string()))
46 }
47 }
48
49 #[must_use]
51 pub fn matches(&self, name: &str) -> bool {
52 match self {
53 Self::Exact(exact) => exact == name,
54 Self::Glob(pattern) => pattern.matches(name),
55 Self::Regex(regex) => regex.is_match(name),
56 }
57 }
58
59 #[must_use]
61 pub fn is_exact(&self) -> bool {
62 matches!(self, Self::Exact(_))
63 }
64
65 #[must_use]
67 pub fn as_str(&self) -> &str {
68 match self {
69 Self::Exact(s) => s,
70 Self::Glob(p) => p.as_str(),
71 Self::Regex(r) => r.as_str(),
72 }
73 }
74}
75
76impl PartialEq for NamePattern {
77 fn eq(&self, other: &Self) -> bool {
78 match (self, other) {
79 (Self::Exact(a), Self::Exact(b)) => a == b,
80 (Self::Glob(a), Self::Glob(b)) => a.as_str() == b.as_str(),
81 (Self::Regex(a), Self::Regex(b)) => a.as_str() == b.as_str(),
82 _ => false,
83 }
84 }
85}
86
87impl fmt::Display for NamePattern {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 Self::Exact(s) => write!(f, "{s}"),
91 Self::Glob(p) => write!(f, "glob:{}", p.as_str()),
92 Self::Regex(r) => write!(f, "regex:{}", r.as_str()),
93 }
94 }
95}
96
97#[derive(Debug, Clone, thiserror::Error)]
99pub enum PatternError {
100 #[error("invalid glob pattern '{pattern}': {message}")]
102 Glob {
103 pattern: String,
105 message: String,
107 },
108 #[error("invalid regex pattern '{pattern}': {message}")]
110 Regex {
111 pattern: String,
113 message: String,
115 },
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_exact_match() {
124 let pattern = NamePattern::parse("malloc").unwrap();
125 assert!(pattern.is_exact());
126 assert!(pattern.matches("malloc"));
127 assert!(!pattern.matches("calloc"));
128 assert!(!pattern.matches("malloc_usable_size"));
129 }
130
131 #[test]
132 fn test_glob_prefix_match() {
133 let pattern = NamePattern::parse("glob:str*").unwrap();
134 assert!(!pattern.is_exact());
135 assert!(pattern.matches("strlen"));
136 assert!(pattern.matches("strcpy"));
137 assert!(pattern.matches("strcat"));
138 assert!(!pattern.matches("malloc"));
139 }
140
141 #[test]
142 fn test_glob_suffix_match() {
143 let pattern = NamePattern::parse("glob:*alloc").unwrap();
144 assert!(pattern.matches("malloc"));
145 assert!(pattern.matches("calloc"));
146 assert!(pattern.matches("realloc"));
147 assert!(!pattern.matches("free"));
148 }
149
150 #[test]
151 fn test_glob_complex() {
152 let pattern = NamePattern::parse("glob:mem[cs]*").unwrap();
153 assert!(pattern.matches("memcpy"));
154 assert!(pattern.matches("memset"));
155 assert!(pattern.matches("memcmp"));
156 assert!(!pattern.matches("memmove"));
157 }
158
159 #[test]
160 fn test_regex_alternation() {
161 let pattern = NamePattern::parse("regex:^mem(cpy|set|move)$").unwrap();
162 assert!(pattern.matches("memcpy"));
163 assert!(pattern.matches("memset"));
164 assert!(pattern.matches("memmove"));
165 assert!(!pattern.matches("memcmp"));
166 assert!(!pattern.matches("memory"));
167 }
168
169 #[test]
170 fn test_regex_prefix() {
171 let pattern = NamePattern::parse("regex:^pthread_").unwrap();
172 assert!(pattern.matches("pthread_create"));
173 assert!(pattern.matches("pthread_join"));
174 assert!(pattern.matches("pthread_mutex_lock"));
175 assert!(!pattern.matches("create_pthread"));
176 }
177
178 #[test]
179 fn test_invalid_glob() {
180 let result = NamePattern::parse("glob:[invalid");
181 assert!(result.is_err());
182 let err = result.unwrap_err();
183 assert!(matches!(err, PatternError::Glob { .. }));
184 }
185
186 #[test]
187 fn test_invalid_regex() {
188 let result = NamePattern::parse("regex:[invalid");
189 assert!(result.is_err());
190 let err = result.unwrap_err();
191 assert!(matches!(err, PatternError::Regex { .. }));
192 }
193
194 #[test]
195 fn test_pattern_display() {
196 assert_eq!(NamePattern::parse("malloc").unwrap().to_string(), "malloc");
197 assert_eq!(
198 NamePattern::parse("glob:str*").unwrap().to_string(),
199 "glob:str*"
200 );
201 assert_eq!(
202 NamePattern::parse("regex:^mem").unwrap().to_string(),
203 "regex:^mem"
204 );
205 }
206
207 #[test]
208 fn test_pattern_equality() {
209 let p1 = NamePattern::parse("malloc").unwrap();
210 let p2 = NamePattern::parse("malloc").unwrap();
211 let p3 = NamePattern::parse("calloc").unwrap();
212 assert_eq!(p1, p2);
213 assert_ne!(p1, p3);
214
215 let g1 = NamePattern::parse("glob:str*").unwrap();
216 let g2 = NamePattern::parse("glob:str*").unwrap();
217 let g3 = NamePattern::parse("glob:mem*").unwrap();
218 assert_eq!(g1, g2);
219 assert_ne!(g1, g3);
220 }
221}