mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
wc: avoid excess String allocations
print_stats will now take advantage of the buffer built into io::stdout(). We can also waste fewer lines on show! by making a helper macro.
This commit is contained in:
parent
c3b06e10a6
commit
38b4825e7f
3 changed files with 41 additions and 81 deletions
|
@ -17,7 +17,7 @@ use countable::WordCountable;
|
|||
use unicode_width::UnicodeWidthChar;
|
||||
use utf8::{BufReadDecoder, BufReadDecoderError};
|
||||
use uucore::{format_usage, help_about, help_usage, show};
|
||||
use word_count::{TitledWordCount, WordCount};
|
||||
use word_count::WordCount;
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
|
@ -29,7 +29,7 @@ use std::fs::{self, File};
|
|||
use std::io::{self, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uucore::error::{UError, UResult, USimpleError};
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::quoting_style::{escape_name, QuotingStyle};
|
||||
|
||||
/// The minimum character width for formatting counts when reading from stdin.
|
||||
|
@ -621,93 +621,72 @@ fn compute_number_width(inputs: &[Input], settings: &Settings) -> usize {
|
|||
fn wc(inputs: &[Input], settings: &Settings) -> UResult<()> {
|
||||
let mut total_word_count = WordCount::default();
|
||||
|
||||
let (number_width, are_stats_visible, total_row_title) =
|
||||
if settings.total_when == TotalWhen::Only {
|
||||
(1, false, None)
|
||||
} else {
|
||||
let number_width = compute_number_width(inputs, settings);
|
||||
let title = Some(String::from("total"));
|
||||
|
||||
(number_width, true, title)
|
||||
};
|
||||
|
||||
let (number_width, are_stats_visible) = match settings.total_when {
|
||||
TotalWhen::Only => (1, false),
|
||||
_ => (compute_number_width(inputs, settings), true),
|
||||
};
|
||||
let is_total_row_visible = settings.total_when.is_total_row_visible(inputs.len());
|
||||
|
||||
for input in inputs {
|
||||
let word_count = match word_count_from_input(input, settings) {
|
||||
CountResult::Success(word_count) => word_count,
|
||||
CountResult::Interrupted(word_count, error) => {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!("{}: {}", input.path_display(), error)
|
||||
));
|
||||
CountResult::Interrupted(word_count, err) => {
|
||||
show!(err.map_err_context(|| input.path_display()));
|
||||
word_count
|
||||
}
|
||||
CountResult::Failure(error) => {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!("{}: {}", input.path_display(), error)
|
||||
));
|
||||
CountResult::Failure(err) => {
|
||||
show!(err.map_err_context(|| input.path_display()));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
total_word_count += word_count;
|
||||
let result = word_count.with_title(input.to_title());
|
||||
|
||||
if are_stats_visible {
|
||||
if let Err(err) = print_stats(settings, &result, number_width) {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!(
|
||||
"failed to print result for {}: {}",
|
||||
&result.title.unwrap_or_else(|| String::from("<stdin>")),
|
||||
err,
|
||||
),
|
||||
));
|
||||
let maybe_title = input.to_title();
|
||||
let maybe_title_str = maybe_title.as_deref();
|
||||
if let Err(err) = print_stats(settings, &word_count, maybe_title_str, number_width) {
|
||||
let title = maybe_title_str.unwrap_or("<stdin>");
|
||||
show!(err.map_err_context(|| format!("failed to print result for {title}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_total_row_visible {
|
||||
let total_result = total_word_count.with_title(total_row_title);
|
||||
if let Err(err) = print_stats(settings, &total_result, number_width) {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!("failed to print total: {err}")
|
||||
));
|
||||
let title = are_stats_visible.then_some("total");
|
||||
if let Err(err) = print_stats(settings, &total_word_count, title, number_width) {
|
||||
show!(err.map_err_context(|| "failed to print total".into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Although this appears to be returning `Ok`, the exit code may
|
||||
// have been set to a non-zero value by a call to `show!()` above.
|
||||
// Although this appears to be returning `Ok`, the exit code may have been set to a non-zero
|
||||
// value by a call to `record_error!()` above.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_stats(
|
||||
settings: &Settings,
|
||||
result: &TitledWordCount,
|
||||
result: &WordCount,
|
||||
title: Option<&str>,
|
||||
number_width: usize,
|
||||
) -> io::Result<()> {
|
||||
let mut columns = Vec::new();
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
||||
if settings.show_lines {
|
||||
columns.push(format!("{:1$}", result.count.lines, number_width));
|
||||
}
|
||||
if settings.show_words {
|
||||
columns.push(format!("{:1$}", result.count.words, number_width));
|
||||
}
|
||||
if settings.show_chars {
|
||||
columns.push(format!("{:1$}", result.count.chars, number_width));
|
||||
}
|
||||
if settings.show_bytes {
|
||||
columns.push(format!("{:1$}", result.count.bytes, number_width));
|
||||
}
|
||||
if settings.show_max_line_length {
|
||||
columns.push(format!("{:1$}", result.count.max_line_length, number_width));
|
||||
}
|
||||
if let Some(title) = &result.title {
|
||||
columns.push(title.clone());
|
||||
let maybe_cols = [
|
||||
(settings.show_lines, result.lines),
|
||||
(settings.show_words, result.words),
|
||||
(settings.show_chars, result.chars),
|
||||
(settings.show_bytes, result.bytes),
|
||||
(settings.show_max_line_length, result.max_line_length),
|
||||
];
|
||||
|
||||
let mut space = "";
|
||||
for (_, num) in maybe_cols.iter().filter(|(show, _)| *show) {
|
||||
write!(stdout, "{space}{num:number_width$}")?;
|
||||
space = " ";
|
||||
}
|
||||
|
||||
writeln!(io::stdout().lock(), "{}", columns.join(" "))
|
||||
if let Some(title) = title {
|
||||
writeln!(stdout, "{space}{title}")
|
||||
} else {
|
||||
writeln!(stdout)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,19 +29,3 @@ impl AddAssign for WordCount {
|
|||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl WordCount {
|
||||
pub fn with_title(self, title: Option<String>) -> TitledWordCount {
|
||||
TitledWordCount { title, count: self }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 unnecessary copying of `String`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TitledWordCount {
|
||||
pub title: Option<String>,
|
||||
pub count: WordCount,
|
||||
}
|
||||
|
|
|
@ -375,7 +375,7 @@ fn test_read_from_directory_error() {
|
|||
#[cfg(not(windows))]
|
||||
const STDERR: &str = ".: Is a directory";
|
||||
#[cfg(windows)]
|
||||
const STDERR: &str = ".: Access is denied";
|
||||
const STDERR: &str = ".: Permission denied";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const STDOUT: &str = " 0 0 0 .\n";
|
||||
|
@ -392,10 +392,7 @@ fn test_read_from_directory_error() {
|
|||
/// Test that getting counts from nonexistent file is an error.
|
||||
#[test]
|
||||
fn test_read_from_nonexistent_file() {
|
||||
#[cfg(not(windows))]
|
||||
const MSG: &str = "bogusfile: No such file or directory";
|
||||
#[cfg(windows)]
|
||||
const MSG: &str = "bogusfile: The system cannot find the file specified";
|
||||
new_ucmd!()
|
||||
.args(&["bogusfile"])
|
||||
.fails()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue