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

wc: emit '-' in ouput when set on command-line

When stdin is explicitly specified on the command-line with '-', emit it
in the output stats to match GNU wc output.

Fixes #2188.
This commit is contained in:
Nicolas Thery 2021-05-09 15:42:55 +02:00
parent d43af35147
commit 112b042769
3 changed files with 85 additions and 32 deletions

View file

@ -104,6 +104,34 @@ fn get_usage() -> String {
)
}
enum StdinKind {
/// Stdin specified on command-line with "-".
Explicit,
/// Stdin implicitly specified on command-line by not passing any positional argument.
Implicit,
}
/// Supported inputs.
enum Input {
/// A regular file.
Path(String),
/// Standard input.
Stdin(StdinKind),
}
impl Input {
/// Converts input to title that appears in stats.
fn to_title(&self) -> Option<&str> {
match self {
Input::Path(path) => Some(path),
Input::Stdin(StdinKind::Explicit) => Some("-"),
Input::Stdin(StdinKind::Implicit) => None,
}
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
@ -144,18 +172,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
.get_matches_from(args);
let mut files: Vec<String> = matches
let mut inputs: Vec<Input> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.map(|v| {
v.map(|i| {
if i == "-" {
Input::Stdin(StdinKind::Explicit)
} else {
Input::Path(ToString::to_string(i))
}
})
.collect()
})
.unwrap_or_default();
if files.is_empty() {
files.push("-".to_owned());
if inputs.is_empty() {
inputs.push(Input::Stdin(StdinKind::Implicit));
}
let settings = Settings::new(&matches);
if wc(files, &settings).is_ok() {
if wc(inputs, &settings).is_ok() {
0
} else {
1
@ -198,12 +235,14 @@ fn word_count_from_reader<T: WordCountable>(
Ok(total)
}
fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount> {
if path == "-" {
fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCount> {
match input {
Input::Stdin(_) => {
let stdin = io::stdin();
let stdin_lock = stdin.lock();
word_count_from_reader(stdin_lock, settings, path)
} else {
word_count_from_reader(stdin_lock, settings, "-")
}
Input::Path(path) => {
let path_obj = Path::new(path);
if path_obj.is_dir() {
Err(WcError::IsDirectory(path.to_owned()))
@ -212,18 +251,19 @@ fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount>
word_count_from_reader(file, settings, path)
}
}
}
}
fn wc(files: Vec<String>, settings: &Settings) -> Result<(), u32> {
fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
let mut total_word_count = WordCount::default();
let mut results = vec![];
let mut max_width: usize = 0;
let mut error_count = 0;
let num_files = files.len();
let num_inputs = inputs.len();
for path in &files {
let word_count = word_count_from_path(&path, settings).unwrap_or_else(|err| {
for input in &inputs {
let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| {
show_error!("{}", err);
error_count += 1;
WordCount::default()
@ -235,18 +275,22 @@ fn wc(files: Vec<String>, settings: &Settings) -> Result<(), u32> {
// formatting each count as a string for output.
max_width = max(max_width, word_count.bytes.to_string().len());
total_word_count += word_count;
results.push(word_count.with_title(path));
results.push(word_count.with_title(input.to_title()));
}
for result in &results {
if let Err(err) = print_stats(settings, &result, max_width) {
show_warning!("failed to print result for {}: {}", result.title, err);
show_warning!(
"failed to print result for {}: {}",
result.title.unwrap_or("<stdin>"),
err
);
error_count += 1;
}
}
if num_files > 1 {
let total_result = total_word_count.with_title("total");
if num_inputs > 1 {
let total_result = total_word_count.with_title(Some("total"));
if let Err(err) = print_stats(settings, &total_result, max_width) {
show_warning!("failed to print total: {}", err);
error_count += 1;
@ -315,10 +359,10 @@ fn print_stats(
)?;
}
if result.title == "-" {
writeln!(stdout_lock)?;
if let Some(title) = result.title {
writeln!(stdout_lock, " {}", title)?;
} else {
writeln!(stdout_lock, " {}", result.title)?;
writeln!(stdout_lock)?;
}
Ok(())

View file

@ -103,7 +103,7 @@ impl WordCount {
(word_count, char_count)
}
pub fn with_title(self, title: &str) -> TitledWordCount {
pub fn with_title(self, title: Option<&str>) -> TitledWordCount {
TitledWordCount { title, count: self }
}
@ -120,12 +120,12 @@ impl WordCount {
}
}
/// This struct supplements the actual word count with a title that is displayed
/// to the user at the end of the program.
/// This struct supplements the actual word count with an optional title that is
/// displayed to the user at the end of the program.
/// The reason we don't simply include title in the `WordCount` struct is that
/// it would result in unneccesary copying of `String`.
#[derive(Debug, Default, Clone)]
pub struct TitledWordCount<'a> {
pub title: &'a str,
pub title: Option<&'a str>,
pub count: WordCount,
}

View file

@ -36,6 +36,15 @@ fn test_stdin_default() {
.stdout_is(" 13 109 772\n");
}
#[test]
fn test_stdin_explicit() {
new_ucmd!()
.pipe_in_fixture("lorem_ipsum.txt")
.arg("-")
.run()
.stdout_is(" 13 109 772 -\n");
}
#[test]
fn test_utf8() {
new_ucmd!()