pub fn word_tokens(text: &str) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut current = String::new();
let mut prev_lower = false;
let flush = |buf: &mut String, out: &mut Vec<String>| {
if !buf.is_empty() {
out.push(std::mem::take(buf));
}
};
for ch in text.chars() {
if ch.is_alphanumeric() {
if ch.is_uppercase() && prev_lower {
flush(&mut current, &mut out);
}
for lc in ch.to_lowercase() {
current.push(lc);
}
prev_lower = ch.is_lowercase() || ch.is_numeric();
} else {
flush(&mut current, &mut out);
prev_lower = false;
}
}
flush(&mut current, &mut out);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splits_camel_and_snake() {
assert_eq!(
word_tokens("getUserByID get_user_by_id"),
vec!["get", "user", "by", "id", "get", "user", "by", "id"]
);
}
#[test]
fn drops_punctuation_keeps_words() {
assert_eq!(
word_tokens("rate-limit middleware (retry)"),
vec!["rate", "limit", "middleware", "retry"]
);
}
}