From 0cc779c73360199b661246fa343101ef07bfece1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 9 May 2021 21:36:39 -0400 Subject: [PATCH] tail: simplify unbounded_tail() function Refactor common code out of two branches of the `unbounded_tail()` function into a new `unbounded_tail_collect()` helper function, that collects from an iterator into a `VecDeque` and keeps either the last `n` elements or all but the first `n` elements. This commit also adds a new struct, `RingBuffer`, in a new module, `ringbuffer.rs`, to be responsible for keeping the last `n` elements of an iterator. --- src/uu/tail/src/ringbuffer.rs | 61 ++++++++++++++++++++++ src/uu/tail/src/tail.rs | 98 +++++++++++++---------------------- 2 files changed, 96 insertions(+), 63 deletions(-) create mode 100644 src/uu/tail/src/ringbuffer.rs diff --git a/src/uu/tail/src/ringbuffer.rs b/src/uu/tail/src/ringbuffer.rs new file mode 100644 index 000000000..86483b8ed --- /dev/null +++ b/src/uu/tail/src/ringbuffer.rs @@ -0,0 +1,61 @@ +//! A fixed-size ring buffer. +use std::collections::VecDeque; + +/// A fixed-size ring buffer backed by a `VecDeque`. +/// +/// If the ring buffer is not full, then calling the [`push_back`] +/// method appends elements, as in a [`VecDeque`]. If the ring buffer +/// is full, then calling [`push_back`] removes the element at the +/// front of the buffer (in a first-in, first-out manner) before +/// appending the new element to the back of the buffer. +/// +/// Use [`from_iter`] to take the last `size` elements from an +/// iterator. +/// +/// # Examples +/// +/// After exceeding the size limit, the oldest elements are dropped in +/// favor of the newest element: +/// +/// ```rust,ignore +/// let buffer: RingBuffer = RingBuffer::new(2); +/// buffer.push_back(0); +/// buffer.push_back(1); +/// buffer.push_back(2); +/// assert_eq!(vec![1, 2], buffer.data); +/// ``` +/// +/// Take the last `n` elements from an iterator: +/// +/// ```rust,ignore +/// let iter = vec![0, 1, 2, 3].iter(); +/// assert_eq!(vec![2, 3], RingBuffer::from_iter(iter, 2).data); +/// ``` +pub struct RingBuffer { + pub data: VecDeque, + size: usize, +} + +impl RingBuffer { + pub fn new(size: usize) -> RingBuffer { + RingBuffer { + data: VecDeque::new(), + size, + } + } + + pub fn from_iter(iter: impl Iterator, size: usize) -> RingBuffer { + let mut ringbuf = RingBuffer::new(size); + for value in iter { + ringbuf.push_back(value); + } + ringbuf + } + + pub fn push_back(&mut self, value: T) { + if self.size <= self.data.len() { + self.data.pop_front(); + } + self.data.push_back(value) + } +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index fec88e841..0a3ff778d 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -16,6 +16,8 @@ extern crate clap; extern crate uucore; mod platform; +mod ringbuffer; +use ringbuffer::RingBuffer; use clap::{App, Arg}; use std::collections::VecDeque; @@ -482,71 +484,46 @@ fn bounded_tail(mut file: &File, settings: &Settings) { } } +/// Collect the last elements of an iterator into a `VecDeque`. +/// +/// This function returns a [`VecDeque`] containing either the last +/// `count` elements of `iter`, an [`Iterator`] over [`Result`] +/// instances, or all but the first `count` elements of `iter`. If +/// `beginning` is `true`, then all but the first `count` elements are +/// returned. +/// +/// # Panics +/// +/// If any element of `iter` is an [`Err`], then this function panics. +fn unbounded_tail_collect( + iter: impl Iterator>, + count: u64, + beginning: bool, +) -> VecDeque +where + E: fmt::Debug, +{ + if beginning { + iter.skip(count as usize).map(|r| r.unwrap()).collect() + } else { + RingBuffer::from_iter(iter.map(|r| r.unwrap()), count as usize).data + } +} + fn unbounded_tail(reader: &mut BufReader, settings: &Settings) { // Read through each line/char and store them in a ringbuffer that always // contains count lines/chars. When reaching the end of file, output the // data in the ringbuf. match settings.mode { - FilterMode::Lines(mut count, _delimiter) => { - let mut ringbuf: VecDeque = VecDeque::new(); - let mut skip = if settings.beginning { - let temp = count; - count = ::std::u64::MAX; - temp - 1 - } else { - 0 - }; - loop { - let mut datum = String::new(); - match reader.read_line(&mut datum) { - Ok(0) => break, - Ok(_) => { - if skip > 0 { - skip -= 1; - } else { - if count <= ringbuf.len() as u64 { - ringbuf.pop_front(); - } - ringbuf.push_back(datum); - } - } - Err(err) => panic!("{}", err), - } - } - let mut stdout = stdout(); - for datum in &ringbuf { - print_string(&mut stdout, datum); + FilterMode::Lines(count, _) => { + for line in unbounded_tail_collect(reader.lines(), count, settings.beginning) { + println!("{}", line); } } - FilterMode::Bytes(mut count) => { - let mut ringbuf: VecDeque = VecDeque::new(); - let mut skip = if settings.beginning { - let temp = count; - count = ::std::u64::MAX; - temp - 1 - } else { - 0 - }; - loop { - let mut datum = [0; 1]; - match reader.read(&mut datum) { - Ok(0) => break, - Ok(_) => { - if skip > 0 { - skip -= 1; - } else { - if count <= ringbuf.len() as u64 { - ringbuf.pop_front(); - } - ringbuf.push_back(datum[0]); - } - } - Err(err) => panic!("{}", err), - } - } - let mut stdout = stdout(); - for datum in &ringbuf { - print_byte(&mut stdout, *datum); + FilterMode::Bytes(count) => { + for byte in unbounded_tail_collect(reader.bytes(), count, settings.beginning) { + let mut stdout = stdout(); + print_byte(&mut stdout, byte); } } } @@ -562,8 +539,3 @@ fn print_byte(stdout: &mut T, ch: u8) { crash!(1, "{}", err); } } - -#[inline] -fn print_string(_: &mut T, s: &str) { - print!("{}", s); -}