slopguard_core/
sanitize.rs1use std::borrow::Cow;
5
6pub fn sanitize_control(s: &str) -> Cow<'_, str> {
12 let needs_escape = |c: char| c.is_control() && c != '\n' && c != '\t';
13 if !s.chars().any(needs_escape) {
14 return Cow::Borrowed(s);
15 }
16 let mut out = String::with_capacity(s.len() + 8);
17 for c in s.chars() {
18 if needs_escape(c) {
19 out.push_str(&format!("\\x{:02x}", u32::from(c)));
20 } else {
21 out.push(c);
22 }
23 }
24 Cow::Owned(out)
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 #[test]
32 fn escapes_esc() {
33 assert_eq!(sanitize_control("a\x1b[2Jb"), "a\\x1b[2Jb");
34 }
35
36 #[test]
37 fn keeps_newline_and_tab() {
38 let out = sanitize_control("a\n\tb");
39 assert_eq!(out, "a\n\tb");
40 assert!(matches!(out, Cow::Borrowed(_)));
41 }
42
43 #[test]
44 fn escapes_carriage_return() {
45 assert_eq!(sanitize_control("a\rb"), "a\\x0db");
46 }
47
48 #[test]
49 fn escapes_c1() {
50 assert_eq!(sanitize_control("a\u{9b}b"), "a\\x9bb");
51 }
52
53 #[test]
54 fn leaves_plain_and_non_ascii_borrowed() {
55 for s in ["plain", "caf\u{e9}"] {
56 let out = sanitize_control(s);
57 assert_eq!(out, s);
58 assert!(matches!(out, Cow::Borrowed(_)));
59 }
60 }
61}