str_utils/
replace_newlines_with_space.rs

1use alloc::{borrow::Cow, vec::Vec};
2
3/// To extend `str` and `Cow<str>` to have `replace_newlines_with_space` method.
4///
5/// This can be useful when you need to normalize multiline strings into a single line for logging, database storage, or display purposes.
6pub trait ReplaceNewlinesWithSpace<'a> {
7    /// Returns a `Cow<str>` where all newline sequences are replaced with a space.
8    ///
9    /// Replaces Windows-style newlines (`\r\n`), old Mac-style (`\r`), and Unix-style (`\n`).
10    fn replace_newlines_with_space(self) -> Cow<'a, str>;
11}
12
13impl<'a> ReplaceNewlinesWithSpace<'a> for &'a str {
14    fn replace_newlines_with_space(self) -> Cow<'a, str> {
15        let s = self;
16        let bytes = s.as_bytes();
17        let length = bytes.len();
18
19        let mut p = 0;
20
21        let first_len = loop {
22            if p == length {
23                return Cow::from(s);
24            }
25
26            let e = bytes[p];
27
28            match e {
29                b'\r' => {
30                    if p < length - 1 && bytes[p + 1] == b'\n' {
31                        break 2; // CRLF
32                    } else {
33                        break 1; // CR
34                    }
35                },
36                b'\n' => {
37                    break 1; // LF
38                },
39                _ => (),
40            }
41
42            p += 1;
43        };
44
45        let mut new_v = Vec::with_capacity(bytes.len());
46
47        new_v.extend_from_slice(&bytes[..p]);
48        new_v.push(b' ');
49
50        p += first_len;
51
52        let mut start = p;
53
54        loop {
55            if p == length {
56                break;
57            }
58
59            let e = bytes[p];
60
61            match e {
62                b'\r' => {
63                    new_v.extend_from_slice(&bytes[start..p]);
64
65                    if p < length - 1 && bytes[p + 1] == b'\n' {
66                        // CRLF
67                        p += 1;
68                        start = p + 1;
69                    } else {
70                        // CR
71                        start = p + 1;
72                    }
73
74                    new_v.push(b' ');
75                },
76                b'\n' => {
77                    // LF
78                    new_v.extend_from_slice(&bytes[start..p]);
79                    start = p + 1;
80
81                    new_v.push(b' ');
82                },
83                _ => (),
84            }
85
86            p += 1;
87        }
88
89        new_v.extend_from_slice(&bytes[start..p]);
90
91        Cow::from(unsafe { String::from_utf8_unchecked(new_v) })
92    }
93}
94
95impl<'a> ReplaceNewlinesWithSpace<'a> for Cow<'a, str> {
96    #[inline]
97    fn replace_newlines_with_space(self) -> Cow<'a, str> {
98        match self {
99            Cow::Borrowed(s) => s.replace_newlines_with_space(),
100            Cow::Owned(s) => {
101                match s.replace_newlines_with_space() {
102                    Cow::Borrowed(_) => {
103                        // it changes nothing
104                        // if there were any characters that needed to be replaced, it had to be `Cow::Owned`
105                        Cow::Owned(s)
106                    },
107                    Cow::Owned(s) => Cow::Owned(s),
108                }
109            },
110        }
111    }
112}