#[derive(Debug)]
pub struct LiveOutput {
text: String,
limit: usize,
}
impl LiveOutput {
pub fn new(limit: usize) -> LiveOutput {
LiveOutput {
text: String::new(),
limit,
}
}
pub fn push(&mut self, chunk: &str) {
self.text.push_str(chunk);
if self.text.len() > self.limit {
self.text = tail(&self.text, self.limit).to_string();
}
}
pub fn clear(&mut self) {
self.text.clear();
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub fn tail_lines(&self, count: usize) -> Vec<&str> {
let text = self.text.strip_suffix('\n').unwrap_or(&self.text);
if text.is_empty() {
return Vec::new();
}
let lines: Vec<&str> = text.lines().collect();
lines[lines.len().saturating_sub(count)..].to_vec()
}
}
pub fn tail(text: &str, max_bytes: usize) -> &str {
if text.len() <= max_bytes {
return text;
}
let mut start = text.len() - max_bytes;
while start < text.len() && !text.is_char_boundary(start) {
start += 1;
}
&text[start..]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shows_the_last_lines() {
let mut live = LiveOutput::new(1024);
live.push("one\ntwo\n");
live.push("three\nfour\n");
assert_eq!(live.tail_lines(3), vec!["two", "three", "four"]);
}
#[test]
fn asking_for_more_lines_than_there_are_gives_what_there_is() {
let mut live = LiveOutput::new(1024);
live.push("only\n");
assert_eq!(live.tail_lines(3), vec!["only"]);
}
#[test]
fn a_trailing_newline_is_not_a_line() {
let mut live = LiveOutput::new(1024);
live.push("one\ntwo\nthree\n");
assert_eq!(live.tail_lines(3), vec!["one", "two", "three"]);
}
#[test]
fn a_line_still_being_written_is_shown() {
let mut live = LiveOutput::new(1024);
live.push("done\nworking");
assert_eq!(live.tail_lines(2), vec!["done", "working"]);
}
#[test]
fn nothing_printed_yet_is_no_lines() {
let live = LiveOutput::new(1024);
assert!(live.is_empty());
assert!(live.tail_lines(3).is_empty());
}
#[test]
fn clearing_starts_the_next_run_empty() {
let mut live = LiveOutput::new(1024);
live.push("from the last run\n");
live.clear();
assert!(live.is_empty());
}
#[test]
fn a_command_that_never_stops_printing_does_not_grow_the_buffer() {
let mut live = LiveOutput::new(64);
for index in 0..1000 {
live.push(&format!("line {index}\n"));
}
assert!(live.text.len() <= 64, "buffer grew to {}", live.text.len());
assert_eq!(live.tail_lines(1), vec!["line 999"]);
}
#[test]
fn dropping_the_oldest_text_never_splits_a_character() {
let mut live = LiveOutput::new(16);
for _ in 0..20 {
live.push("行\n");
}
assert!(live.text.ends_with("行\n"));
}
#[test]
fn text_shorter_than_the_limit_is_left_alone() {
assert_eq!(tail("short", 64), "short");
}
}