1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

wc: implement files0-from option

When this option is present, the files argument is not processed. This option processes the file list from provided file, splitting them by the ascii NUL (\0) character. When files0-from is '-', the file list is processed from stdin.
This commit is contained in:
Allan Silva 2022-01-30 01:18:32 -03:00
parent 572a505119
commit 6a6875012e
4 changed files with 155 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 {
Input::Stdin(StdinKind::Explicit)
} else {
Input::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("");
}

BIN
tests/fixtures/wc/files0_list.txt vendored Normal file

Binary file not shown.

Binary file not shown.