From 13fdf4702258f1717679aa9b15ae04631aa05943 Mon Sep 17 00:00:00 2001 From: Shannon Skipper Date: Thu, 6 Jun 2024 02:50:41 -0700 Subject: [PATCH] Refactor logging for a DRYer, unified interface --- src/main.rs | 108 ++++++++++++++++++++++------------------------------ 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7fd9efe..24747bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ -//! `word-tally` tallies and outputs the count of words from a file or streamed input. +//! `word-tally` tallies and outputs the count of words from a given input. pub(crate) mod args; use anyhow::{Context, Result}; use args::Args; use clap::Parser; -use clap_stdin::Source; use std::fs::File; -use std::io::{self, ErrorKind::BrokenPipe, LineWriter, StderrLock, Write}; +use std::io::{self, ErrorKind::BrokenPipe, LineWriter, Write}; use unescaper::unescape; use word_tally::{Minimums, WordTally}; @@ -43,73 +42,38 @@ fn log_details( args: &Args, delimiter: &str, ) -> Result<()> { - let mut stderr_lock = stderr.lock(); + let mut w = Box::new(stderr.lock()) as Writer; if args.verbose { - log_verbose( - &mut stderr_lock, - word_tally.count(), - word_tally.uniq_count(), - word_tally.avg(), - &args.input.source, + log_detail( + &mut w, + "source", delimiter, + format!("{:?}", args.input.source), )?; + log_detail(&mut w, "total-words", delimiter, word_tally.count())?; + log_detail(&mut w, "unique-words", delimiter, word_tally.uniq_count())?; + + if let Some(avg) = word_tally.avg() { + log_detail(&mut w, "average-word-count", delimiter, format!("{avg:.3}"))?; + } } if args.debug { - log_debug(&mut stderr_lock, args, delimiter)?; + log_detail(&mut w, "delimiter", delimiter, format!("{delimiter:?}"))?; + log_detail(&mut w, "case", delimiter, args.case)?; + log_detail(&mut w, "order", delimiter, args.sort)?; + log_detail(&mut w, "min-chars", delimiter, args.min_chars)?; + log_detail(&mut w, "min-count", delimiter, args.min_count)?; + log_detail(&mut w, "verbose", delimiter, args.verbose)?; + log_detail(&mut w, "debug", delimiter, args.debug)?; } if word_tally.count() > 0 { - piping(stderr_lock.write_all(b"\n"))?; - } - - piping(stderr_lock.flush())?; - - Ok(()) -} - -/// Log verbose details to stderr. -fn log_verbose( - stderr: &mut StderrLock<'_>, - count: u64, - uniq_count: usize, - maybe_avg: Option, - source: &Source, - delimiter: &str, -) -> Result<()> { - let details = [ - format!("source{delimiter}{source:#?}\n"), - format!("total-words{delimiter}{count}\n"), - format!("unique-words{delimiter}{uniq_count}\n"), - ]; - - for detail in &details { - piping(stderr.write_all(detail.as_bytes()))?; + log(&mut w, "\n")?; } - if let Some(avg) = maybe_avg { - piping(stderr.write_all(format!("average-word-count{delimiter}{avg:.3}\n").as_bytes()))?; - } - - Ok(()) -} - -/// Log debug details to stderr. -fn log_debug(stderr: &mut StderrLock<'_>, args: &Args, delimiter: &str) -> Result<()> { - let details = [ - format!("delimiter{delimiter}{delimiter:#?}\n"), - format!("case{delimiter}{}\n", args.case), - format!("order{delimiter}{}\n", args.sort), - format!("min-chars{delimiter}{}\n", args.min_chars), - format!("min-count{delimiter}{}\n", args.min_count), - format!("verbose{delimiter}{}\n", args.verbose), - format!("debug{delimiter}{}\n", args.debug), - ]; - - for detail in &details { - piping(stderr.write_all(detail.as_bytes()))?; - } + piping(w.flush())?; Ok(()) } @@ -121,23 +85,41 @@ fn write_tally( args: &Args, delimiter: &str, ) -> Result<()> { - let mut writer: Writer = match &args.output { + let mut w: Writer = match &args.output { Some(path) => Box::new(LineWriter::new(File::create(path)?)), None => Box::new(stdout.lock()), }; for (word, count) in word_tally.tally() { let line = format!("{word}{delimiter}{count}\n"); - piping(writer.write_all(line.as_bytes()))?; + log(&mut w, &line)?; } - piping(writer.flush())?; + piping(w.flush())?; + + Ok(()) +} + +/// Log a formatted details line. +fn log_detail( + w: &mut Writer, + label: &str, + delimiter: &str, + value: T, +) -> Result<()> { + let line = format!("{label}{delimiter}{value}\n"); + + log(w, &line) +} + +/// Log a line. +fn log(w: &mut Writer, line: &str) -> Result<()> { + piping(w.write_all(line.as_bytes()))?; Ok(()) } -/// Processes the result of a write operation, handling `BrokenPipe` errors -/// gracefully. +/// Processes the result of a write, handling `BrokenPipe` errors gracefully. // This can be simplified once `-Zon-broken-pipe=kill` stabilizes and can be // used to kill the program if it tries to write to a closed pipe. fn piping(result: std::io::Result<()>) -> Result<()> {