pub fn drain_complete_lines(buf: &mut Vec<u8>) -> Vec<String> {
let mut lines = Vec::new();
while let Some(newline_pos) = buf.iter().position(|&b| b == b'\n') {
let line_bytes: Vec<u8> = buf.drain(..=newline_pos).collect();
let content = &line_bytes[..line_bytes.len() - 1];
lines.push(String::from_utf8_lossy(content).into_owned());
}
lines
}
#[cfg(test)]
mod tests {
use super::drain_complete_lines;
#[test]
fn drain_empty_buf() {
let mut buf: Vec<u8> = Vec::new();
assert!(drain_complete_lines(&mut buf).is_empty());
assert!(buf.is_empty());
}
#[test]
fn drain_without_newline_keeps_buf_intact() {
let mut buf = b"partial".to_vec();
assert!(drain_complete_lines(&mut buf).is_empty());
assert_eq!(buf, b"partial");
}
#[test]
fn drain_yields_complete_lines_and_keeps_tail() {
let mut buf = b"first\nsecond\ntail".to_vec();
let lines = drain_complete_lines(&mut buf);
assert_eq!(lines, vec!["first".to_string(), "second".to_string()]);
assert_eq!(buf, b"tail");
}
#[test]
fn drain_preserves_codepoints_across_chunks() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(b"hello ");
buf.extend_from_slice(&[0xE4, 0xBD]);
assert!(drain_complete_lines(&mut buf).is_empty());
buf.extend_from_slice(&[0xA0]);
buf.extend_from_slice("好\n".as_bytes());
let lines = drain_complete_lines(&mut buf);
assert_eq!(lines, vec!["hello 你好".to_string()]);
assert!(buf.is_empty());
}
}