pub fn normalize(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut consecutive_newlines = 0u32;
for ch in input.chars() {
if ch == '\n' {
consecutive_newlines += 1;
if consecutive_newlines <= 2 {
result.push('\n');
}
} else {
consecutive_newlines = 0;
result.push(ch);
}
}
let lines: Vec<&str> = result.lines().map(|l| l.trim_end()).collect();
let joined = lines.join("\n");
joined.trim().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collapse_excessive_newlines() {
assert_eq!(normalize("a\n\n\n\nb"), "a\n\nb");
}
#[test]
fn preserve_single_blank_line() {
assert_eq!(normalize("a\n\nb"), "a\n\nb");
}
#[test]
fn trim_trailing_whitespace() {
assert_eq!(normalize("hello \nworld "), "hello\nworld");
}
#[test]
fn trim_outer_whitespace() {
assert_eq!(normalize("\n\nhello\n\n"), "hello");
}
#[test]
fn empty_input() {
assert_eq!(normalize(""), "");
}
}