const SHORTEST: usize = 17;
const LONGEST: usize = 36;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Candidate<'a> {
pub(crate) text: &'a str,
pub(crate) start: usize,
}
const fn is_run_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'-'
}
pub(crate) fn candidates(text: &str) -> Vec<Candidate<'_>> {
let bytes = text.as_bytes();
let mut out = Vec::new();
let mut at = 0;
while at < bytes.len() {
if !is_run_byte(bytes[at]) {
at += 1;
continue;
}
let start = at;
while at < bytes.len() && is_run_byte(bytes[at]) {
at += 1;
}
if (SHORTEST..=LONGEST).contains(&(at - start)) {
out.push(Candidate {
text: &text[start..at],
start,
});
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn texts(input: &str) -> Vec<&str> {
candidates(input)
.into_iter()
.map(|candidate| candidate.text)
.collect()
}
const UUID: &str = "550e8400-e29b-41d4-a716-446655440000";
#[test]
fn a_hyphenated_uuid_is_one_run() {
assert_eq!(texts(UUID), [UUID]);
}
#[test]
fn quotes_and_punctuation_bound_a_run() {
assert_eq!(texts(&format!("{{\"id\":\"{UUID}\"}}")), [UUID]);
assert_eq!(texts(&format!("the id {UUID}, then more")), [UUID]);
assert_eq!(texts(&format!("urn:uuid:{UUID}")), [UUID]);
}
#[test]
fn a_prefixed_identifier_is_one_run_and_matches_nothing() {
assert!(
texts(&format!("user_{UUID}")).is_empty(),
"41 characters is longer than any kind"
);
assert_eq!(
texts("x-6a7bb780a1b2c3d4e5f60718"),
["x-6a7bb780a1b2c3d4e5f60718"]
);
}
#[test]
fn the_offset_points_at_the_start_of_the_run() {
let input = format!("id = {UUID}");
let found = candidates(&input);
assert_eq!(found[0].start, 5);
assert_eq!(&input[found[0].start..], UUID);
}
#[test]
fn runs_come_back_in_document_order() {
let input = format!("{UUID}\n01KZSM9K00ABCDEFGH12345678\n");
assert_eq!(texts(&input), [UUID, "01KZSM9K00ABCDEFGH12345678"]);
}
#[test]
fn runs_outside_every_kinds_length_are_not_candidates() {
assert!(texts("the quick brown fox jumped over").is_empty());
assert!(texts(&"a".repeat(LONGEST + 1)).is_empty());
assert_eq!(texts(&"a".repeat(LONGEST)).len(), 1);
assert_eq!(texts(&"a".repeat(SHORTEST)).len(), 1);
assert!(texts(&"a".repeat(SHORTEST - 1)).is_empty());
}
#[test]
fn a_non_ascii_byte_ends_a_run() {
let input = format!("café{UUID}");
assert_eq!(texts(&input), [UUID]);
}
#[test]
fn an_empty_document_yields_nothing() {
assert!(texts("").is_empty());
}
}