max / makenotwork
1 file changed,
+15 insertions,
-6 deletions
| @@ -34,6 +34,15 @@ use std::fmt; | |||
| 34 | 34 | /// Separator between tag levels. | |
| 35 | 35 | pub const SEPARATOR: char = '.'; | |
| 36 | 36 | ||
| 37 | + | /// `SEPARATOR` as a single byte, for the scans that walk `str::bytes` and slice at | |
| 38 | + | /// the byte offsets they find. Those are only sound while the separator is ASCII: | |
| 39 | + | /// a multi-byte separator would match a continuation byte and slice mid-codepoint. | |
| 40 | + | /// The assert pins that invariant at compile time. | |
| 41 | + | const SEPARATOR_BYTE: u8 = { | |
| 42 | + | assert!(SEPARATOR.is_ascii(), "SEPARATOR must be ASCII; byte-level scans slice at byte offsets"); | |
| 43 | + | SEPARATOR as u8 | |
| 44 | + | }; | |
| 45 | + | ||
| 37 | 46 | // --------------------------------------------------------------------------- | |
| 38 | 47 | // Configuration | |
| 39 | 48 | // --------------------------------------------------------------------------- | |
| @@ -249,7 +258,7 @@ pub fn prefix_at_depth(tag: &str, n: usize) -> Option<&str> { | |||
| 249 | 258 | } | |
| 250 | 259 | let mut dots_seen = 0; | |
| 251 | 260 | for (i, ch) in tag.bytes().enumerate() { | |
| 252 | - | if ch == SEPARATOR as u8 { | |
| 261 | + | if ch == SEPARATOR_BYTE { | |
| 253 | 262 | dots_seen += 1; | |
| 254 | 263 | if dots_seen == n { | |
| 255 | 264 | return Some(&tag[..i]); | |
| @@ -297,7 +306,7 @@ pub fn ancestors(tag: &str) -> Vec<&str> { | |||
| 297 | 306 | pub fn is_ancestor_of(ancestor: &str, descendant: &str) -> bool { | |
| 298 | 307 | descendant.len() > ancestor.len() | |
| 299 | 308 | && descendant.starts_with(ancestor) | |
| 300 | - | && descendant.as_bytes()[ancestor.len()] == SEPARATOR as u8 | |
| 309 | + | && descendant.as_bytes()[ancestor.len()] == SEPARATOR_BYTE | |
| 301 | 310 | } | |
| 302 | 311 | ||
| 303 | 312 | /// Find the longest common ancestor of two tags, or `None` if they share no prefix. | |
| @@ -315,7 +324,7 @@ pub fn common_ancestor<'a>(a: &'a str, b: &str) -> Option<&'a str> { | |||
| 315 | 324 | if ca != cb { | |
| 316 | 325 | break; | |
| 317 | 326 | } | |
| 318 | - | if ca == SEPARATOR as u8 { | |
| 327 | + | if ca == SEPARATOR_BYTE { | |
| 319 | 328 | last_sep = Some(i); | |
| 320 | 329 | } | |
| 321 | 330 | } | |
| @@ -327,7 +336,7 @@ pub fn common_ancestor<'a>(a: &'a str, b: &str) -> Option<&'a str> { | |||
| 327 | 336 | && (min_len == a.len() || min_len == b.len()) | |
| 328 | 337 | { | |
| 329 | 338 | let longer = if a.len() > b.len() { a } else { b }; | |
| 330 | - | if longer.as_bytes()[min_len] == SEPARATOR as u8 { | |
| 339 | + | if longer.as_bytes()[min_len] == SEPARATOR_BYTE { | |
| 331 | 340 | return Some(&a[..min_len]); | |
| 332 | 341 | } | |
| 333 | 342 | } | |
| @@ -422,7 +431,7 @@ pub fn children_at_prefix(prefix: &str, tags: &[impl AsRef<str>]) -> Vec<String> | |||
| 422 | 431 | } else { | |
| 423 | 432 | tag.starts_with(prefix) | |
| 424 | 433 | && tag.len() > prefix.len() | |
| 425 | - | && tag.as_bytes()[prefix.len()] == SEPARATOR as u8 | |
| 434 | + | && tag.as_bytes()[prefix.len()] == SEPARATOR_BYTE | |
| 426 | 435 | }; | |
| 427 | 436 | if !matches || tag.len() <= strip_len { | |
| 428 | 437 | continue; | |
| @@ -455,7 +464,7 @@ pub fn subtree<'a>(prefix: &str, tags: &'a [impl AsRef<str>]) -> Vec<&'a str> { | |||
| 455 | 464 | *tag == prefix | |
| 456 | 465 | || (tag.starts_with(prefix) | |
| 457 | 466 | && tag.len() > prefix.len() | |
| 458 | - | && tag.as_bytes()[prefix.len()] == SEPARATOR as u8) | |
| 467 | + | && tag.as_bytes()[prefix.len()] == SEPARATOR_BYTE) | |
| 459 | 468 | }) | |
| 460 | 469 | .collect() | |
| 461 | 470 | } |