pub fn floor_char_boundary(s: &str, max: usize) -> usize {
let mut end = max.min(s.len());
while !s.is_char_boundary(end) {
end -= 1;
}
end
}
#[expect(
clippy::string_slice,
reason = "floor_char_boundary returns a char boundary by construction - this is the one raw \
slice the rest of the workspace defers to"
)]
pub fn truncate_at_boundary(s: &str, max: usize) -> &str {
&s[..floor_char_boundary(s, max)]
}
pub fn estimate_tokens(s: &str) -> usize {
s.len().div_ceil(4)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn estimate_tokens_rounds_up_and_never_zero_for_nonempty() {
assert_eq!(estimate_tokens(""), 0);
assert_eq!(estimate_tokens("abc"), 1);
assert_eq!(estimate_tokens("abcd"), 1);
assert_eq!(estimate_tokens("abcde"), 2);
assert_eq!(estimate_tokens("\u{65e5}"), 1);
}
#[test]
fn floor_char_boundary_clamps_and_walks_back() {
assert_eq!(floor_char_boundary("abc", 99), 3);
assert_eq!(floor_char_boundary("abc", 2), 2);
assert_eq!(floor_char_boundary("日本語", 0), 0);
let s = "abc🎉";
assert_eq!(floor_char_boundary(s, 4), 3);
assert_eq!(floor_char_boundary(s, 6), 3);
assert_eq!(floor_char_boundary(s, 7), 7);
assert_eq!(floor_char_boundary("🎉abc", 2), 0);
assert_eq!(floor_char_boundary("", 5), 0);
}
#[test]
fn truncate_at_boundary_never_splits_a_character() {
assert_eq!(truncate_at_boundary("abc", 99), "abc");
assert_eq!(truncate_at_boundary("abcdef", 3), "abc");
assert_eq!(truncate_at_boundary("abc🎉def", 5), "abc");
assert_eq!(truncate_at_boundary("🎉abc", 2), "");
assert_eq!(truncate_at_boundary("", 5), "");
}
}