1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57: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 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); 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)) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
.get_matches_from(args); .get_matches_from(args);
let mut files: Vec<String> = matches let mut inputs: Vec<Input> = matches
.values_of(ARG_FILES) .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(); .unwrap_or_default();
if files.is_empty() { if inputs.is_empty() {
files.push("-".to_owned()); inputs.push(Input::Stdin(StdinKind::Implicit));
} }
let settings = Settings::new(&matches); let settings = Settings::new(&matches);
if wc(files, &settings).is_ok() { if wc(inputs, &settings).is_ok() {
0 0
} else { } else {
1 1
@ -198,12 +235,14 @@ fn word_count_from_reader<T: WordCountable>(
Ok(total) Ok(total)
} }
fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount> { fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCount> {
if path == "-" { match input {
Input::Stdin(_) => {
let stdin = io::stdin(); let stdin = io::stdin();
let stdin_lock = stdin.lock(); let stdin_lock = stdin.lock();
word_count_from_reader(stdin_lock, settings, path) word_count_from_reader(stdin_lock, settings, "-")
} else { }
Input::Path(path) => {
let path_obj = Path::new(path); let path_obj = Path::new(path);
if path_obj.is_dir() { if path_obj.is_dir() {
Err(WcError::IsDirectory(path.to_owned())) Err(WcError::IsDirectory(path.to_owned()))
@ -213,17 +252,18 @@ fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount>
} }
} }
} }
}
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 total_word_count = WordCount::default();
let mut results = vec![]; let mut results = vec![];
let mut max_width: usize = 0; let mut max_width: usize = 0;
let mut error_count = 0; let mut error_count = 0;
let num_files = files.len(); let num_inputs = inputs.len();
for path in &files { for input in &inputs {
let word_count = word_count_from_path(&path, settings).unwrap_or_else(|err| { let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| {
show_error!("{}", err); show_error!("{}", err);
error_count += 1; error_count += 1;
WordCount::default() WordCount::default()
@ -235,18 +275,22 @@ fn wc(files: Vec<String>, settings: &Settings) -> Result<(), u32> {
// formatting each count as a string for output. // formatting each count as a string for output.
max_width = max(max_width, word_count.bytes.to_string().len()); max_width = max(max_width, word_count.bytes.to_string().len());
total_word_count += word_count; 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 { for result in &results {
if let Err(err) = print_stats(settings, &result, max_width) { 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; error_count += 1;
} }
} }
if num_files > 1 { if num_inputs > 1 {
let total_result = total_word_count.with_title("total"); let total_result = total_word_count.with_title(Some("total"));
if let Err(err) = print_stats(settings, &total_result, max_width) { if let Err(err) = print_stats(settings, &total_result, max_width) {
show_warning!("failed to print total: {}", err); show_warning!("failed to print total: {}", err);
error_count += 1; error_count += 1;
@ -315,10 +359,10 @@ fn print_stats(
)?; )?;
} }
if result.title == "-" { if let Some(title) = result.title {
writeln!(stdout_lock)?; writeln!(stdout_lock, " {}", title)?;
} else { } else {
writeln!(stdout_lock, " {}", result.title)?; writeln!(stdout_lock)?;
} }
Ok(()) Ok(())

View file

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

View file

@ -36,6 +36,15 @@ fn test_stdin_default() {
.stdout_is(" 13 109 772\n"); .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] #[test]
fn test_utf8() { fn test_utf8() {
new_ucmd!() new_ucmd!()