use std::io::{self, BufRead, BufReader, Read};
pub struct LineBuffer {
reader: BufReader<Box<dyn Read>>,
lines: Vec<String>,
eof: bool,
final_newline: bool,
}
impl LineBuffer {
pub fn new<R: Read + 'static>(source: R) -> Self {
Self {
reader: BufReader::new(Box::new(source)),
lines: Vec::new(),
eof: false,
final_newline: true,
}
}
pub fn fill_to(&mut self, target_lines: usize) -> io::Result<()> {
while !self.eof && self.lines.len() < target_lines {
let mut s = String::new();
let n = self.reader.read_line(&mut s)?;
if n == 0 {
self.eof = true;
break;
}
if s.ends_with('\n') {
s.pop();
} else {
self.final_newline = false;
self.eof = true;
}
self.lines.push(s);
}
Ok(())
}
pub fn fill_all(&mut self) -> io::Result<()> {
self.fill_to(usize::MAX)
}
pub fn lines(&self) -> &[String] {
&self.lines
}
pub fn len(&self) -> usize {
self.lines.len()
}
pub fn eof(&self) -> bool {
self.eof
}
pub fn final_newline(&self) -> bool {
self.final_newline
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fill_to_stops_at_target() {
let input = b"a\nb\nc\nd\ne\n".to_vec();
let mut buf = LineBuffer::new(io::Cursor::new(input));
buf.fill_to(3).unwrap();
assert_eq!(buf.len(), 3);
assert!(!buf.eof());
assert_eq!(buf.lines(), &["a", "b", "c"]);
}
#[test]
fn fill_to_stops_at_eof() {
let input = b"a\nb\n".to_vec();
let mut buf = LineBuffer::new(io::Cursor::new(input));
buf.fill_to(10).unwrap();
assert_eq!(buf.len(), 2);
assert!(buf.eof());
assert!(buf.final_newline());
}
#[test]
fn fill_continues_from_where_it_left_off() {
let input = b"a\nb\nc\nd\n".to_vec();
let mut buf = LineBuffer::new(io::Cursor::new(input));
buf.fill_to(2).unwrap();
assert_eq!(buf.len(), 2);
assert!(!buf.eof());
buf.fill_all().unwrap();
assert_eq!(buf.len(), 4);
assert!(buf.eof());
}
#[test]
fn detects_missing_final_newline() {
let input = b"hello".to_vec();
let mut buf = LineBuffer::new(io::Cursor::new(input));
buf.fill_all().unwrap();
assert_eq!(buf.lines(), &["hello"]);
assert!(buf.eof());
assert!(!buf.final_newline());
}
}