From 374a4fde8699ae5ac1b36ccab0b29db0855f4b00 Mon Sep 17 00:00:00 2001 From: Ali <320322+ali5h@users.noreply.github.com> Date: Wed, 10 Mar 2021 14:19:12 -0800 Subject: [PATCH] paste: support multi-stdin (#1791) - added `-` as the default input, since `paste` reads stdin if no file is provided - `paste` also supports providing `-` multiple times - added a test for it --- src/uu/paste/src/paste.rs | 37 ++++++++++++++++++++++++++++++------- tests/by-util/test_paste.rs | 23 +++++++++++++++++++---- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 882ba7137..e78dc7e64 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, Read}; +use std::io::{stdin, BufRead, BufReader, Stdin}; use std::iter::repeat; use std::path::Path; @@ -26,6 +26,29 @@ 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); + } +} + pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .version(VERSION) @@ -49,7 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::FILE) .value_name("FILE") .multiple(true) - .required(true), + .default_value("-"), ) .get_matches_from(args); @@ -66,15 +89,15 @@ 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| { - BufReader::new(if name == "-" { - Box::new(stdin()) as Box + if name == "-" { + Box::new(StdinReadLine(stdin())) as Box } else { let r = crash_if_err!(1, File::open(Path::new(&name))); - Box::new(r) as Box - }) + Box::new(BufReadReadLine(BufReader::new(r))) as Box + } }) .collect(); diff --git a/tests/by-util/test_paste.rs b/tests/by-util/test_paste.rs index 27de0b445..ef83fe9da 100644 --- a/tests/by-util/test_paste.rs +++ b/tests/by-util/test_paste.rs @@ -2,8 +2,23 @@ use crate::common::util::*; #[test] fn test_combine_pairs_of_lines() { - new_ucmd!() - .args(&["-s", "-d", "\t\n", "html_colors.txt"]) - .run() - .stdout_is_fixture("html_colors.expected"); + for s in vec!["-s", "--serial"] { + for d in vec!["-d", "--delimiters"] { + new_ucmd!() + .args(&[s, d, "\t\n", "html_colors.txt"]) + .run() + .stdout_is_fixture("html_colors.expected"); + } + } +} + +#[test] +fn test_multi_stdin() { + for d in vec!["-d", "--delimiters"] { + new_ucmd!() + .args(&[d, "\t\n", "-", "-"]) + .pipe_in_fixture("html_colors.txt") + .succeeds() + .stdout_is_fixture("html_colors.expected"); + } }