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:
parent
d43af35147
commit
112b042769
3 changed files with 85 additions and 32 deletions
|
@ -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,32 +235,35 @@ fn word_count_from_reader<T: WordCountable>(
|
|||
Ok(total)
|
||||
}
|
||||
|
||||
fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount> {
|
||||
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<WordCount> {
|
||||
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<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(())
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue