1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

head: add abstractions for "all but last n lines"

Add some abstractions to simplify the `rbuf_but_last_n_lines()`
function, which implements the "take all but the last `n` lines"
functionality of the `head` program. This commit adds

- `RingBuffer`, a fixed-size ring buffer,
- `ZLines`, an iterator over zero-terminated "lines",
- `TakeAllBut`, an iterator over all but the last `n` elements of an
  iterator.

These three together make the implementation of
`rbuf_but_last_n_lines()` concise.
This commit is contained in:
Jeffrey Finkelstein 2021-05-11 23:48:06 -04:00
parent 70e65c419f
commit bc9db289e8
12 changed files with 333 additions and 91 deletions

View file

@ -16,7 +16,7 @@ path = "src/head.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]

View file

@ -27,8 +27,12 @@ mod options {
pub const ZERO_NAME: &str = "ZERO"; pub const ZERO_NAME: &str = "ZERO";
pub const FILES_NAME: &str = "FILE"; pub const FILES_NAME: &str = "FILE";
} }
mod lines;
mod parse; mod parse;
mod split; mod split;
mod take;
use lines::zlines;
use take::take_all_but;
fn app<'a>() -> App<'a, 'a> { fn app<'a>() -> App<'a, 'a> {
App::new(executable!()) App::new(executable!())
@ -293,36 +297,22 @@ fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io
} }
fn rbuf_but_last_n_lines( fn rbuf_but_last_n_lines(
input: &mut impl std::io::BufRead, input: impl std::io::BufRead,
n: usize, n: usize,
zero: bool, zero: bool,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
if n == 0 { if zero {
//prints everything let stdout = std::io::stdout();
return rbuf_n_bytes(input, std::usize::MAX); let mut stdout = stdout.lock();
for bytes in take_all_but(zlines(input), n) {
stdout.write_all(&bytes?)?;
}
} else {
for line in take_all_but(input.lines(), n) {
println!("{}", line?);
}
} }
let mut ringbuf = vec![Vec::new(); n]; Ok(())
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut line = Vec::new();
let mut lines = 0usize;
split::walk_lines(input, zero, |e| match e {
split::Event::Data(dat) => {
line.extend_from_slice(dat);
Ok(true)
}
split::Event::Line => {
if lines < n {
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
lines += 1;
} else {
stdout.write_all(&ringbuf[0])?;
ringbuf.rotate_left(1);
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
}
Ok(true)
}
})
} }
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {

73
src/uu/head/src/lines.rs Normal file
View file

@ -0,0 +1,73 @@
//! Iterate over zero-terminated lines.
use std::io::BufRead;
/// The zero byte, representing the null character.
const ZERO: u8 = 0;
/// Returns an iterator over the lines of the given reader.
///
/// The iterator returned from this function will yield instances of
/// [`io::Result`]<[`Vec`]<[`u8`]>>, representing the bytes of the line
/// *including* the null character (with the possible exception of the
/// last line, which may not have one).
///
/// # Examples
///
/// ```rust,ignore
/// use std::io::Cursor;
///
/// let cursor = Cursor::new(b"x\0y\0z\0");
/// let mut iter = zlines(cursor).map(|l| l.unwrap());
/// assert_eq!(iter.next(), Some(b"x\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"y\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"z\0".to_vec()));
/// assert_eq!(iter.next(), None);
/// ```
pub fn zlines<B>(buf: B) -> ZLines<B> {
ZLines { buf }
}
/// An iterator over the zero-terminated lines of an instance of `BufRead`.
pub struct ZLines<B> {
buf: B,
}
impl<B: BufRead> Iterator for ZLines<B> {
type Item = std::io::Result<Vec<u8>>;
fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> {
let mut buf = Vec::new();
match self.buf.read_until(ZERO, &mut buf) {
Ok(0) => None,
Ok(_) => Some(Ok(buf)),
Err(e) => Some(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use crate::lines::zlines;
use std::io::Cursor;
#[test]
fn test_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z\0");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z\0".to_vec()));
assert_eq!(iter.next(), None);
}
#[test]
fn test_not_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z".to_vec()));
assert_eq!(iter.next(), None);
}
}

93
src/uu/head/src/take.rs Normal file
View file

@ -0,0 +1,93 @@
//! Take all but the last elements of an iterator.
use uucore::ringbuffer::RingBuffer;
/// Create an iterator over all but the last `n` elements of `iter`.
///
/// # Examples
///
/// ```rust,ignore
/// let data = [1, 2, 3, 4, 5];
/// let n = 2;
/// let mut iter = take_all_but(data.iter(), n);
/// assert_eq!(Some(4), iter.next());
/// assert_eq!(Some(5), iter.next());
/// assert_eq!(None, iter.next());
/// ```
pub fn take_all_but<I: Iterator>(iter: I, n: usize) -> TakeAllBut<I> {
TakeAllBut::new(iter, n)
}
/// An iterator that only iterates over the last elements of another iterator.
pub struct TakeAllBut<I: Iterator> {
iter: I,
buf: RingBuffer<<I as Iterator>::Item>,
}
impl<I: Iterator> TakeAllBut<I> {
pub fn new(mut iter: I, n: usize) -> TakeAllBut<I> {
// Create a new ring buffer and fill it up.
//
// If there are fewer than `n` elements in `iter`, then we
// exhaust the iterator so that whenever `TakeAllBut::next()` is
// called, it will return `None`, as expected.
let mut buf = RingBuffer::new(n);
for _ in 0..n {
let value = match iter.next() {
None => {
break;
}
Some(x) => x,
};
buf.push_back(value);
}
TakeAllBut { iter, buf }
}
}
impl<I: Iterator> Iterator for TakeAllBut<I>
where
I: Iterator,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<<I as Iterator>::Item> {
match self.iter.next() {
Some(value) => self.buf.push_back(value),
None => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::take::take_all_but;
#[test]
fn test_fewer_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 2);
assert_eq!(Some(&0), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn test_same_number_of_elements() {
let mut iter = take_all_but([0, 1].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_more_elements() {
let mut iter = take_all_but([0].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_zero_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 0);
assert_eq!(Some(&0), iter.next());
assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(None, iter.next());
}
}

View file

@ -17,7 +17,7 @@ path = "src/tail.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }

View file

@ -1,61 +0,0 @@
//! 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<u8> = 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<T> {
pub data: VecDeque<T>,
size: usize,
}
impl<T> RingBuffer<T> {
pub fn new(size: usize) -> RingBuffer<T> {
RingBuffer {
data: VecDeque::new(),
size,
}
}
pub fn from_iter(iter: impl Iterator<Item = T>, size: usize) -> RingBuffer<T> {
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)
}
}

View file

@ -17,9 +17,7 @@ extern crate uucore;
mod chunks; mod chunks;
mod platform; mod platform;
mod ringbuffer;
use chunks::ReverseChunks; use chunks::ReverseChunks;
use ringbuffer::RingBuffer;
use clap::{App, Arg}; use clap::{App, Arg};
use std::collections::VecDeque; use std::collections::VecDeque;
@ -30,6 +28,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path; use std::path::Path;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use uucore::ringbuffer::RingBuffer;
pub mod options { pub mod options {
pub mod verbosity { pub mod verbosity {

View file

@ -47,6 +47,7 @@ mode = ["libc"]
parse_time = [] parse_time = []
perms = ["libc"] perms = ["libc"]
process = ["libc"] process = ["libc"]
ringbuffer = []
signals = [] signals = []
utf8 = [] utf8 = []
utmpx = ["time", "libc"] utmpx = ["time", "libc"]

View file

@ -8,6 +8,8 @@ pub mod fs;
pub mod fsext; pub mod fsext;
#[cfg(feature = "parse_time")] #[cfg(feature = "parse_time")]
pub mod parse_time; pub mod parse_time;
#[cfg(feature = "ringbuffer")]
pub mod ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]
pub mod zero_copy; pub mod zero_copy;

View file

@ -0,0 +1,134 @@
//! 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 mut buffer: RingBuffer<u8> = 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 = [0, 1, 2].iter();
/// let actual = RingBuffer::from_iter(iter, 2).data;
/// let expected = VecDeque::from_iter([1, 2].iter());
/// assert_eq!(expected, actual);
/// ```
pub struct RingBuffer<T> {
pub data: VecDeque<T>,
size: usize,
}
impl<T> RingBuffer<T> {
pub fn new(size: usize) -> RingBuffer<T> {
RingBuffer {
data: VecDeque::new(),
size,
}
}
pub fn from_iter(iter: impl Iterator<Item = T>, size: usize) -> RingBuffer<T> {
let mut ringbuf = RingBuffer::new(size);
for value in iter {
ringbuf.push_back(value);
}
ringbuf
}
/// Append a value to the end of the ring buffer.
///
/// If the ring buffer is not full, this method return [`None`]. If
/// the ring buffer is full, appending a new element will cause the
/// oldest element to be evicted. In that case this method returns
/// that element, or `None`.
///
/// In the special case where the size limit is zero, each call to
/// this method with input `value` returns `Some(value)`, because
/// the input is immediately evicted.
///
/// # Examples
///
/// Appending an element when the buffer is full returns the oldest
/// element:
///
/// ```rust,ignore
/// let mut buf = RingBuffer::new(3);
/// assert_eq!(None, buf.push_back(0));
/// assert_eq!(None, buf.push_back(1));
/// assert_eq!(None, buf.push_back(2));
/// assert_eq!(Some(0), buf.push_back(3));
/// ```
///
/// If the size limit is zero, then this method always returns the
/// input value:
///
/// ```rust,ignore
/// let mut buf = RingBuffer::new(0);
/// assert_eq!(Some(0), buf.push_back(0));
/// assert_eq!(Some(1), buf.push_back(1));
/// assert_eq!(Some(2), buf.push_back(2));
/// ```
pub fn push_back(&mut self, value: T) -> Option<T> {
if self.size == 0 {
return Some(value);
}
let result = if self.size <= self.data.len() {
self.data.pop_front()
} else {
None
};
self.data.push_back(value);
result
}
}
#[cfg(test)]
mod tests {
use crate::ringbuffer::RingBuffer;
use std::collections::VecDeque;
use std::iter::FromIterator;
#[test]
fn test_size_limit_zero() {
let mut buf = RingBuffer::new(0);
assert_eq!(Some(0), buf.push_back(0));
assert_eq!(Some(1), buf.push_back(1));
assert_eq!(Some(2), buf.push_back(2));
}
#[test]
fn test_evict_oldest() {
let mut buf = RingBuffer::new(2);
assert_eq!(None, buf.push_back(0));
assert_eq!(None, buf.push_back(1));
assert_eq!(Some(0), buf.push_back(2));
}
#[test]
fn test_from_iter() {
let iter = [0, 1, 2].iter();
let actual = RingBuffer::from_iter(iter, 2).data;
let expected = VecDeque::from_iter([1, 2].iter());
assert_eq!(expected, actual);
}
}

View file

@ -39,6 +39,8 @@ pub use crate::features::fs;
pub use crate::features::fsext; pub use crate::features::fsext;
#[cfg(feature = "parse_time")] #[cfg(feature = "parse_time")]
pub use crate::features::parse_time; pub use crate::features::parse_time;
#[cfg(feature = "ringbuffer")]
pub use crate::features::ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]
pub use crate::features::zero_copy; pub use crate::features::zero_copy;

View file

@ -129,6 +129,15 @@ fn test_zero_terminated_syntax_2() {
.stdout_is("x\0y"); .stdout_is("x\0y");
} }
#[test]
fn test_zero_terminated_negative_lines() {
new_ucmd!()
.args(&["-z", "-n", "-1"])
.pipe_in("x\0y\0z\0")
.run()
.stdout_is("x\0y\0");
}
#[test] #[test]
fn test_negative_byte_syntax() { fn test_negative_byte_syntax() {
new_ucmd!() new_ucmd!()