Repository: uutils/coreutils (Rust)

Issue:
wc: error messages incompatible with GNU wc
Environment: Ubuntu 18.04, GNU `wc` version 8.28

What happens now: uutils `wc` for a non-existent file:
```sh
$ ./target/debug/coreutils wc bogusfile
wc: error: No such file or directory (os error 2)
0 0 0 bogusfile
```
and for a directory
```sh
$ ./target/debug/coreutils wc .
wc: error: Expected a file, found directory .
0 0 0 .
```

What I expected to happen: GNU `wc` for a non-existent file:
```sh
$ wc bogusfile
wc: bogusfile: No such file or directory
```
and for a directory:
```sh
$ wc .
wc: .: Is a directory
      0       0       0 .
```


Discussion:

Patch:
diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs
index 226608d4091..5670508f4f2 100644
--- a/src/uu/wc/src/wc.rs
+++ b/src/uu/wc/src/wc.rs
@@ -22,7 +22,7 @@ use thiserror::Error;
 
 use std::cmp::max;
 use std::fs::File;
-use std::io::{self, Write};
+use std::io::{self, ErrorKind, Write};
 use std::path::Path;
 
 #[derive(Error, Debug)]
@@ -254,6 +254,29 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCou
     }
 }
 
+/// Print a message appropriate for the particular error to `stderr`.
+///
+/// # Examples
+///
+/// This will print `wc: /tmp: Is a directory` to `stderr`.
+///
+/// ```rust,ignore
+/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp"))
+/// ```
+fn show_error(input: &Input, err: WcError) {
+    match (input, err) {
+        (_, WcError::IsDirectory(path)) => {
+            show_error_custom_description!(path, "Is a directory");
+        }
+        (Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => {
+            show_error_custom_description!(path, "No such file or directory");
+        }
+        (_, e) => {
+            show_error!("{}", e);
+        }
+    };
+}
+
 fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
     let mut total_word_count = WordCount::default();
     let mut results = vec![];
@@ -264,7 +287,7 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
 
     for input in &inputs {
         let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| {
-            show_error!("{}", err);
+            show_error(&input, err);
             error_count += 1;
             WordCount::default()
         });


Test patch:
diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs
index b61d7e3aac7..8036d0eaab0 100644
--- a/tests/by-util/test_wc.rs
+++ b/tests/by-util/test_wc.rs
@@ -168,3 +168,26 @@ fn test_file_one_long_word() {
         .run()
         .stdout_is("    1     1 10001 10001 10000 onelongword.txt\n");
 }
+
+/// Test that getting counts from a directory is an error.
+#[test]
+fn test_read_from_directory_error() {
+    // TODO To match GNU `wc`, the `stdout` should be:
+    //
+    //     "      0       0       0 .\n"
+    //
+    new_ucmd!()
+        .args(&["."])
+        .fails()
+        .stderr_contains(".: Is a directory\n")
+        .stdout_is("0 0 0 .\n");
+}
+
+/// Test that getting counts from nonexistent file is an error.
+#[test]
+fn test_read_from_nonexistent_file() {
+    new_ucmd!()
+        .args(&["bogusfile"])
+        .fails()
+        .stderr_contains("bogusfile: No such file or directory\n");
+}