| 1 | //! Match Xrm entries against a query. |
| 2 | |
| 3 | use alloc::string::String; |
| 4 | use alloc::vec; |
| 5 | use alloc::vec::Vec; |
| 6 | use std::cmp::Ordering; |
| 7 | |
| 8 | use super::parser::parse_query; |
| 9 | use super::{Binding, Component, Entry}; |
| 10 | |
| 11 | mod zip_longest { |
| 12 | /// Given two slices, produce an iterator that zips the two slices. |
| 13 | /// |
| 14 | /// Compared to std::iter::Iterator::zip(), this iterator does not stop at the end of the |
| 15 | /// shorter of the two slices, but instead continues to the end of the longer slice. To make |
| 16 | /// this possible, the individual items are wrapped in `Option`. |
| 17 | /// |
| 18 | /// See tests below to make this clearer. |
| 19 | pub(super) fn zip_longest<'a, T>( |
| 20 | a: &'a [T], |
| 21 | b: &'a [T], |
| 22 | ) -> impl Iterator<Item = (Option<&'a T>, Option<&'a T>)> + 'a { |
| 23 | ZipLongest { |
| 24 | a: a.iter(), |
| 25 | b: b.iter(), |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | #[derive (Debug)] |
| 30 | struct ZipLongest<A, B> { |
| 31 | a: A, |
| 32 | b: B, |
| 33 | } |
| 34 | |
| 35 | impl<A, B> Iterator for ZipLongest<A, B> |
| 36 | where |
| 37 | A: Iterator, |
| 38 | B: Iterator, |
| 39 | { |
| 40 | type Item = (Option<A::Item>, Option<B::Item>); |
| 41 | |
| 42 | fn next(&mut self) -> Option<Self::Item> { |
| 43 | match (self.a.next(), self.b.next()) { |
| 44 | (None, None) => None, |
| 45 | (a, b) => Some((a, b)), |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | #[cfg (test)] |
| 51 | mod test_zip_longest { |
| 52 | use super::zip_longest; |
| 53 | use alloc::vec::Vec; |
| 54 | |
| 55 | #[test ] |
| 56 | fn empty() { |
| 57 | let (a, b): ([u8; 0], [u8; 0]) = ([], []); |
| 58 | let res = zip_longest(&a, &b).collect::<Vec<_>>(); |
| 59 | assert_eq!(res, []); |
| 60 | } |
| 61 | |
| 62 | #[test ] |
| 63 | fn same_length() { |
| 64 | let a = [0, 1, 2]; |
| 65 | let b = [4, 5, 6]; |
| 66 | let expected = [ |
| 67 | (Some(&0), Some(&4)), |
| 68 | (Some(&1), Some(&5)), |
| 69 | (Some(&2), Some(&6)), |
| 70 | ]; |
| 71 | let res = zip_longest(&a, &b).collect::<Vec<_>>(); |
| 72 | assert_eq!(res, expected); |
| 73 | } |
| 74 | |
| 75 | #[test ] |
| 76 | fn first_shorter() { |
| 77 | let a = [0, 1]; |
| 78 | let b = [4, 5, 6, 7]; |
| 79 | let expected = [ |
| 80 | (Some(&0), Some(&4)), |
| 81 | (Some(&1), Some(&5)), |
| 82 | (None, Some(&6)), |
| 83 | (None, Some(&7)), |
| 84 | ]; |
| 85 | let res = zip_longest(&a, &b).collect::<Vec<_>>(); |
| 86 | assert_eq!(res, expected); |
| 87 | } |
| 88 | |
| 89 | #[test ] |
| 90 | fn second_shorter() { |
| 91 | let a = [0, 1, 2, 3]; |
| 92 | let b = [4, 5]; |
| 93 | let expected = [ |
| 94 | (Some(&0), Some(&4)), |
| 95 | (Some(&1), Some(&5)), |
| 96 | (Some(&2), None), |
| 97 | (Some(&3), None), |
| 98 | ]; |
| 99 | let res = zip_longest(&a, &b).collect::<Vec<_>>(); |
| 100 | assert_eq!(res, expected); |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /// Info how a specific component was matched. |
| 106 | /// |
| 107 | /// This information is used to decide which of two matches is "better" in `compare_matches()`. |
| 108 | #[derive (Debug, Copy, Clone)] |
| 109 | enum HowMatched { |
| 110 | /// The component matched the instance of the query |
| 111 | Instance, |
| 112 | /// The component matched the class of the query |
| 113 | Class, |
| 114 | /// The component is a wildcard and thus matched by default |
| 115 | Wildcard, |
| 116 | } |
| 117 | |
| 118 | /// Info on how an (unskipped) component of the query was matched |
| 119 | /// |
| 120 | /// This information is used to decide which of two matches is "better" in `compare_matches()`. |
| 121 | #[derive (Debug, Copy, Clone)] |
| 122 | struct MatchComponent { |
| 123 | preceding_binding: Binding, |
| 124 | how_matched: HowMatched, |
| 125 | } |
| 126 | |
| 127 | /// Info how a (possibly skipped) component of the query was matched |
| 128 | /// |
| 129 | /// This information is used to decide which of two matches is "better" in `compare_matches()`. |
| 130 | #[derive (Debug, Copy, Clone)] |
| 131 | enum MatchKind { |
| 132 | /// The component was skipped via a loose binding ("*") |
| 133 | SkippedViaLooseBinding, |
| 134 | /// The component was matched against the entry. |
| 135 | Matched(MatchComponent), |
| 136 | } |
| 137 | |
| 138 | impl MatchKind { |
| 139 | /// Create a new `MatchKind::Match` with the given entries. |
| 140 | fn new_match(preceding_binding: Binding, how_matched: HowMatched) -> Self { |
| 141 | Self::Matched(MatchComponent { |
| 142 | preceding_binding, |
| 143 | how_matched, |
| 144 | }) |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | fn check_match(entry: &Entry, resource: &[String], class: &[String]) -> Vec<Vec<MatchKind>> { |
| 149 | /// Current state of the matching machinery |
| 150 | #[derive (Debug, Default)] |
| 151 | struct MatchState { |
| 152 | /// Index into the entry on where we have to continue matching |
| 153 | index: usize, |
| 154 | /// How did we get to this state? |
| 155 | history: Vec<MatchKind>, |
| 156 | } |
| 157 | |
| 158 | impl MatchState { |
| 159 | /// Record that a component was skipped via a loose binding (`*`). |
| 160 | fn skip_loose(&self) -> Self { |
| 161 | let mut history = self.history.clone(); |
| 162 | history.push(MatchKind::SkippedViaLooseBinding); |
| 163 | Self { |
| 164 | index: self.index, |
| 165 | history, |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | /// Record that a component was matched in the given way. |
| 170 | fn step(mut self, kind: MatchKind) -> Self { |
| 171 | self.history.push(kind); |
| 172 | self.index += 1; |
| 173 | self |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // The idea is to check if a nondeterministic finite automaton accepts a given |
| 178 | // word. We have a set of current states. This describes where in the |
| 179 | // entry we are while trying to match. When we match a component, we go to the next |
| 180 | // component in the entry (index + 1, `MatchState::step()`). When we have a loose binding, we |
| 181 | // can accept the current component by staying in the same state (index, |
| 182 | // `MatchState::skip_loose()`). |
| 183 | let mut states = vec![MatchState::default()]; |
| 184 | |
| 185 | // Go through the components and match them against the query |
| 186 | for (resource, class) in zip_longest::zip_longest(resource, class) { |
| 187 | let mut next_states = Vec::new(); |
| 188 | for state in states.into_iter() { |
| 189 | if state.index == entry.components.len() { |
| 190 | // We are at the end of the entry and thus cannot continue this match. |
| 191 | // We drop this match state. |
| 192 | continue; |
| 193 | } |
| 194 | let binding = entry.components[state.index].0; |
| 195 | match binding { |
| 196 | // We have to match here, no way around that. |
| 197 | Binding::Tight => {} |
| 198 | // We could "eat" this with the loose binding by staying in the state |
| 199 | Binding::Loose => next_states.push(state.skip_loose()), |
| 200 | } |
| 201 | // Does the component match? |
| 202 | let kind = match entry.components[state.index].1 { |
| 203 | Component::Wildcard => Some(MatchKind::new_match(binding, HowMatched::Wildcard)), |
| 204 | Component::Normal(ref s) => { |
| 205 | if Some(s) == resource { |
| 206 | Some(MatchKind::new_match(binding, HowMatched::Instance)) |
| 207 | } else if Some(s) == class { |
| 208 | Some(MatchKind::new_match(binding, HowMatched::Class)) |
| 209 | } else { |
| 210 | None |
| 211 | } |
| 212 | } |
| 213 | }; |
| 214 | if let Some(kind) = kind { |
| 215 | // Yes, the component matches and we go to the next state |
| 216 | next_states.push(state.step(kind)); |
| 217 | } |
| 218 | } |
| 219 | states = next_states; |
| 220 | } |
| 221 | // We have a match if we reached the end of the components |
| 222 | states |
| 223 | .into_iter() |
| 224 | .filter(|s| s.index == entry.components.len()) |
| 225 | .map(|s| s.history) |
| 226 | .collect() |
| 227 | } |
| 228 | |
| 229 | /// Compare two matches and decide which one of the two is better (`Ordering::Greater`) |
| 230 | fn compare_matches(match1: &[MatchKind], match2: &[MatchKind]) -> Ordering { |
| 231 | use Binding::*; |
| 232 | use HowMatched::*; |
| 233 | use MatchKind::*; |
| 234 | |
| 235 | fn rule1(match1: &MatchKind, match2: &MatchKind) -> Ordering { |
| 236 | // Precedence rule #1: Matching components (including wildcard '?') outweighs loose bindings ('*') |
| 237 | if let Matched(_) = match1 { |
| 238 | if let SkippedViaLooseBinding = match2 { |
| 239 | return Ordering::Greater; |
| 240 | } |
| 241 | } |
| 242 | Ordering::Equal |
| 243 | } |
| 244 | |
| 245 | fn rule2(match1: &MatchKind, match2: &MatchKind) -> Ordering { |
| 246 | // Precedence rule #2a: Matching instance outweighs both matching class and wildcard |
| 247 | if let Matched(MatchComponent { |
| 248 | how_matched: Instance, |
| 249 | preceding_binding: _, |
| 250 | }) = match1 |
| 251 | { |
| 252 | if let Matched(MatchComponent { |
| 253 | how_matched: Class, |
| 254 | preceding_binding: _, |
| 255 | }) = match2 |
| 256 | { |
| 257 | return Ordering::Greater; |
| 258 | } |
| 259 | if let Matched(MatchComponent { |
| 260 | how_matched: Wildcard, |
| 261 | .. |
| 262 | }) = match2 |
| 263 | { |
| 264 | return Ordering::Greater; |
| 265 | } |
| 266 | } |
| 267 | // Precedence rule #2b: Matching class outweighs wildcard |
| 268 | if let Matched(MatchComponent { |
| 269 | how_matched: Class, .. |
| 270 | }) = match1 |
| 271 | { |
| 272 | if let Matched(MatchComponent { |
| 273 | how_matched: Wildcard, |
| 274 | .. |
| 275 | }) = match2 |
| 276 | { |
| 277 | return Ordering::Greater; |
| 278 | } |
| 279 | } |
| 280 | Ordering::Equal |
| 281 | } |
| 282 | |
| 283 | fn rule3(match1: &MatchKind, match2: &MatchKind) -> Ordering { |
| 284 | // Precedence rule #3: A preceding exact match outweights a preceding '*' |
| 285 | if let Matched(MatchComponent { |
| 286 | preceding_binding: Tight, |
| 287 | .. |
| 288 | }) = match1 |
| 289 | { |
| 290 | if let Matched(MatchComponent { |
| 291 | preceding_binding: Loose, |
| 292 | .. |
| 293 | }) = match2 |
| 294 | { |
| 295 | return Ordering::Greater; |
| 296 | } |
| 297 | } |
| 298 | Ordering::Equal |
| 299 | } |
| 300 | |
| 301 | assert_eq!( |
| 302 | match1.len(), |
| 303 | match2.len(), |
| 304 | "Both matches should have the same length (which is guaranteed by the current \ |
| 305 | implementation of check_match())" |
| 306 | ); |
| 307 | for (m1, m2) in match1.iter().zip(match2.iter()) { |
| 308 | let ordering = rule1(m1, m2) |
| 309 | .then_with(|| rule1(m2, m1).reverse()) |
| 310 | .then_with(|| rule2(m1, m2)) |
| 311 | .then_with(|| rule2(m2, m1).reverse()) |
| 312 | .then_with(|| rule3(m1, m2)) |
| 313 | .then_with(|| rule3(m2, m1).reverse()); |
| 314 | if ordering != Ordering::Equal { |
| 315 | return ordering; |
| 316 | } |
| 317 | } |
| 318 | Ordering::Equal |
| 319 | } |
| 320 | |
| 321 | /// Find the best match for the given query in the database, returning `None` when nothing matches. |
| 322 | pub(crate) fn match_entry<'a>( |
| 323 | database: &'a [Entry], |
| 324 | resource: &str, |
| 325 | class: &str, |
| 326 | ) -> Option<&'a [u8]> { |
| 327 | let resource: Vec = parse_query(data:resource.as_bytes())?; |
| 328 | let class: Vec = parse_query(data:class.as_bytes())?; |
| 329 | databaseOption<(&Entry, Vec)> |
| 330 | .iter() |
| 331 | // Filter for entries that match the query (and record some info on how they match) |
| 332 | .flat_map(|entry| { |
| 333 | let matches = check_match(entry, &resource, &class); |
| 334 | let best_match = matches |
| 335 | .into_iter() |
| 336 | .max_by(|match1, match2| compare_matches(match1, match2)); |
| 337 | best_match.map(|m| (entry, m)) |
| 338 | }) |
| 339 | .max_by(|(_, match1: &Vec), (_, match2: &Vec)| compare_matches(match1, match2)) |
| 340 | .map(|(entry: &Entry, _)| &entry.value[..]) |
| 341 | } |
| 342 | |
| 343 | #[cfg (test)] |
| 344 | mod test { |
| 345 | use super::super::parser::parse_database; |
| 346 | use super::match_entry; |
| 347 | |
| 348 | use alloc::format; |
| 349 | use alloc::string::{String, ToString}; |
| 350 | use alloc::vec::Vec; |
| 351 | use std::eprintln; |
| 352 | |
| 353 | // Most tests in here are based on [1], which is: Copyright © 2016 Ingo Bürk |
| 354 | // [1]: https://github.com/Airblader/xcb-util-xrm/blob/master/tests/tests_match.c |
| 355 | |
| 356 | #[test ] |
| 357 | fn test_matches() { |
| 358 | let tests = [ |
| 359 | // Non-matches / Errors |
| 360 | (&b"" [..], "" , "" , None), |
| 361 | // Xlib returns the match here, despite the query violating the specs (different number |
| 362 | // of components in the query) |
| 363 | ( |
| 364 | b"First.second: 1" , |
| 365 | "First.second" , |
| 366 | "First.second.third" , |
| 367 | None, |
| 368 | ), |
| 369 | (b"" , "First.second" , "" , None), |
| 370 | (b"First.second: 1" , "First.third" , "" , None), |
| 371 | (b"First.second: 1" , "First" , "" , None), |
| 372 | (b"First: 1" , "First.second" , "" , None), |
| 373 | (b"First.?.fourth: 1" , "First.second.third.fourth" , "" , None), |
| 374 | (b"First*?.third: 1" , "First.third" , "" , None), |
| 375 | (b"First: 1" , "first" , "" , None), |
| 376 | (b"First: 1" , "" , "first" , None), |
| 377 | // Duplicate entries |
| 378 | ( |
| 379 | b"First: 1 \nFirst: 2 \nFirst: 3 \n" , |
| 380 | "First" , |
| 381 | "" , |
| 382 | Some(&b"3" [..]), |
| 383 | ), |
| 384 | ( |
| 385 | b"First: 1 \nSecond: 2 \nSecond: 3 \nThird: 4 \n" , |
| 386 | "Second" , |
| 387 | "" , |
| 388 | Some(b"3" ), |
| 389 | ), |
| 390 | // Basic matching |
| 391 | (b"First: 1" , "First" , "" , Some(b"1" )), |
| 392 | (b"First.second: 1" , "First.second" , "" , Some(b"1" )), |
| 393 | (b"?.second: 1" , "First.second" , "" , Some(b"1" )), |
| 394 | (b"First.?.third: 1" , "First.second.third" , "" , Some(b"1" )), |
| 395 | ( |
| 396 | b"First.?.?.fourth: 1" , |
| 397 | "First.second.third.fourth" , |
| 398 | "" , |
| 399 | Some(b"1" ), |
| 400 | ), |
| 401 | (b"*second: 1" , "First.second" , "" , Some(b"1" )), |
| 402 | (b".second: 1" , "First.second" , "" , None), |
| 403 | (b"*third: 1" , "First.second.third" , "" , Some(b"1" )), |
| 404 | (b"First*second: 1" , "First.second" , "" , Some(b"1" )), |
| 405 | (b"First*third: 1" , "First.second.third" , "" , Some(b"1" )), |
| 406 | ( |
| 407 | b"First*fourth: 1" , |
| 408 | "First.second.third.fourth" , |
| 409 | "" , |
| 410 | Some(b"1" ), |
| 411 | ), |
| 412 | (b"First*?.third: 1" , "First.second.third" , "" , Some(b"1" )), |
| 413 | (b"First: 1" , "Second" , "First" , Some(b"1" )), |
| 414 | ( |
| 415 | b"First.second: 1" , |
| 416 | "First.third" , |
| 417 | "first.second" , |
| 418 | Some(b"1" ), |
| 419 | ), |
| 420 | ( |
| 421 | b"First.second.third: 1" , |
| 422 | "First.third.third" , |
| 423 | "first.second.fourth" , |
| 424 | Some(b"1" ), |
| 425 | ), |
| 426 | ( |
| 427 | b"First*third*fifth: 1" , |
| 428 | "First.second.third.fourth.third.fifth" , |
| 429 | "" , |
| 430 | Some(b"1" ), |
| 431 | ), |
| 432 | (b"First: x \\\ny" , "First" , "" , Some(b"xy" )), |
| 433 | (b"! First: x" , "First" , "" , None), |
| 434 | (b"# First: x" , "First" , "" , None), |
| 435 | (b"First:" , "First" , "" , Some(b"" )), |
| 436 | (b"First: " , "First" , "" , Some(b"" )), |
| 437 | (b"First: \t " , "First" , "" , Some(b"" )), |
| 438 | // Consecutive bindings |
| 439 | (b"*.bar: 1" , "foo.foo.bar" , "" , Some(b"1" )), |
| 440 | (b"...bar: 1" , "foo.bar" , "" , None), |
| 441 | (b"...bar: 1" , "foo.foo.foo.bar" , "" , None), |
| 442 | (b"***bar: 1" , "foo.bar" , "" , Some(b"1" )), |
| 443 | (b".*.bar: 1" , "foo.bar" , "" , Some(b"1" )), |
| 444 | (b".*.bar: 1" , "foo.foo.bar" , "" , Some(b"1" )), |
| 445 | (b"..*bar: 1" , "foo.foo.foo.foo.bar" , "" , Some(b"1" )), |
| 446 | (b"a.*.z: 1" , "a.b.c.d.e.f.z" , "" , Some(b"1" )), |
| 447 | (b"a...z: 1" , "a.z" , "" , Some(b"1" )), |
| 448 | (b"a...z: 1" , "a.b.z" , "" , None), |
| 449 | // Matching among multiple entries |
| 450 | (b"First: 1 \nSecond: 2 \n" , "First" , "" , Some(b"1" )), |
| 451 | (b"First: 1 \nSecond: 2 \n" , "Second" , "" , Some(b"2" )), |
| 452 | // Greediness |
| 453 | (b"a*c.e: 1" , "a.b.c.d.c.e" , "" , Some(b"1" )), |
| 454 | (b"a*c.e: 1" , "a.b.c.c.e" , "" , Some(b"1" )), |
| 455 | (b"a*?.e: 1" , "a.b.c.e" , "" , Some(b"1" )), |
| 456 | (b"a*c*e: 1" , "a.b.c.d.c.d.e.d.e" , "" , Some(b"1" )), |
| 457 | // Precedence rules |
| 458 | // Rule 1 |
| 459 | ( |
| 460 | b"First.second.third: 1 \nFirst*third: 2 \n" , |
| 461 | "First.second.third" , |
| 462 | "" , |
| 463 | Some(b"1" ), |
| 464 | ), |
| 465 | ( |
| 466 | b"First*third: 2 \nFirst.second.third: 1 \n" , |
| 467 | "First.second.third" , |
| 468 | "" , |
| 469 | Some(b"1" ), |
| 470 | ), |
| 471 | ( |
| 472 | b"First.second.third: 1 \nFirst*third: 2 \n" , |
| 473 | "x.x.x" , |
| 474 | "First.second.third" , |
| 475 | Some(b"1" ), |
| 476 | ), |
| 477 | ( |
| 478 | b"First*third: 2 \nFirst.second.third: 1 \n" , |
| 479 | "x.x.x" , |
| 480 | "First.second.third" , |
| 481 | Some(b"1" ), |
| 482 | ), |
| 483 | // Rule 2 |
| 484 | ( |
| 485 | b"First.second: 1 \nFirst.third: 2 \n" , |
| 486 | "First.second" , |
| 487 | "First.third" , |
| 488 | Some(b"1" ), |
| 489 | ), |
| 490 | ( |
| 491 | b"First.third: 2 \nFirst.second: 1 \n" , |
| 492 | "First.second" , |
| 493 | "First.third" , |
| 494 | Some(b"1" ), |
| 495 | ), |
| 496 | ( |
| 497 | b"First.second.third: 1 \nFirst.?.third: 2 \n" , |
| 498 | "First.second.third" , |
| 499 | "" , |
| 500 | Some(b"1" ), |
| 501 | ), |
| 502 | ( |
| 503 | b"First.?.third: 2 \nFirst.second.third: 1 \n" , |
| 504 | "First.second.third" , |
| 505 | "" , |
| 506 | Some(b"1" ), |
| 507 | ), |
| 508 | ( |
| 509 | b"First.second.third: 1 \nFirst.?.third: 2 \n" , |
| 510 | "x.x.x" , |
| 511 | "First.second.third" , |
| 512 | Some(b"1" ), |
| 513 | ), |
| 514 | ( |
| 515 | b"First.?.third: 2 \nFirst.second.third: 1 \n" , |
| 516 | "x.x.x" , |
| 517 | "First.second.third" , |
| 518 | Some(b"1" ), |
| 519 | ), |
| 520 | // Rule 3 |
| 521 | ( |
| 522 | b"First.second: 1 \nFirst*second: 2 \n" , |
| 523 | "First.second" , |
| 524 | "" , |
| 525 | Some(b"1" ), |
| 526 | ), |
| 527 | ( |
| 528 | b"First*second: 2 \nFirst.second: 1 \n" , |
| 529 | "First.second" , |
| 530 | "" , |
| 531 | Some(b"1" ), |
| 532 | ), |
| 533 | // Some real world examples. May contain duplicates to the above tests. |
| 534 | |
| 535 | // From the specification: |
| 536 | // https://tronche.com/gui/x/xlib/resource-manager/matching-rules.html |
| 537 | ( |
| 538 | b"xmh*Paned*activeForeground: red \n\ |
| 539 | *incorporate.Foreground: blue \n\ |
| 540 | xmh.toc*Command*activeForeground: green \n\ |
| 541 | xmh.toc*?.Foreground: white \n\ |
| 542 | xmh.toc*Command.activeForeground: black" , |
| 543 | "xmh.toc.messagefunctions.incorporate.activeForeground" , |
| 544 | "Xmh.Paned.Box.Command.Foreground" , |
| 545 | Some(b"black" ), |
| 546 | ), |
| 547 | ( |
| 548 | b"urxvt*background: [95]#000" , |
| 549 | "urxvt.background" , |
| 550 | "" , |
| 551 | Some(b"[95]#000" ), |
| 552 | ), |
| 553 | ( |
| 554 | b"urxvt*scrollBar_right:true" , |
| 555 | "urxvt.scrollBar_right" , |
| 556 | "" , |
| 557 | Some(b"true" ), |
| 558 | ), |
| 559 | ( |
| 560 | b"urxvt*cutchars: ' \"'()*<>[]{|}" , |
| 561 | "urxvt.cutchars" , |
| 562 | "" , |
| 563 | Some(b"' \"'()*<>[]{|}" ), |
| 564 | ), |
| 565 | ( |
| 566 | b"urxvt.keysym.Control-Shift-Up: perl:font:increment" , |
| 567 | "urxvt.keysym.Control-Shift-Up" , |
| 568 | "" , |
| 569 | Some(b"perl:font:increment" ), |
| 570 | ), |
| 571 | ( |
| 572 | b"rofi.normal: #000000, #000000, #000000, #000000" , |
| 573 | "rofi.normal" , |
| 574 | "" , |
| 575 | Some(b"#000000, #000000, #000000, #000000" ), |
| 576 | ), |
| 577 | // Own tests |
| 578 | (b"*foo.bar: 1" , "bar" , "" , None), |
| 579 | ( |
| 580 | b"First.Second.Third: 1 \nFirst.Second: 2" , |
| 581 | "First.Second.Third" , |
| 582 | "First.Second" , |
| 583 | Some(b"1" ), |
| 584 | ), |
| 585 | ( |
| 586 | b"First.Second.Third: 1 \nFirst.Second: 2" , |
| 587 | "First.Second" , |
| 588 | "First.Second.Third" , |
| 589 | Some(b"1" ), |
| 590 | ), |
| 591 | ]; |
| 592 | let mut failures = 0; |
| 593 | for &(data, resource, class, expected) in tests.iter() { |
| 594 | let mut entries = Vec::new(); |
| 595 | parse_database(data, &mut entries, |_, _| unreachable!()); |
| 596 | let result = match_entry(&entries, resource, class); |
| 597 | if result != expected { |
| 598 | eprintln!( |
| 599 | "While testing resource '{}' and class '{}' with the following input:" , |
| 600 | resource, class |
| 601 | ); |
| 602 | eprintln!("{}" , print_string(data)); |
| 603 | eprintln!("Expected: {:?}" , expected.map(print_string)); |
| 604 | eprintln!("Got: {:?}" , result.map(print_string)); |
| 605 | eprintln!(); |
| 606 | failures += 1; |
| 607 | } |
| 608 | } |
| 609 | if failures != 0 { |
| 610 | panic!("Had {} failures" , failures) |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | fn print_string(data: &[u8]) -> String { |
| 615 | std::str::from_utf8(data) |
| 616 | .map(|s| s.to_string()) |
| 617 | .unwrap_or_else(|_| format!("{:?}" , data)) |
| 618 | } |
| 619 | } |
| 620 | |