mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #2209 from jfinkels/head-ring-buffer
head: add abstractions for "all but last n lines"
This commit is contained in:
commit
41bd025d00
12 changed files with 333 additions and 91 deletions
|
@ -16,7 +16,7 @@ path = "src/head.rs"
|
|||
|
||||
[dependencies]
|
||||
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" }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -27,8 +27,12 @@ mod options {
|
|||
pub const ZERO_NAME: &str = "ZERO";
|
||||
pub const FILES_NAME: &str = "FILE";
|
||||
}
|
||||
mod lines;
|
||||
mod parse;
|
||||
mod split;
|
||||
mod take;
|
||||
use lines::zlines;
|
||||
use take::take_all_but;
|
||||
|
||||
fn app<'a>() -> App<'a, 'a> {
|
||||
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(
|
||||
input: &mut impl std::io::BufRead,
|
||||
input: impl std::io::BufRead,
|
||||
n: usize,
|
||||
zero: bool,
|
||||
) -> std::io::Result<()> {
|
||||
if n == 0 {
|
||||
//prints everything
|
||||
return rbuf_n_bytes(input, std::usize::MAX);
|
||||
if zero {
|
||||
let stdout = std::io::stdout();
|
||||
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];
|
||||
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)
|
||||
}
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
|
||||
|
|
73
src/uu/head/src/lines.rs
Normal file
73
src/uu/head/src/lines.rs
Normal 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
93
src/uu/head/src/take.rs
Normal 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());
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ path = "src/tail.rs"
|
|||
[dependencies]
|
||||
clap = "2.33"
|
||||
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" }
|
||||
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -17,9 +17,7 @@ extern crate uucore;
|
|||
|
||||
mod chunks;
|
||||
mod platform;
|
||||
mod ringbuffer;
|
||||
use chunks::ReverseChunks;
|
||||
use ringbuffer::RingBuffer;
|
||||
|
||||
use clap::{App, Arg};
|
||||
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::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use uucore::ringbuffer::RingBuffer;
|
||||
|
||||
pub mod options {
|
||||
pub mod verbosity {
|
||||
|
|
|
@ -47,6 +47,7 @@ mode = ["libc"]
|
|||
parse_time = []
|
||||
perms = ["libc"]
|
||||
process = ["libc"]
|
||||
ringbuffer = []
|
||||
signals = []
|
||||
utf8 = []
|
||||
utmpx = ["time", "libc"]
|
||||
|
|
|
@ -8,6 +8,8 @@ pub mod fs;
|
|||
pub mod fsext;
|
||||
#[cfg(feature = "parse_time")]
|
||||
pub mod parse_time;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub mod ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
pub mod zero_copy;
|
||||
|
||||
|
|
134
src/uucore/src/lib/features/ringbuffer.rs
Normal file
134
src/uucore/src/lib/features/ringbuffer.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -39,6 +39,8 @@ pub use crate::features::fs;
|
|||
pub use crate::features::fsext;
|
||||
#[cfg(feature = "parse_time")]
|
||||
pub use crate::features::parse_time;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub use crate::features::ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
pub use crate::features::zero_copy;
|
||||
|
||||
|
|
|
@ -129,6 +129,15 @@ fn test_zero_terminated_syntax_2() {
|
|||
.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]
|
||||
fn test_negative_byte_syntax() {
|
||||
new_ucmd!()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue