mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 01:38:04 +00:00
paste: cleanup multi-stdin support (#1803)
cleaner impl for multi-stdin support
This commit is contained in:
parent
3ab114f283
commit
5ced3a670b
3 changed files with 95 additions and 28 deletions
|
@ -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<usize>;
|
||||
}
|
||||
|
||||
struct StdinReadLine(Stdin);
|
||||
struct BufReadReadLine<R: BufRead>(R);
|
||||
|
||||
impl ReadLine for StdinReadLine {
|
||||
fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
|
||||
return self.0.read_line(buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> ReadLine for BufReadReadLine<R> {
|
||||
fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
|
||||
return self.0.read_line(buf);
|
||||
// Wraps BufReader and stdin
|
||||
fn read_line<R: Read>(
|
||||
reader: Option<&mut BufReader<R>>,
|
||||
buf: &mut String,
|
||||
) -> std::io::Result<usize> {
|
||||
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<String>, serial: bool, delimiters: String) {
|
||||
let mut files: Vec<Box<dyn ReadLine>> = filenames
|
||||
let mut files: Vec<_> = filenames
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
if name == "-" {
|
||||
Box::new(StdinReadLine(stdin())) as Box<dyn ReadLine>
|
||||
None
|
||||
} else {
|
||||
let r = crash_if_err!(1, File::open(Path::new(&name)));
|
||||
Box::new(BufReadReadLine(BufReader::new(r))) as Box<dyn ReadLine>
|
||||
Some(BufReader::new(r))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -112,7 +100,7 @@ fn paste(filenames: Vec<String>, 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<String>, 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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue