mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +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
4 changed files with 155 additions and 20 deletions
|
@ -20,12 +20,15 @@ use word_count::{TitledWordCount, WordCount};
|
||||||
use clap::{crate_version, App, AppSettings, Arg, ArgMatches};
|
use clap::{crate_version, App, AppSettings, Arg, ArgMatches};
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use uucore::display::{Quotable, Quoted};
|
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.
|
/// The minimum character width for formatting counts when reading from stdin.
|
||||||
const MINIMUM_WIDTH: usize = 7;
|
const MINIMUM_WIDTH: usize = 7;
|
||||||
|
@ -83,12 +86,14 @@ more than one FILE is specified.";
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub static BYTES: &str = "bytes";
|
pub static BYTES: &str = "bytes";
|
||||||
pub static CHAR: &str = "chars";
|
pub static CHAR: &str = "chars";
|
||||||
|
pub static FILES0_FROM: &str = "files0-from";
|
||||||
pub static LINES: &str = "lines";
|
pub static LINES: &str = "lines";
|
||||||
pub static MAX_LINE_LENGTH: &str = "max-line-length";
|
pub static MAX_LINE_LENGTH: &str = "max-line-length";
|
||||||
pub static WORDS: &str = "words";
|
pub static WORDS: &str = "words";
|
||||||
}
|
}
|
||||||
|
|
||||||
static ARG_FILES: &str = "files";
|
static ARG_FILES: &str = "files";
|
||||||
|
static STDIN_REPR: &str = "-";
|
||||||
|
|
||||||
fn usage() -> String {
|
fn usage() -> String {
|
||||||
format!(
|
format!(
|
||||||
|
@ -115,12 +120,22 @@ enum Input {
|
||||||
Stdin(StdinKind),
|
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 {
|
impl Input {
|
||||||
/// Converts input to title that appears in stats.
|
/// Converts input to title that appears in stats.
|
||||||
fn to_title(&self) -> Option<&Path> {
|
fn to_title(&self) -> Option<&Path> {
|
||||||
match self {
|
match self {
|
||||||
Input::Path(path) => Some(path),
|
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,
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
|
|
||||||
let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);
|
let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);
|
||||||
|
|
||||||
let mut inputs: Vec<Input> = matches
|
let inputs = inputs(&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 settings = Settings::new(&matches);
|
let settings = Settings::new(&matches);
|
||||||
|
|
||||||
|
@ -179,6 +208,17 @@ pub fn uu_app<'a>() -> App<'a> {
|
||||||
.long(options::CHAR)
|
.long(options::CHAR)
|
||||||
.help("print the character counts"),
|
.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(
|
||||||
Arg::new(options::LINES)
|
Arg::new(options::LINES)
|
||||||
.short('l')
|
.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>(
|
fn word_count_from_reader<T: WordCountable>(
|
||||||
mut reader: T,
|
mut reader: T,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
|
|
|
@ -245,3 +245,57 @@ fn test_files_from_pseudo_filesystem() {
|
||||||
let result = new_ucmd!().arg("-c").arg("/proc/version").succeeds();
|
let result = new_ucmd!().arg("-c").arg("/proc/version").succeeds();
|
||||||
assert_ne!(result.stdout_str(), "0 /proc/version\n");
|
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
BIN
tests/fixtures/wc/files0_list.txt
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/wc/files0_list_with_stdin.txt
vendored
Normal file
BIN
tests/fixtures/wc/files0_list_with_stdin.txt
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue