From 5ced3a670b02f296d51ef60dd790ad7e0686fa24 Mon Sep 17 00:00:00 2001 From: Ali <320322+ali5h@users.noreply.github.com> Date: Fri, 12 Mar 2021 04:26:09 -0800 Subject: [PATCH] paste: cleanup multi-stdin support (#1803) cleaner impl for multi-stdin support --- src/uu/paste/src/paste.rs | 40 +++++++------------ tests/by-util/test_paste.rs | 80 +++++++++++++++++++++++++++++++++++++ tests/common/util.rs | 3 +- 3 files changed, 95 insertions(+), 28 deletions(-) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index e78dc7e64..751cc0a04 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -12,7 +12,7 @@ extern crate uucore; use clap::{App, Arg}; use std::fs::File; -use std::io::{stdin, BufRead, BufReader, Stdin}; +use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; @@ -26,26 +26,14 @@ mod options { pub const FILE: &str = "file"; } -// We need this trait to wrap both BufReader and Stdin. We need -// `read_line` function only, but Stdin does not provide BufRead -// unless lock function is called, which prevents us from using stdin -// multiple times -trait ReadLine { - fn read_line(&mut self, buf: &mut String) -> std::io::Result; -} - -struct StdinReadLine(Stdin); -struct BufReadReadLine(R); - -impl ReadLine for StdinReadLine { - fn read_line(&mut self, buf: &mut String) -> std::io::Result { - return self.0.read_line(buf); - } -} - -impl ReadLine for BufReadReadLine { - fn read_line(&mut self, buf: &mut String) -> std::io::Result { - return self.0.read_line(buf); +// Wraps BufReader and stdin +fn read_line( + reader: Option<&mut BufReader>, + buf: &mut String, +) -> std::io::Result { + match reader { + Some(reader) => reader.read_line(buf), + None => stdin().read_line(buf), } } @@ -89,14 +77,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } fn paste(filenames: Vec, serial: bool, delimiters: String) { - let mut files: Vec> = filenames + let mut files: Vec<_> = filenames .into_iter() .map(|name| { if name == "-" { - Box::new(StdinReadLine(stdin())) as Box + None } else { let r = crash_if_err!(1, File::open(Path::new(&name))); - Box::new(BufReadReadLine(BufReader::new(r))) as Box + Some(BufReader::new(r)) } }) .collect(); @@ -112,7 +100,7 @@ fn paste(filenames: Vec, serial: bool, delimiters: String) { let mut output = String::new(); loop { let mut line = String::new(); - match file.read_line(&mut line) { + match read_line(file.as_mut(), &mut line) { Ok(0) => break, Ok(_) => { output.push_str(line.trim_end()); @@ -134,7 +122,7 @@ fn paste(filenames: Vec, serial: bool, delimiters: String) { eof_count += 1; } else { let mut line = String::new(); - match file.read_line(&mut line) { + match read_line(file.as_mut(), &mut line) { Ok(0) => { eof[i] = true; eof_count += 1; diff --git a/tests/by-util/test_paste.rs b/tests/by-util/test_paste.rs index ef83fe9da..4604c5cf5 100644 --- a/tests/by-util/test_paste.rs +++ b/tests/by-util/test_paste.rs @@ -1,5 +1,67 @@ use crate::common::util::*; +struct TestData<'b> { + name: &'b str, + args: &'b [&'b str], + ins: &'b [&'b str], + out: &'b str, +} + +static EXAMPLE_DATA: &'static [TestData<'static>] = &[ + // Ensure that paste properly handles files lacking a final newline. + TestData { + name: "no-nl-1", + args: &[], + ins: &["a", "b"], + out: "a\tb\n", + }, + TestData { + name: "no-nl-2", + args: &[], + ins: &["a\n", "b"], + out: "a\tb\n", + }, + TestData { + name: "no-nl-3", + args: &[], + ins: &["a", "b\n"], + out: "a\tb\n", + }, + TestData { + name: "no-nl-4", + args: &[], + ins: &["a\n", "b\n"], + out: "a\tb\n", + }, + // Same as above, but with a two lines in each input file and the + // addition of the -d option to make SPACE be the output + // delimiter. + TestData { + name: "no-nla-1", + args: &["-d", " "], + ins: &["1\na", "2\nb"], + out: "1 2\na b\n", + }, + TestData { + name: "no-nla-2", + args: &["-d", " "], + ins: &["1\na\n", "2\nb"], + out: "1 2\na b\n", + }, + TestData { + name: "no-nla-3", + args: &["-d", " "], + ins: &["1\na", "2\nb\n"], + out: "1 2\na b\n", + }, + TestData { + name: "no-nla-4", + args: &["-d", " "], + ins: &["1\na\n", "2\nb\n"], + out: "1 2\na b\n", + }, +]; + #[test] fn test_combine_pairs_of_lines() { for s in vec!["-s", "--serial"] { @@ -22,3 +84,21 @@ fn test_multi_stdin() { .stdout_is_fixture("html_colors.expected"); } } + +#[test] +fn test_data() { + for example in EXAMPLE_DATA { + let (at, mut ucmd) = at_and_ucmd!(); + let mut ins = vec![]; + for (i, _in) in example.ins.iter().enumerate() { + let file = format!("in{}", i); + at.write(&file, _in); + ins.push(file); + } + println!("{}", example.name); + ucmd.args(example.args) + .args(&ins) + .succeeds() + .stdout_is(example.out); + } +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 6b85c8561..92e5b9128 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -261,8 +261,7 @@ impl AtPath { } pub fn write(&self, name: &str, contents: &str) { - let mut f = self.open(name); - let _ = f.write(contents.as_bytes()); + let _ = std::fs::write(self.plus(name), contents); } pub fn append(&self, name: &str, contents: &str) {