From 112b04276922a3c10f39abf88907bccf714d6b30 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sun, 9 May 2021 15:42:55 +0200 Subject: [PATCH] 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. --- src/uu/wc/src/wc.rs | 100 ++++++++++++++++++++++++++----------- src/uu/wc/src/wordcount.rs | 8 +-- tests/by-util/test_wc.rs | 9 ++++ 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 33b2ba5ec..226608d40 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -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 = matches + let mut inputs: Vec = 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,32 +235,35 @@ fn word_count_from_reader( Ok(total) } -fn word_count_from_path(path: &str, settings: &Settings) -> WcResult { - if path == "-" { - let stdin = io::stdin(); - let stdin_lock = stdin.lock(); - word_count_from_reader(stdin_lock, settings, path) - } else { - let path_obj = Path::new(path); - if path_obj.is_dir() { - Err(WcError::IsDirectory(path.to_owned())) - } else { - let file = File::open(path)?; - word_count_from_reader(file, settings, path) +fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { + match input { + Input::Stdin(_) => { + let stdin = io::stdin(); + let stdin_lock = stdin.lock(); + 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())) + } else { + let file = File::open(path)?; + word_count_from_reader(file, settings, path) + } } } } -fn wc(files: Vec, settings: &Settings) -> Result<(), u32> { +fn wc(inputs: Vec, 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, 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(""), + 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(()) diff --git a/src/uu/wc/src/wordcount.rs b/src/uu/wc/src/wordcount.rs index 785e57eff..9e2a81fca 100644 --- a/src/uu/wc/src/wordcount.rs +++ b/src/uu/wc/src/wordcount.rs @@ -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, } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 87a86fca4..b61d7e3aa 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -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!()