mirror of
https://github.com/uutils/coreutils
synced 2024-10-15 12:24:09 +00:00
Merge pull request #2966 from allan-silva/wc-files0-from-opt
wc: implement files0-from option
This commit is contained in:
commit
f9e04ae5ef
|
@ -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,
|
||||
|
|
|
@ -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
1
tests/fixtures/wc/files0_list.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
lorem_ipsum.txt |