diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index eded419df..959d87604 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -38,6 +38,7 @@ mod options { mod lines; mod parse; mod take; +use lines::lines; use lines::zlines; use take::take_all_but; use take::take_lines; @@ -285,8 +286,8 @@ fn read_but_last_n_lines( stdout.write_all(&bytes?)?; } } else { - for line in take_all_but(input.lines(), n) { - println!("{}", line?); + for line in take_all_but(lines(input), n) { + print!("{}", line?); } } Ok(()) diff --git a/src/uu/head/src/lines.rs b/src/uu/head/src/lines.rs index 474f5717d..5c1b23b27 100644 --- a/src/uu/head/src/lines.rs +++ b/src/uu/head/src/lines.rs @@ -1,11 +1,75 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. // spell-checker:ignore (vars) zline zlines - -//! Iterate over zero-terminated lines. +//! Iterate over lines, including the line ending character(s). +//! +//! This module provides the [`lines`] and [`zlines`] functions, +//! similar to the [`BufRead::lines`] method. While the +//! [`BufRead::lines`] method yields [`String`] instances that do not +//! include the line ending characters (`"\n"` or `"\r\n"`), our +//! functions yield [`String`] instances that include the line ending +//! characters. This is useful if the input data does not end with a +//! newline character and you want to preserve the exact form of the +//! input data. use std::io::BufRead; /// The zero byte, representing the null character. const ZERO: u8 = 0; +/// Returns an iterator over the lines, including line ending characters. +/// +/// This function is just like [`BufRead::lines`], but it includes the +/// line ending characters in each yielded [`String`] if the input +/// data has them. +/// +/// # Examples +/// +/// If the input data does not end with a newline character (`'\n'`), +/// then the last [`String`] yielded by this iterator also does not +/// end with a newline: +/// +/// ```rust,ignore +/// use std::io::BufRead; +/// use std::io::Cursor; +/// +/// let cursor = Cursor::new(b"x\ny\nz"); +/// let mut it = cursor.lines(); +/// +/// assert_eq!(it.next(), Some(String::from("x\n"))); +/// assert_eq!(it.next(), Some(String::from("y\n"))); +/// assert_eq!(it.next(), Some(String::from("z"))); +/// assert_eq!(it.next(), None); +/// ``` +pub(crate) fn lines(reader: B) -> Lines +where + B: BufRead, +{ + Lines { buf: reader } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// This struct is generally created by calling [`lines`] on a `BufRead`. +/// Please see the documentation of [`lines`] for more details. +pub(crate) struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = std::io::Result; + + fn next(&mut self) -> Option> { + let mut buf = String::new(); + match self.buf.read_line(&mut buf) { + Ok(0) => None, + Ok(_n) => Some(Ok(buf)), + Err(e) => Some(Err(e)), + } + } +} + /// Returns an iterator over the lines of the given reader. /// /// The iterator returned from this function will yield instances of @@ -50,6 +114,7 @@ impl Iterator for ZLines { #[cfg(test)] mod tests { + use crate::lines::lines; use crate::lines::zlines; use std::io::Cursor; @@ -72,4 +137,15 @@ mod tests { assert_eq!(iter.next(), Some(b"z".to_vec())); assert_eq!(iter.next(), None); } + + #[test] + fn test_lines() { + let cursor = Cursor::new(b"x\ny\nz"); + let mut it = lines(cursor).map(|l| l.unwrap()); + + assert_eq!(it.next(), Some(String::from("x\n"))); + assert_eq!(it.next(), Some(String::from("y\n"))); + assert_eq!(it.next(), Some(String::from("z"))); + assert_eq!(it.next(), None); + } } diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 246f5b62a..8f4932edf 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -157,11 +157,17 @@ fn test_negative_byte_syntax() { #[test] fn test_negative_zero_lines() { new_ucmd!() - .args(&["--lines=-0"]) + .arg("--lines=-0") .pipe_in("a\nb\n") .succeeds() .stdout_is("a\nb\n"); + new_ucmd!() + .arg("--lines=-0") + .pipe_in("a\nb") + .succeeds() + .stdout_is("a\nb"); } + #[test] fn test_negative_zero_bytes() { new_ucmd!()