mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #2898 from jfinkels/tail-lines-zero-terminated
tail: support zero-terminated lines in streams
This commit is contained in:
commit
b8b642101f
3 changed files with 66 additions and 21 deletions
|
@ -17,31 +17,45 @@ use std::io::BufRead;
|
||||||
///
|
///
|
||||||
/// This function is just like [`BufRead::lines`], but it includes the
|
/// This function is just like [`BufRead::lines`], but it includes the
|
||||||
/// line ending characters in each yielded [`String`] if the input
|
/// line ending characters in each yielded [`String`] if the input
|
||||||
/// data has them.
|
/// data has them. Set the `sep` parameter to the line ending
|
||||||
|
/// character; for Unix line endings, use `b'\n'`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
/// Use `sep` to specify an alternate character for line endings. For
|
||||||
|
/// example, if lines are terminated by the null character `b'\0'`:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// use std::io::BufRead;
|
||||||
|
/// use std::io::Cursor;
|
||||||
|
///
|
||||||
|
/// let cursor = Cursor::new(b"x\0y\0z\0");
|
||||||
|
/// let mut it = lines(cursor, b'\0').map(|l| l.unwrap());
|
||||||
|
///
|
||||||
|
/// assert_eq!(it.next(), Some(Vec::from("x\0")));
|
||||||
|
/// assert_eq!(it.next(), Some(Vec::from("y\0")));
|
||||||
|
/// assert_eq!(it.next(), Some(Vec::from("z\0")));
|
||||||
|
/// assert_eq!(it.next(), None);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// If the input data does not end with a newline character (`'\n'`),
|
/// If the input data does not end with a newline character (`'\n'`),
|
||||||
/// then the last [`String`] yielded by this iterator also does not
|
/// then the last [`String`] yielded by this iterator also does not
|
||||||
/// end with a newline:
|
/// end with a newline:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// use std::io::BufRead;
|
|
||||||
/// use std::io::Cursor;
|
|
||||||
///
|
|
||||||
/// let cursor = Cursor::new(b"x\ny\nz");
|
/// let cursor = Cursor::new(b"x\ny\nz");
|
||||||
/// let mut it = cursor.lines();
|
/// let mut it = lines(cursor, b'\n').map(|l| l.unwrap());
|
||||||
///
|
///
|
||||||
/// assert_eq!(it.next(), Some(String::from("x\n")));
|
/// assert_eq!(it.next(), Some(Vec::from("x\n")));
|
||||||
/// assert_eq!(it.next(), Some(String::from("y\n")));
|
/// assert_eq!(it.next(), Some(Vec::from("y\n")));
|
||||||
/// assert_eq!(it.next(), Some(String::from("z")));
|
/// assert_eq!(it.next(), Some(Vec::from("z")));
|
||||||
/// assert_eq!(it.next(), None);
|
/// assert_eq!(it.next(), None);
|
||||||
/// ```
|
/// ```
|
||||||
pub(crate) fn lines<B>(reader: B) -> Lines<B>
|
pub(crate) fn lines<B>(reader: B, sep: u8) -> Lines<B>
|
||||||
where
|
where
|
||||||
B: BufRead,
|
B: BufRead,
|
||||||
{
|
{
|
||||||
Lines { buf: reader }
|
Lines { buf: reader, sep }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the lines of an instance of `BufRead`.
|
/// An iterator over the lines of an instance of `BufRead`.
|
||||||
|
@ -50,14 +64,15 @@ where
|
||||||
/// Please see the documentation of [`lines`] for more details.
|
/// Please see the documentation of [`lines`] for more details.
|
||||||
pub(crate) struct Lines<B> {
|
pub(crate) struct Lines<B> {
|
||||||
buf: B,
|
buf: B,
|
||||||
|
sep: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: BufRead> Iterator for Lines<B> {
|
impl<B: BufRead> Iterator for Lines<B> {
|
||||||
type Item = std::io::Result<String>;
|
type Item = std::io::Result<Vec<u8>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<std::io::Result<String>> {
|
fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> {
|
||||||
let mut buf = String::new();
|
let mut buf = Vec::new();
|
||||||
match self.buf.read_line(&mut buf) {
|
match self.buf.read_until(self.sep, &mut buf) {
|
||||||
Ok(0) => None,
|
Ok(0) => None,
|
||||||
Ok(_n) => Some(Ok(buf)),
|
Ok(_n) => Some(Ok(buf)),
|
||||||
Err(e) => Some(Err(e)),
|
Err(e) => Some(Err(e)),
|
||||||
|
@ -73,11 +88,24 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lines() {
|
fn test_lines() {
|
||||||
let cursor = Cursor::new(b"x\ny\nz");
|
let cursor = Cursor::new(b"x\ny\nz");
|
||||||
let mut it = lines(cursor).map(|l| l.unwrap());
|
let mut it = lines(cursor, b'\n').map(|l| l.unwrap());
|
||||||
|
|
||||||
assert_eq!(it.next(), Some(String::from("x\n")));
|
assert_eq!(it.next(), Some(Vec::from("x\n")));
|
||||||
assert_eq!(it.next(), Some(String::from("y\n")));
|
assert_eq!(it.next(), Some(Vec::from("y\n")));
|
||||||
assert_eq!(it.next(), Some(String::from("z")));
|
assert_eq!(it.next(), Some(Vec::from("z")));
|
||||||
|
assert_eq!(it.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lines_zero_terminated() {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
let cursor = Cursor::new(b"x\0y\0z\0");
|
||||||
|
let mut it = lines(cursor, b'\0').map(|l| l.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(it.next(), Some(Vec::from("x\0")));
|
||||||
|
assert_eq!(it.next(), Some(Vec::from("y\0")));
|
||||||
|
assert_eq!(it.next(), Some(Vec::from("z\0")));
|
||||||
assert_eq!(it.next(), None);
|
assert_eq!(it.next(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -575,9 +575,12 @@ fn unbounded_tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> UR
|
||||||
// contains count lines/chars. When reaching the end of file, output the
|
// contains count lines/chars. When reaching the end of file, output the
|
||||||
// data in the ringbuf.
|
// data in the ringbuf.
|
||||||
match settings.mode {
|
match settings.mode {
|
||||||
FilterMode::Lines(count, _) => {
|
FilterMode::Lines(count, sep) => {
|
||||||
for line in unbounded_tail_collect(lines(reader), count, settings.beginning) {
|
let mut stdout = stdout();
|
||||||
print!("{}", line);
|
for line in unbounded_tail_collect(lines(reader, sep), count, settings.beginning) {
|
||||||
|
stdout
|
||||||
|
.write_all(&line)
|
||||||
|
.map_err_context(|| String::from("IO error"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FilterMode::Bytes(count) => {
|
FilterMode::Bytes(count) => {
|
||||||
|
|
|
@ -548,3 +548,17 @@ fn test_no_such_file() {
|
||||||
fn test_no_trailing_newline() {
|
fn test_no_trailing_newline() {
|
||||||
new_ucmd!().pipe_in("x").succeeds().stdout_only("x");
|
new_ucmd!().pipe_in("x").succeeds().stdout_only("x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lines_zero_terminated() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "-n", "2"])
|
||||||
|
.pipe_in("a\0b\0c\0d\0e\0")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("d\0e\0");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "-n", "+2"])
|
||||||
|
.pipe_in("a\0b\0c\0d\0e\0")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("b\0c\0d\0e\0");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue