Merge pull request #2966 from allan-silva/wc-files0-from-opt

wc: implement files0-from option
This commit is contained in:
Sylvestre Ledru 2022-02-12 19:05:05 +01:00 committed by GitHub
commit f9e04ae5ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 20 deletions

View file

@ -20,12 +20,15 @@ use word_count::{TitledWordCount, WordCount};
use clap::{crate_version, App, AppSettings, Arg, ArgMatches};
use std::cmp::max;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt::Display;
use std::fs::{self, File};
use std::io::{self, Write};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use uucore::display::{Quotable, Quoted};
use uucore::error::{UResult, USimpleError};
use uucore::error::{UError, UResult, USimpleError};
/// The minimum character width for formatting counts when reading from stdin.
const MINIMUM_WIDTH: usize = 7;
@ -83,12 +86,14 @@ more than one FILE is specified.";
pub mod options {
pub static BYTES: &str = "bytes";
pub static CHAR: &str = "chars";
pub static FILES0_FROM: &str = "files0-from";
pub static LINES: &str = "lines";
pub static MAX_LINE_LENGTH: &str = "max-line-length";
pub static WORDS: &str = "words";
}
static ARG_FILES: &str = "files";
static STDIN_REPR: &str = "-";
fn usage() -> String {
format!(
@ -115,12 +120,22 @@ enum Input {
Stdin(StdinKind),
}
impl From<&OsStr> for Input {
fn from(input: &OsStr) -> Self {
if input == STDIN_REPR {
Self::Stdin(StdinKind::Explicit)
} else {
Self::Path(input.into())
}
}
}
impl Input {
/// Converts input to title that appears in stats.
fn to_title(&self) -> Option<&Path> {
match self {
Input::Path(path) => Some(path),
Input::Stdin(StdinKind::Explicit) => Some("-".as_ref()),
Input::Stdin(StdinKind::Explicit) => Some(STDIN_REPR.as_ref()),
Input::Stdin(StdinKind::Implicit) => None,
}
}
@ -133,29 +148,43 @@ impl Input {
}
}
#[derive(Debug)]
enum WcError {
FilesDisabled(String),
StdinReprNotAllowed(String),
}
impl UError for WcError {
fn code(&self) -> i32 {
match self {
WcError::FilesDisabled(_) | WcError::StdinReprNotAllowed(_) => 1,
}
}
fn usage(&self) -> bool {
matches!(self, WcError::FilesDisabled(_))
}
}
impl Error for WcError {}
impl Display for WcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WcError::FilesDisabled(message) | WcError::StdinReprNotAllowed(message) => {
write!(f, "{}", message)
}
}
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = usage();
let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);
let mut inputs: Vec<Input> = matches
.values_of_os(ARG_FILES)
.map(|v| {
v.map(|i| {
if i == "-" {
Input::Stdin(StdinKind::Explicit)
} else {
Input::Path(i.into())
}
})
.collect()
})
.unwrap_or_default();
if inputs.is_empty() {
inputs.push(Input::Stdin(StdinKind::Implicit));
}
let inputs = inputs(&matches)?;
let settings = Settings::new(&matches);
@ -179,6 +208,17 @@ pub fn uu_app<'a>() -> App<'a> {
.long(options::CHAR)
.help("print the character counts"),
)
.arg(
Arg::new(options::FILES0_FROM)
.long(options::FILES0_FROM)
.takes_value(true)
.value_name("F")
.help(
"read input from the files specified by
NUL-terminated names in file F;
If F is - then read names from standard input",
),
)
.arg(
Arg::new(options::LINES)
.short('l')
@ -205,6 +245,47 @@ pub fn uu_app<'a>() -> App<'a> {
)
}
fn inputs(matches: &ArgMatches) -> UResult<Vec<Input>> {
match matches.values_of_os(ARG_FILES) {
Some(os_values) => {
if matches.is_present(options::FILES0_FROM) {
return Err(WcError::FilesDisabled(
"file operands cannot be combined with --files0-from".into(),
)
.into());
}
Ok(os_values.map(Input::from).collect())
}
None => match matches.value_of(options::FILES0_FROM) {
Some(files_0_from) => create_paths_from_files0(files_0_from),
None => Ok(vec![Input::Stdin(StdinKind::Implicit)]),
},
}
}
fn create_paths_from_files0(files_0_from: &str) -> UResult<Vec<Input>> {
let mut paths = String::new();
let read_from_stdin = files_0_from == STDIN_REPR;
if read_from_stdin {
io::stdin().lock().read_to_string(&mut paths)?;
} else {
File::open(files_0_from)?.read_to_string(&mut paths)?;
}
let paths: Vec<&str> = paths.split_terminator('\0').collect();
if read_from_stdin && paths.contains(&STDIN_REPR) {
return Err(WcError::StdinReprNotAllowed(
"when reading file names from stdin, no file name of '-' allowed".into(),
)
.into());
}
Ok(paths.iter().map(OsStr::new).map(Input::from).collect())
}
fn word_count_from_reader<T: WordCountable>(
mut reader: T,
settings: &Settings,

View file

@ -245,3 +245,57 @@ fn test_files_from_pseudo_filesystem() {
let result = new_ucmd!().arg("-c").arg("/proc/version").succeeds();
assert_ne!(result.stdout_str(), "0 /proc/version\n");
}
#[test]
fn test_files0_disabled_files_argument() {
const MSG: &str = "file operands cannot be combined with --files0-from";
new_ucmd!()
.args(&["--files0-from=files0_list.txt"])
.arg("lorem_ipsum.txt")
.fails()
.stderr_contains(MSG)
.stdout_is("");
}
#[test]
fn test_files0_from() {
new_ucmd!()
.args(&["--files0-from=files0_list.txt"])
.run()
.stdout_is(
" 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \
alice_in_wonderland.txt\n 36 370 2189 total\n",
);
}
#[test]
fn test_files0_from_with_stdin() {
new_ucmd!()
.args(&["--files0-from=-"])
.pipe_in("lorem_ipsum.txt")
.run()
.stdout_is(" 13 109 772 lorem_ipsum.txt\n");
}
#[test]
fn test_files0_from_with_stdin_in_file() {
new_ucmd!()
.args(&["--files0-from=files0_list_with_stdin.txt"])
.pipe_in_fixture("alice_in_wonderland.txt")
.run()
.stdout_is(
" 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \
-\n 36 370 2189 total\n",
);
}
#[test]
fn test_files0_from_with_stdin_try_read_from_stdin() {
const MSG: &str = "when reading file names from stdin, no file name of '-' allowed";
new_ucmd!()
.args(&["--files0-from=-"])
.pipe_in("-")
.fails()
.stderr_contains(MSG)
.stdout_is("");
}

1
tests/fixtures/wc/files0_list.txt vendored Normal file
View file

@ -0,0 +1 @@
lorem_ipsum.txtmoby_dick.txtalice_in_wonderland.txt

View file

@ -0,0 +1 @@
lorem_ipsum.txtmoby_dick.txt-