Skip to content

Commit

Permalink
Extract a Logger from main for handling details
Browse files Browse the repository at this point in the history
  • Loading branch information
havenwood committed Oct 15, 2024
1 parent ae210e1 commit faa5460
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 79 deletions.
69 changes: 69 additions & 0 deletions src/logging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::output::Output;
use anyhow::Result;
use word_tally::{Case, Sort, WordTally};

/// `Logger` contains configuration and methods for logging verbose and debug information.
pub struct Logger {
pub verbose: bool,
pub debug: bool,
pub case: Case,
pub sort: Sort,
pub min_chars: Option<usize>,
pub min_count: Option<usize>,
}

impl Logger {
/// Log verbose and debug details to stderr.
pub fn log_details(
&self,
stderr: &mut Output,
word_tally: &WordTally,
delimiter: &str,
source: &str,
) -> Result<()> {
if self.verbose {
self.log(stderr, "source", delimiter, source)?;
self.log(stderr, "total-words", delimiter, word_tally.count())?;
self.log(stderr, "unique-words", delimiter, word_tally.uniq_count())?;
}

if self.debug {
self.log(stderr, "delimiter", delimiter, format!("{:?}", delimiter))?;
self.log(stderr, "case", delimiter, self.case)?;
self.log(stderr, "order", delimiter, self.sort)?;
self.log(
stderr,
"min-chars",
delimiter,
self.min_chars
.map_or("none".to_string(), |count| count.to_string()),
)?;
self.log(
stderr,
"min-count",
delimiter,
self.min_count
.map_or("none".to_string(), |count| count.to_string()),
)?;
self.log(stderr, "verbose", delimiter, self.verbose)?;
self.log(stderr, "debug", delimiter, self.debug)?;
}

if word_tally.count() > 0 {
stderr.write_line("\n")?;
}

Ok(())
}

/// Log a formatted details line.
fn log<T: std::fmt::Display>(
&self,
w: &mut Output,
label: &str,
delimiter: &str,
value: T,
) -> Result<()> {
w.write_line(&format!("{label}{delimiter}{value}\n"))
}
}
75 changes: 6 additions & 69 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! `word-tally` tallies and outputs the count of words from a given input.
pub(crate) mod args;
pub(crate) mod input;
pub(crate) mod logging;
pub(crate) mod output;

use anyhow::{Context, Result};
use args::Args;
use clap::Parser;
use input::Input;
use logging::Logger;
use output::Output;
use unescaper::unescape;
use word_tally::{Case, ExcludeWords, Filters, MinChars, MinCount, Options, Sort, WordTally};
use word_tally::{ExcludeWords, Filters, MinChars, MinCount, Options, WordTally};

fn main() -> Result<()> {
let args = Args::parse();
Expand All @@ -35,7 +38,7 @@ fn main() -> Result<()> {
let word_tally = WordTally::new(reader, options, filters);

if args.verbose || args.debug {
let log_config = LogConfig {
let logger = Logger {
verbose: args.verbose,
debug: args.debug,
case: args.case,
Expand All @@ -45,12 +48,11 @@ fn main() -> Result<()> {
};

let mut stderr_output = Output::stderr();
log_details(
logger.log_details(
&mut stderr_output,
&word_tally,
&delimiter,
&input_file_name,
log_config,
)?;
}

Expand All @@ -64,68 +66,3 @@ fn main() -> Result<()> {

Ok(())
}

/// Log a formatted details line.
fn log_detail<T: std::fmt::Display>(
w: &mut Output,
label: &str,
delimiter: &str,
value: T,
) -> Result<()> {
w.write_line(&format!("{label}{delimiter}{value}\n"))
}

/// `LogConfig` contains `Args` flags that may be used for logging.
struct LogConfig {
verbose: bool,
debug: bool,
case: Case,
sort: Sort,
min_chars: Option<usize>,
min_count: Option<usize>,
}

/// Log verbose and debug details to stderr.
fn log_details(
stderr: &mut Output,
word_tally: &WordTally,
delimiter: &str,
source: &str,
log_config: LogConfig,
) -> Result<()> {
if log_config.verbose {
log_detail(stderr, "source", delimiter, source)?;
log_detail(stderr, "total-words", delimiter, word_tally.count())?;
log_detail(stderr, "unique-words", delimiter, word_tally.uniq_count())?;
}

if log_config.debug {
log_detail(stderr, "delimiter", delimiter, format!("{:?}", delimiter))?;
log_detail(stderr, "case", delimiter, log_config.case)?;
log_detail(stderr, "order", delimiter, log_config.sort)?;
log_detail(
stderr,
"min-chars",
delimiter,
log_config
.min_chars
.map_or("none".to_string(), |count| count.to_string()),
)?;
log_detail(
stderr,
"min-count",
delimiter,
log_config
.min_count
.map_or("none".to_string(), |count| count.to_string()),
)?;
log_detail(stderr, "verbose", delimiter, log_config.verbose)?;
log_detail(stderr, "debug", delimiter, log_config.debug)?;
}

if word_tally.count() > 0 {
stderr.write_line("\n")?;
}

Ok(())
}
20 changes: 10 additions & 10 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
use anyhow::{Context, Result};
use std::fs::File;
use std::io::{self, ErrorKind::BrokenPipe, LineWriter, Write};
use std::path::PathBuf;
use std::path::{Path, PathBuf};

/// `Writer` is a boxed type for dynamic dispatch of the `Write` trait.
pub type Writer = Box<dyn Write>;

/// `Output` to a file or pipe.
/// `Output` manages writing to either a file, stdout, or stderr.
pub struct Output {
writer: Writer,
}

impl Output {
/// Construct an `Output` to a file with error context.
/// Creates an `Output` that writes to a file with error context.
pub fn file(path: PathBuf) -> Result<Self> {
let file = File::create(&path)
.map(|file| Box::new(LineWriter::new(file)) as Writer)
.with_context(|| format!("Failed to create file: {:?}", path))?;
Ok(Self { writer: file })
}

/// Constructs an `Output` to stdout.
/// Creates an `Output` that writes to stdout.
pub fn stdout() -> Self {
Self {
writer: Box::new(io::stdout().lock()),
}
}

/// Constructs an `Output` to stderr.
/// Creates an `Output` that writes to stderr.
pub fn stderr() -> Self {
Self {
writer: Box::new(io::stderr().lock()),
}
}

/// Constructs an `Output` from optional arguments, choosing between file or stdout.
/// Creates an `Output` from optional arguments, choosing between file or stdout.
pub fn from_args(output: Option<PathBuf>) -> Result<Self> {
match output.as_deref().map(|p| p.to_string_lossy()) {
Some(ref path) if path == "-" => Ok(Self::stdout()),
Some(path) => Self::file(PathBuf::from(path.as_ref())),
match output.as_deref() {
Some(path) if path == Path::new("-") => Ok(Self::stdout()),
Some(path) => Self::file(path.to_path_buf()),
None => Ok(Self::stdout()),
}
}

/// Writes a line to the writer, handling pipe-related errors.
/// Writes a line to the writer, handling `BrokenPipe` errors gracefully.
pub fn write_line(&mut self, line: &str) -> Result<()> {
Self::handle_broken_pipe(self.writer.write_all(line.as_bytes()))
}
Expand Down

0 comments on commit faa5460

Please sign in to comment.