1pub fn excerpt(body: &str, max_chars: usize) -> String {
15 let cleaned: String = body
16 .chars()
17 .map(|c| if c.is_control() { ' ' } else { c })
18 .collect();
19 let mut out = String::with_capacity(max_chars);
20 let mut last_was_space = false;
21 for c in cleaned.trim().chars() {
22 if out.chars().count() >= max_chars {
23 out.push('…');
24 break;
25 }
26 if c == ' ' {
27 if last_was_space {
28 continue;
29 }
30 last_was_space = true;
31 } else {
32 last_was_space = false;
33 }
34 out.push(c);
35 }
36 if out.is_empty() {
37 return "<nothing>".to_owned();
41 }
42 out
43}
44
45#[cfg(test)]
46mod tests {
47 use super::excerpt;
48
49 #[test]
50 fn control_characters_are_replaced_not_passed_through() {
51 let out = excerpt("a\u{1b}[31mred\u{7}b", 100);
54 assert!(!out.chars().any(char::is_control), "{out:?}");
55 assert!(out.contains("red"), "{out:?}");
56 }
57
58 #[test]
59 fn text_within_the_limit_is_returned_intact_without_a_marker() {
60 assert_eq!(excerpt("short", 100), "short");
61 }
62
63 #[test]
64 fn text_over_the_limit_is_cut_and_marked() {
65 let out = excerpt(&"x".repeat(50), 10);
66 assert_eq!(out.chars().count(), 11, "{out:?}");
67 assert!(out.ends_with('…'), "{out:?}");
68 }
69
70 #[test]
71 fn runs_of_space_collapse_and_the_edges_are_trimmed() {
72 assert_eq!(excerpt(" a\t\t\tb ", 100), "a b");
73 }
74
75 #[test]
76 fn empty_input_names_itself_rather_than_quoting_nothing() {
77 assert_eq!(excerpt(" ", 100), "<nothing>");
78 }
79
80 #[test]
81 fn the_limit_counts_characters_not_bytes() {
82 let out = excerpt(&"—".repeat(10), 10);
85 assert_eq!(out.chars().count(), 10, "{out:?}");
86 assert!(!out.ends_with('…'), "{out:?}");
87 }
88}