1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37: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:
Jed Denlea 2023-03-31 19:52:52 -07:00
parent c3b06e10a6
commit 38b4825e7f
3 changed files with 41 additions and 81 deletions

View file

@ -17,7 +17,7 @@ use countable::WordCountable;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
use utf8::{BufReadDecoder, BufReadDecoderError}; use utf8::{BufReadDecoder, BufReadDecoderError};
use uucore::{format_usage, help_about, help_usage, show}; 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}; 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::io::{self, Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use uucore::error::{UError, UResult, USimpleError}; use uucore::error::{FromIo, UError, UResult};
use uucore::quoting_style::{escape_name, QuotingStyle}; use uucore::quoting_style::{escape_name, QuotingStyle};
/// The minimum character width for formatting counts when reading from stdin. /// 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<()> { fn wc(inputs: &[Input], settings: &Settings) -> UResult<()> {
let mut total_word_count = WordCount::default(); let mut total_word_count = WordCount::default();
let (number_width, are_stats_visible, total_row_title) = let (number_width, are_stats_visible) = match settings.total_when {
if settings.total_when == TotalWhen::Only { TotalWhen::Only => (1, false),
(1, false, None) _ => (compute_number_width(inputs, settings), true),
} else { };
let number_width = compute_number_width(inputs, settings);
let title = Some(String::from("total"));
(number_width, true, title)
};
let is_total_row_visible = settings.total_when.is_total_row_visible(inputs.len()); let is_total_row_visible = settings.total_when.is_total_row_visible(inputs.len());
for input in inputs { for input in inputs {
let word_count = match word_count_from_input(input, settings) { let word_count = match word_count_from_input(input, settings) {
CountResult::Success(word_count) => word_count, CountResult::Success(word_count) => word_count,
CountResult::Interrupted(word_count, error) => { CountResult::Interrupted(word_count, err) => {
show!(USimpleError::new( show!(err.map_err_context(|| input.path_display()));
1,
format!("{}: {}", input.path_display(), error)
));
word_count word_count
} }
CountResult::Failure(error) => { CountResult::Failure(err) => {
show!(USimpleError::new( show!(err.map_err_context(|| input.path_display()));
1,
format!("{}: {}", input.path_display(), error)
));
continue; continue;
} }
}; };
total_word_count += word_count; total_word_count += word_count;
let result = word_count.with_title(input.to_title());
if are_stats_visible { if are_stats_visible {
if let Err(err) = print_stats(settings, &result, number_width) { let maybe_title = input.to_title();
show!(USimpleError::new( let maybe_title_str = maybe_title.as_deref();
1, if let Err(err) = print_stats(settings, &word_count, maybe_title_str, number_width) {
format!( let title = maybe_title_str.unwrap_or("<stdin>");
"failed to print result for {}: {}", show!(err.map_err_context(|| format!("failed to print result for {title}")));
&result.title.unwrap_or_else(|| String::from("<stdin>")),
err,
),
));
} }
} }
} }
if is_total_row_visible { if is_total_row_visible {
let total_result = total_word_count.with_title(total_row_title); let title = are_stats_visible.then_some("total");
if let Err(err) = print_stats(settings, &total_result, number_width) { if let Err(err) = print_stats(settings, &total_word_count, title, number_width) {
show!(USimpleError::new( show!(err.map_err_context(|| "failed to print total".into()));
1,
format!("failed to print total: {err}")
));
} }
} }
// Although this appears to be returning `Ok`, the exit code may // Although this appears to be returning `Ok`, the exit code may have been set to a non-zero
// have been set to a non-zero value by a call to `show!()` above. // value by a call to `record_error!()` above.
Ok(()) Ok(())
} }
fn print_stats( fn print_stats(
settings: &Settings, settings: &Settings,
result: &TitledWordCount, result: &WordCount,
title: Option<&str>,
number_width: usize, number_width: usize,
) -> io::Result<()> { ) -> io::Result<()> {
let mut columns = Vec::new(); let mut stdout = io::stdout().lock();
if settings.show_lines { let maybe_cols = [
columns.push(format!("{:1$}", result.count.lines, number_width)); (settings.show_lines, result.lines),
} (settings.show_words, result.words),
if settings.show_words { (settings.show_chars, result.chars),
columns.push(format!("{:1$}", result.count.words, number_width)); (settings.show_bytes, result.bytes),
} (settings.show_max_line_length, result.max_line_length),
if settings.show_chars { ];
columns.push(format!("{:1$}", result.count.chars, number_width));
} let mut space = "";
if settings.show_bytes { for (_, num) in maybe_cols.iter().filter(|(show, _)| *show) {
columns.push(format!("{:1$}", result.count.bytes, number_width)); write!(stdout, "{space}{num:number_width$}")?;
} space = " ";
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());
} }
writeln!(io::stdout().lock(), "{}", columns.join(" ")) if let Some(title) = title {
writeln!(stdout, "{space}{title}")
} else {
writeln!(stdout)
}
} }

View file

@ -29,19 +29,3 @@ impl AddAssign for WordCount {
*self = *self + other; *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,
}

View file

@ -375,7 +375,7 @@ fn test_read_from_directory_error() {
#[cfg(not(windows))] #[cfg(not(windows))]
const STDERR: &str = ".: Is a directory"; const STDERR: &str = ".: Is a directory";
#[cfg(windows)] #[cfg(windows)]
const STDERR: &str = ".: Access is denied"; const STDERR: &str = ".: Permission denied";
#[cfg(not(windows))] #[cfg(not(windows))]
const STDOUT: &str = " 0 0 0 .\n"; 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 that getting counts from nonexistent file is an error.
#[test] #[test]
fn test_read_from_nonexistent_file() { fn test_read_from_nonexistent_file() {
#[cfg(not(windows))]
const MSG: &str = "bogusfile: No such file or directory"; const MSG: &str = "bogusfile: No such file or directory";
#[cfg(windows)]
const MSG: &str = "bogusfile: The system cannot find the file specified";
new_ucmd!() new_ucmd!()
.args(&["bogusfile"]) .args(&["bogusfile"])
.fails() .fails()