Nushell plugin for interacting with D-Bus
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 7068e7598ea7bc117bb332430f81bcb05b482e53 387 lines 14 kB view raw
1#[derive(Debug, Clone, PartialEq, Eq)] 2pub struct Pattern { 3 separator: Option<char>, 4 tokens: Vec<PatternToken>, 5} 6 7#[derive(Debug, Clone, PartialEq, Eq)] 8enum PatternToken { 9 Exact(String), 10 OneWildcard, 11 ManyWildcard, 12 AnyChar, 13} 14 15impl Pattern { 16 pub fn new(pattern: &str, separator: Option<char>) -> Pattern { 17 let mut tokens = vec![]; 18 for ch in pattern.chars() { 19 match ch { 20 '*' => 21 if tokens.last() == Some(&PatternToken::OneWildcard) { 22 *tokens.last_mut().unwrap() = PatternToken::ManyWildcard; 23 } else { 24 tokens.push(PatternToken::OneWildcard); 25 }, 26 '?' => 27 tokens.push(PatternToken::AnyChar), 28 _ => 29 match tokens.last_mut() { 30 Some(PatternToken::Exact(ref mut s)) => s.push(ch), 31 _ => tokens.push(PatternToken::Exact(ch.into())), 32 }, 33 } 34 } 35 Pattern { separator, tokens } 36 } 37 38 pub fn is_match(&self, string: &str) -> bool { 39 #[derive(Debug)] 40 enum MatchState { 41 Precise, 42 ScanAhead { stop_at_separator: bool }, 43 } 44 let mut state = MatchState::Precise; 45 let mut tokens = &self.tokens[..]; 46 let mut search_str = string; 47 while !tokens.is_empty() { 48 match tokens.first().unwrap() { 49 PatternToken::Exact(s) => { 50 if search_str.starts_with(s) { 51 // Exact match passed. Consume the token and string and continue 52 tokens = &tokens[1..]; 53 search_str = &search_str[s.len()..]; 54 state = MatchState::Precise; 55 } else { 56 match state { 57 MatchState::Precise => { 58 // Can't possibly match 59 return false; 60 }, 61 MatchState::ScanAhead { stop_at_separator } => { 62 if search_str.is_empty() { 63 // End of input, can't match 64 return false; 65 } 66 if stop_at_separator && 67 self.separator.is_some_and(|sep| search_str.starts_with(sep)) { 68 // Found the separator. Consume a char and revert to precise 69 // mode 70 search_str = &search_str[1..]; 71 state = MatchState::Precise; 72 } else { 73 // Skip the non-matching char and continue 74 search_str = &search_str[1..]; 75 } 76 } 77 } 78 } 79 }, 80 PatternToken::OneWildcard => { 81 // Set the mode to ScanAhead, stopping at separator 82 state = MatchState::ScanAhead { stop_at_separator: true }; 83 tokens = &tokens[1..]; 84 }, 85 PatternToken::ManyWildcard => { 86 // Set the mode to ScanAhead, ignoring separator 87 state = MatchState::ScanAhead { stop_at_separator: false }; 88 tokens = &tokens[1..]; 89 }, 90 PatternToken::AnyChar => { 91 if !search_str.is_empty() { 92 // Take a char from the search str and continue 93 search_str = &search_str[1..]; 94 tokens = &tokens[1..]; 95 } else { 96 // End of input 97 return false; 98 } 99 }, 100 } 101 } 102 #[cfg(test)] { 103 println!("end, state={:?}, search_str={:?}, tokens={:?}", state, search_str, tokens); 104 } 105 if !search_str.is_empty() { 106 // If the search str is not empty at the end 107 match state { 108 // We didn't end with a wildcard, so this is a fail 109 MatchState::Precise => false, 110 // This could be a match as long as the separator isn't contained in the remainder 111 MatchState::ScanAhead { stop_at_separator: true } => 112 if let Some(separator) = self.separator { 113 !search_str.contains(separator) 114 } else { 115 // No separator specified, so this is a success 116 true 117 }, 118 // Always a success, no matter what remains 119 MatchState::ScanAhead { stop_at_separator: false } => true, 120 } 121 } else { 122 // The match has succeeded - there is nothing more to match 123 true 124 } 125 } 126} 127 128#[test] 129fn test_pattern_new() { 130 assert_eq!( 131 Pattern::new("", Some('/')), 132 Pattern { separator: Some('/'), tokens: vec![] } 133 ); 134 assert_eq!( 135 Pattern::new("", None), 136 Pattern { separator: None, tokens: vec![] } 137 ); 138 assert_eq!( 139 Pattern::new("org.freedesktop.DBus", Some('.')), 140 Pattern { separator: Some('.'), tokens: vec![ 141 PatternToken::Exact("org.freedesktop.DBus".into()), 142 ] } 143 ); 144 assert_eq!( 145 Pattern::new("*", Some('.')), 146 Pattern { separator: Some('.'), tokens: vec![ 147 PatternToken::OneWildcard, 148 ] } 149 ); 150 assert_eq!( 151 Pattern::new("**", Some('.')), 152 Pattern { separator: Some('.'), tokens: vec![ 153 PatternToken::ManyWildcard, 154 ] } 155 ); 156 assert_eq!( 157 Pattern::new("?", Some('.')), 158 Pattern { separator: Some('.'), tokens: vec![ 159 PatternToken::AnyChar, 160 ] } 161 ); 162 assert_eq!( 163 Pattern::new("org.freedesktop.*", Some('.')), 164 Pattern { separator: Some('.'), tokens: vec![ 165 PatternToken::Exact("org.freedesktop.".into()), 166 PatternToken::OneWildcard, 167 ] } 168 ); 169 assert_eq!( 170 Pattern::new("org.freedesktop.**", Some('.')), 171 Pattern { separator: Some('.'), tokens: vec![ 172 PatternToken::Exact("org.freedesktop.".into()), 173 PatternToken::ManyWildcard, 174 ] } 175 ); 176 assert_eq!( 177 Pattern::new("org.*.DBus", Some('.')), 178 Pattern { separator: Some('.'), tokens: vec![ 179 PatternToken::Exact("org.".into()), 180 PatternToken::OneWildcard, 181 PatternToken::Exact(".DBus".into()), 182 ] } 183 ); 184 assert_eq!( 185 Pattern::new("org.**.DBus", Some('.')), 186 Pattern { separator: Some('.'), tokens: vec![ 187 PatternToken::Exact("org.".into()), 188 PatternToken::ManyWildcard, 189 PatternToken::Exact(".DBus".into()), 190 ] } 191 ); 192 assert_eq!( 193 Pattern::new("org.**.?Bus", Some('.')), 194 Pattern { separator: Some('.'), tokens: vec![ 195 PatternToken::Exact("org.".into()), 196 PatternToken::ManyWildcard, 197 PatternToken::Exact(".".into()), 198 PatternToken::AnyChar, 199 PatternToken::Exact("Bus".into()), 200 ] } 201 ); 202 assert_eq!( 203 Pattern::new("org.free*top", Some('.')), 204 Pattern { separator: Some('.'), tokens: vec![ 205 PatternToken::Exact("org.free".into()), 206 PatternToken::OneWildcard, 207 PatternToken::Exact("top".into()), 208 ] } 209 ); 210 assert_eq!( 211 Pattern::new("org.free**top", Some('.')), 212 Pattern { separator: Some('.'), tokens: vec![ 213 PatternToken::Exact("org.free".into()), 214 PatternToken::ManyWildcard, 215 PatternToken::Exact("top".into()), 216 ] } 217 ); 218 assert_eq!( 219 Pattern::new("org.**top", Some('.')), 220 Pattern { separator: Some('.'), tokens: vec![ 221 PatternToken::Exact("org.".into()), 222 PatternToken::ManyWildcard, 223 PatternToken::Exact("top".into()), 224 ] } 225 ); 226 assert_eq!( 227 Pattern::new("**top", Some('.')), 228 Pattern { separator: Some('.'), tokens: vec![ 229 PatternToken::ManyWildcard, 230 PatternToken::Exact("top".into()), 231 ] } 232 ); 233 assert_eq!( 234 Pattern::new("org.free**", Some('.')), 235 Pattern { separator: Some('.'), tokens: vec![ 236 PatternToken::Exact("org.free".into()), 237 PatternToken::ManyWildcard, 238 ] } 239 ); 240} 241 242#[test] 243fn test_pattern_is_match_empty() { 244 let pat = Pattern { separator: Some('.'), tokens: vec![] }; 245 assert!(pat.is_match("")); 246 assert!(!pat.is_match("anystring")); 247 assert!(!pat.is_match("anystring.anyotherstring")); 248} 249 250#[test] 251fn test_pattern_is_match_exact() { 252 let pat = Pattern { separator: Some('.'), tokens: vec![ 253 PatternToken::Exact("specific".into()), 254 ] }; 255 assert!(pat.is_match("specific")); 256 assert!(!pat.is_match("")); 257 assert!(!pat.is_match("specifi")); 258 assert!(!pat.is_match("specifica")); 259} 260 261#[test] 262fn test_pattern_is_match_one_wildcard() { 263 let pat = Pattern { separator: Some('.'), tokens: vec![ 264 PatternToken::Exact("foo.".into()), 265 PatternToken::OneWildcard, 266 PatternToken::Exact(".baz".into()), 267 ] }; 268 assert!(pat.is_match("foo.bar.baz")); 269 assert!(pat.is_match("foo.grok.baz")); 270 assert!(pat.is_match("foo..baz")); 271 assert!(!pat.is_match("foo.ono.notmatch.baz")); 272 assert!(!pat.is_match("")); 273 assert!(!pat.is_match("specifi")); 274 assert!(!pat.is_match("specifica.baz")); 275 assert!(!pat.is_match("foo.specifica")); 276} 277 278#[test] 279fn test_pattern_is_match_one_wildcard_at_end() { 280 let pat = Pattern { separator: Some('.'), tokens: vec![ 281 PatternToken::Exact("foo.".into()), 282 PatternToken::OneWildcard, 283 ] }; 284 assert!(pat.is_match("foo.bar")); 285 assert!(pat.is_match("foo.grok")); 286 assert!(pat.is_match("foo.")); 287 assert!(!pat.is_match("foo.ono.notmatch.baz")); 288 assert!(!pat.is_match("")); 289 assert!(!pat.is_match("specifi")); 290 assert!(!pat.is_match("specifica.baz")); 291} 292 293#[test] 294fn test_pattern_is_match_one_wildcard_at_start() { 295 let pat = Pattern { separator: Some('.'), tokens: vec![ 296 PatternToken::OneWildcard, 297 PatternToken::Exact(".bar".into()), 298 ] }; 299 assert!(pat.is_match("foo.bar")); 300 assert!(pat.is_match("grok.bar")); 301 assert!(pat.is_match(".bar")); 302 assert!(!pat.is_match("foo.ono.notmatch.bar")); 303 assert!(!pat.is_match("")); 304 assert!(!pat.is_match("specifi")); 305 assert!(!pat.is_match("specifica.baz")); 306} 307 308#[test] 309fn test_pattern_is_match_one_wildcard_no_separator() { 310 let pat = Pattern { separator: None, tokens: vec![ 311 PatternToken::Exact("foo.".into()), 312 PatternToken::OneWildcard, 313 PatternToken::Exact(".baz".into()), 314 ] }; 315 assert!(pat.is_match("foo.bar.baz")); 316 assert!(pat.is_match("foo.grok.baz")); 317 assert!(pat.is_match("foo..baz")); 318 assert!(pat.is_match("foo.this.shouldmatch.baz")); 319 assert!(pat.is_match("foo.this.should.match.baz")); 320 assert!(!pat.is_match("")); 321 assert!(!pat.is_match("specifi")); 322 assert!(!pat.is_match("specifica.baz")); 323 assert!(!pat.is_match("foo.specifica")); 324} 325 326#[test] 327fn test_pattern_is_match_many_wildcard() { 328 let pat = Pattern { separator: Some('.'), tokens: vec![ 329 PatternToken::Exact("foo.".into()), 330 PatternToken::ManyWildcard, 331 PatternToken::Exact(".baz".into()), 332 ] }; 333 assert!(pat.is_match("foo.bar.baz")); 334 assert!(pat.is_match("foo.grok.baz")); 335 assert!(pat.is_match("foo..baz")); 336 assert!(pat.is_match("foo.this.shouldmatch.baz")); 337 assert!(pat.is_match("foo.this.should.match.baz")); 338 assert!(!pat.is_match("")); 339 assert!(!pat.is_match("specifi")); 340 assert!(!pat.is_match("specifica.baz")); 341 assert!(!pat.is_match("foo.specifica")); 342} 343 344#[test] 345fn test_pattern_is_match_many_wildcard_at_end() { 346 let pat = Pattern { separator: Some('.'), tokens: vec![ 347 PatternToken::Exact("foo.".into()), 348 PatternToken::ManyWildcard, 349 ] }; 350 assert!(pat.is_match("foo.bar")); 351 assert!(pat.is_match("foo.grok")); 352 assert!(pat.is_match("foo.")); 353 assert!(pat.is_match("foo.this.should.match")); 354 assert!(!pat.is_match("")); 355 assert!(!pat.is_match("specifi")); 356 assert!(!pat.is_match("specifica.baz")); 357} 358 359#[test] 360fn test_pattern_is_match_many_wildcard_at_start() { 361 let pat = Pattern { separator: Some('.'), tokens: vec![ 362 PatternToken::ManyWildcard, 363 PatternToken::Exact(".bar".into()), 364 ] }; 365 assert!(pat.is_match("foo.bar")); 366 assert!(pat.is_match("grok.bar")); 367 assert!(pat.is_match("should.match.bar")); 368 assert!(pat.is_match(".bar")); 369 assert!(!pat.is_match("")); 370 assert!(!pat.is_match("specifi")); 371 assert!(!pat.is_match("specifica.baz")); 372} 373 374#[test] 375fn test_pattern_is_match_any_char() { 376 let pat = Pattern { separator: Some('.'), tokens: vec![ 377 PatternToken::Exact("fo".into()), 378 PatternToken::AnyChar, 379 PatternToken::Exact(".baz".into()), 380 ] }; 381 assert!(pat.is_match("foo.baz")); 382 assert!(pat.is_match("foe.baz")); 383 assert!(pat.is_match("foi.baz")); 384 assert!(!pat.is_match("")); 385 assert!(!pat.is_match("fooo.baz")); 386 assert!(!pat.is_match("fo.baz")); 387}