mailparsing/
textwrap.rs

1pub fn wrap(value: &str) -> String {
2    const SOFT_WIDTH: usize = 75;
3    const HARD_WIDTH: usize = 1000;
4    wrap_impl(value, SOFT_WIDTH, HARD_WIDTH)
5}
6
7/// We can't use textwrap::fill here because it will prefer to break
8/// a line rather than finding stuff that fits.  We use a simple
9/// algorithm that tries to fill up to the desired width, allowing
10/// for overflow if there is a word that is too long to fit in
11/// the header, but breaking after a hard limit threshold.
12fn wrap_impl(value: &str, soft_width: usize, hard_width: usize) -> String {
13    let mut result = String::new();
14    let mut line = String::new();
15
16    for word in value.split_ascii_whitespace() {
17        if line.len() + word.len() < soft_width {
18            if !line.is_empty() {
19                line.push(' ');
20            }
21            line.push_str(word);
22            continue;
23        }
24
25        // Need to wrap.
26
27        // Accumulate line so far, if any
28        if !line.is_empty() {
29            if !result.is_empty() {
30                // There's an existing line, start a new one, indented
31                result.push('\t');
32            }
33            result.push_str(&line);
34            result.push_str("\r\n");
35            line.clear();
36        }
37
38        // build out a line from the characters of this one
39        if word.len() <= hard_width {
40            line.push_str(word);
41        } else {
42            for c in word.chars() {
43                line.push(c);
44                if line.len() >= hard_width {
45                    if !result.is_empty() {
46                        result.push('\t');
47                    }
48                    result.push_str(&line);
49                    result.push_str("\r\n");
50                    line.clear();
51                    continue;
52                }
53            }
54        }
55    }
56
57    if !line.is_empty() {
58        if !result.is_empty() {
59            result.push('\t');
60        }
61        result.push_str(&line);
62    }
63
64    result
65}
66
67#[cfg(test)]
68mod test {
69    use super::*;
70
71    #[test]
72    fn wrapping() {
73        for (input, expect) in [
74            ("foo", "foo"),
75            ("hi there", "hi there"),
76            ("hello world", "hello\r\n\tworld"),
77            (
78                "hello world foo bar baz woot woot",
79                "hello\r\n\tworld foo\r\n\tbar baz\r\n\twoot woot",
80            ),
81            (
82                "hi there breakmepleaseIamtoolong",
83                "hi there\r\n\tbreakmepleaseIa\r\n\tmtoolong",
84            ),
85        ] {
86            let wrapped = wrap_impl(input, 10, 15);
87            k9::assert_equal!(
88                wrapped,
89                expect,
90                "input: '{input}' should produce '{expect}'"
91            );
92        }
93    }
94}