Skip to content

Commit

Permalink
Merge pull request uutils#2887 from jfinkels/tail-no-trailing-newline
Browse files Browse the repository at this point in the history
tail: don't add trailing \n if input doesn't end with one
  • Loading branch information
sylvestre authored Jan 19, 2022
2 parents 13de6ca + ca812a7 commit b45351f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 2 deletions.
83 changes: 83 additions & 0 deletions src/uu/tail/src/lines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// * 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.
//! Iterate over lines, including the line ending character(s).
//!
//! This module provides the [`lines`] function, 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 function yields [`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;

/// 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<B>(reader: B) -> Lines<B>
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<B> {
buf: B,
}

impl<B: BufRead> Iterator for Lines<B> {
type Item = std::io::Result<String>;

fn next(&mut self) -> Option<std::io::Result<String>> {
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)),
}
}
}

#[cfg(test)]
mod tests {
use crate::lines::lines;
use std::io::Cursor;

#[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);
}
}
6 changes: 4 additions & 2 deletions src/uu/tail/src/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ extern crate clap;
extern crate uucore;

mod chunks;
mod lines;
mod parse;
mod platform;
use chunks::ReverseChunks;
use lines::lines;

use clap::{App, Arg};
use std::collections::VecDeque;
Expand Down Expand Up @@ -482,8 +484,8 @@ fn unbounded_tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> UR
// data in the ringbuf.
match settings.mode {
FilterMode::Lines(count, _) => {
for line in unbounded_tail_collect(reader.lines(), count, settings.beginning) {
println!("{}", line);
for line in unbounded_tail_collect(lines(reader), count, settings.beginning) {
print!("{}", line);
}
}
FilterMode::Bytes(count) => {
Expand Down
5 changes: 5 additions & 0 deletions tests/by-util/test_tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,8 @@ fn test_no_such_file() {
.no_stdout()
.stderr_contains("cannot open 'bogusfile' for reading: No such file or directory");
}

#[test]
fn test_no_trailing_newline() {
new_ucmd!().pipe_in("x").succeeds().stdout_only("x");
}

0 comments on commit b45351f

Please sign in to comment.