mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
wc: Stricter simpler error handling
Errors are now always shown with the corresponding filename. Errors are no longer converted into warnings. Previously `wc < .` would cause a loop. Checking whether something is a directory is no longer done in advance. This removes race conditions and the edge case where stdin is a directory. The custom error type is removed because io::Error is now enough.
This commit is contained in:
parent
35793fc260
commit
657a04f706
5 changed files with 26 additions and 47 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3126,7 +3126,6 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.20.0",
|
"nix 0.20.0",
|
||||||
"thiserror",
|
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
|
|
@ -18,7 +18,6 @@ path = "src/wc.rs"
|
||||||
clap = { version = "2.33", features = ["wrap_help"] }
|
clap = { version = "2.33", features = ["wrap_help"] }
|
||||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||||
thiserror = "1.0"
|
|
||||||
bytecount = "0.6.2"
|
bytecount = "0.6.2"
|
||||||
utf-8 = "0.7.6"
|
utf-8 = "0.7.6"
|
||||||
unicode-width = "0.1.8"
|
unicode-width = "0.1.8"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::word_count::WordCount;
|
use crate::word_count::WordCount;
|
||||||
|
|
||||||
use super::{WcResult, WordCountable};
|
use super::WordCountable;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{ErrorKind, Read};
|
use std::io::{self, ErrorKind, Read};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use libc::S_IFREG;
|
use libc::S_IFREG;
|
||||||
|
@ -88,7 +88,7 @@ fn count_bytes_using_splice(fd: RawFd) -> Result<usize, usize> {
|
||||||
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
||||||
/// other things such as lines and words.
|
/// other things such as lines and words.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usize> {
|
pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<usize> {
|
||||||
let mut byte_count = 0;
|
let mut byte_count = 0;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -123,12 +123,12 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usi
|
||||||
byte_count += n;
|
byte_count += n;
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> WcResult<WordCount> {
|
pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> io::Result<WordCount> {
|
||||||
let mut total = WordCount::default();
|
let mut total = WordCount::default();
|
||||||
let mut buf = [0; BUF_SIZE];
|
let mut buf = [0; BUF_SIZE];
|
||||||
loop {
|
loop {
|
||||||
|
@ -139,7 +139,7 @@ pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> WcResult<Wo
|
||||||
total.lines += bytecount::count(&buf[..n], b'\n');
|
total.lines += bytecount::count(&buf[..n], b'\n');
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,26 +18,15 @@ use utf8::{BufReadDecoder, BufReadDecoderError};
|
||||||
use word_count::{TitledWordCount, WordCount};
|
use word_count::{TitledWordCount, WordCount};
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg, ArgMatches};
|
use clap::{crate_version, App, Arg, ArgMatches};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, ErrorKind, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum WcError {
|
|
||||||
#[error("{0}")]
|
|
||||||
Io(#[from] io::Error),
|
|
||||||
#[error("Expected a file, found directory {0}")]
|
|
||||||
IsDirectory(PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
type WcResult<T> = Result<T, WcError>;
|
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
show_bytes: bool,
|
show_bytes: bool,
|
||||||
show_chars: bool,
|
show_chars: bool,
|
||||||
|
@ -132,6 +121,13 @@ impl Input {
|
||||||
Input::Stdin(StdinKind::Implicit) => None,
|
Input::Stdin(StdinKind::Implicit) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_display(&self) -> std::path::Display<'_> {
|
||||||
|
match self {
|
||||||
|
Input::Path(path) => path.display(),
|
||||||
|
Input::Stdin(_) => Path::display("'standard input'".as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
|
@ -206,8 +202,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
fn word_count_from_reader<T: WordCountable>(
|
fn word_count_from_reader<T: WordCountable>(
|
||||||
mut reader: T,
|
mut reader: T,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
path: &Path,
|
) -> io::Result<WordCount> {
|
||||||
) -> WcResult<WordCount> {
|
|
||||||
let only_count_bytes = settings.show_bytes
|
let only_count_bytes = settings.show_bytes
|
||||||
&& (!(settings.show_chars
|
&& (!(settings.show_chars
|
||||||
|| settings.show_lines
|
|| settings.show_lines
|
||||||
|
@ -273,7 +268,7 @@ fn word_count_from_reader<T: WordCountable>(
|
||||||
total.bytes += bytes.len();
|
total.bytes += bytes.len();
|
||||||
}
|
}
|
||||||
Err(BufReadDecoderError::Io(e)) => {
|
Err(BufReadDecoderError::Io(e)) => {
|
||||||
show_warning!("Error while reading {}: {}", path.display(), e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,20 +278,16 @@ fn word_count_from_reader<T: WordCountable>(
|
||||||
Ok(total)
|
Ok(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCount> {
|
fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result<WordCount> {
|
||||||
match input {
|
match input {
|
||||||
Input::Stdin(_) => {
|
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, "-".as_ref())
|
word_count_from_reader(stdin_lock, settings)
|
||||||
}
|
}
|
||||||
Input::Path(path) => {
|
Input::Path(path) => {
|
||||||
if path.is_dir() {
|
let file = File::open(path)?;
|
||||||
Err(WcError::IsDirectory(path.to_owned()))
|
word_count_from_reader(file, settings)
|
||||||
} else {
|
|
||||||
let file = File::open(path)?;
|
|
||||||
word_count_from_reader(file, settings, path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,18 +301,8 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult<WordCou
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp"))
|
/// show_error(Input::Path("/tmp"), WcError::IsDirectory("/tmp"))
|
||||||
/// ```
|
/// ```
|
||||||
fn show_error(input: &Input, err: WcError) {
|
fn show_error(input: &Input, err: io::Error) {
|
||||||
match (input, err) {
|
show_error!("{}: {}", input.path_display(), err);
|
||||||
(_, WcError::IsDirectory(path)) => {
|
|
||||||
show_error_custom_description!(path.display(), "Is a directory");
|
|
||||||
}
|
|
||||||
(Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => {
|
|
||||||
show_error_custom_description!(path.display(), "No such file or directory");
|
|
||||||
}
|
|
||||||
(_, e) => {
|
|
||||||
show_error!("{}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the number of digits needed to represent any count for this input.
|
/// Compute the number of digits needed to represent any count for this input.
|
||||||
|
@ -343,7 +324,7 @@ fn show_error(input: &Input, err: WcError) {
|
||||||
/// let input = Input::Stdin(StdinKind::Explicit);
|
/// let input = Input::Stdin(StdinKind::Explicit);
|
||||||
/// assert_eq!(7, digit_width(input));
|
/// assert_eq!(7, digit_width(input));
|
||||||
/// ```
|
/// ```
|
||||||
fn digit_width(input: &Input) -> WcResult<Option<usize>> {
|
fn digit_width(input: &Input) -> io::Result<Option<usize>> {
|
||||||
match input {
|
match input {
|
||||||
Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)),
|
Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)),
|
||||||
Input::Path(filename) => {
|
Input::Path(filename) => {
|
||||||
|
@ -453,7 +434,7 @@ fn print_stats(
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
result: &TitledWordCount,
|
result: &TitledWordCount,
|
||||||
mut min_width: usize,
|
mut min_width: usize,
|
||||||
) -> WcResult<()> {
|
) -> io::Result<()> {
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut stdout_lock = stdout.lock();
|
let mut stdout_lock = stdout.lock();
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,7 @@ fn test_read_from_directory_error() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["."])
|
.args(&["."])
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains(".: Is a directory\n")
|
.stderr_contains(".: Is a directory")
|
||||||
.stdout_is("0 0 0 .\n");
|
.stdout_is("0 0 0 .\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,5 +222,5 @@ fn test_read_from_nonexistent_file() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["bogusfile"])
|
.args(&["bogusfile"])
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains("bogusfile: No such file or directory\n");
|
.stderr_contains("bogusfile: No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue