Skip to content

Commit

Permalink
Refactor logging for a DRYer, unified interface
Browse files Browse the repository at this point in the history
  • Loading branch information
havenwood committed Jun 6, 2024
1 parent bf8816c commit 13fdf47
Showing 1 changed file with 45 additions and 63 deletions.
108 changes: 45 additions & 63 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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<f64>,
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(())
}
Expand All @@ -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<T: std::fmt::Display>(
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<()> {
Expand Down

0 comments on commit 13fdf47

Please sign in to comment.