pub fn first_stop(text: &str, stops: &[String]) -> Option<(usize, String)> {
let mut best: Option<(usize, String)> = None;
for s in stops {
if s.is_empty() {
continue;
}
if let Some(pos) = text.find(s.as_str()) {
let better = match &best {
None => true,
Some((b, bs)) => pos < *b || (pos == *b && s.len() > bs.len()),
};
if better {
best = Some((pos, s.clone()));
}
}
}
best
}
pub fn safe_stream_end(text: &str, stops: &[String]) -> usize {
let maxstop = stops.iter().map(String::len).max().unwrap_or(0);
if maxstop <= 1 {
return text.len();
}
let mut end = text.len().saturating_sub(maxstop - 1);
while end > 0 && !text.is_char_boundary(end) {
end -= 1;
}
end
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| x.to_string()).collect()
}
#[test]
fn should_find_earliest_stop_and_report_its_start() {
let (pos, which) = first_stop("hello END world", &s(&["END"])).expect("match");
assert_eq!(pos, 6);
assert_eq!(which, "END");
assert_eq!(&"hello END world"[..pos], "hello ");
}
#[test]
fn should_pick_the_earliest_when_multiple_stops_match() {
let (pos, which) = first_stop("a STOP b HALT c", &s(&["HALT", "STOP"])).expect("match");
assert_eq!(pos, 2);
assert_eq!(which, "STOP");
}
#[test]
fn should_ignore_empty_stop_strings() {
assert!(first_stop("anything", &s(&[""])).is_none());
}
#[test]
fn should_return_none_when_no_stop_present() {
assert!(first_stop("nothing here", &s(&["END", "STOP"])).is_none());
}
#[test]
fn should_truncate_on_a_utf8_boundary() {
let text = "café STOP";
let (pos, _) = first_stop(text, &s(&["STOP"])).expect("match");
assert!(text.is_char_boundary(pos));
assert_eq!(&text[..pos], "café ");
}
#[test]
fn should_hold_back_potential_stop_prefix_when_streaming() {
let end = safe_stream_end("hello STO", &s(&["STOP"]));
assert_eq!(&"hello STO"[..end], "hello ");
}
#[test]
fn should_stream_everything_when_no_stops() {
assert_eq!(safe_stream_end("hello world", &[]), "hello world".len());
}
#[test]
fn safe_stream_end_never_splits_a_codepoint() {
let text = "abcé";
let end = safe_stream_end(text, &s(&["xé"]));
assert!(text.is_char_boundary(end));
}
}