summaryrefslogtreecommitdiff
path: root/src/logger.rs
diff options
context:
space:
mode:
authorA Farzat <a@farzat.xyz>2026-06-22 19:56:40 +0300
committerA Farzat <a@farzat.xyz>2026-06-22 19:56:40 +0300
commit1293ea92bdbf3eb2086ec117e355714b6ee54ae9 (patch)
tree9de76726abd8c4de07904c7ed0284599211a53db /src/logger.rs
parent711df9de70e8a61a441b7ba7200ae006258ce978 (diff)
downloadrepo2markdown-1293ea92bdbf3eb2086ec117e355714b6ee54ae9.tar.gz
repo2markdown-1293ea92bdbf3eb2086ec117e355714b6ee54ae9.zip
Make Logger's output destination customizableHEADmaster
This aids in testing and might allow logging to a file at a later point.
Diffstat (limited to 'src/logger.rs')
-rw-r--r--src/logger.rs119
1 files changed, 109 insertions, 10 deletions
diff --git a/src/logger.rs b/src/logger.rs
index 0352d61..0a0bdc2 100644
--- a/src/logger.rs
+++ b/src/logger.rs
@@ -1,5 +1,10 @@
//! Minimal stderr logging utilities.
+use std::{
+ cell::RefCell,
+ io::{self, Write},
+};
+
/// Logging verbosity level.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Verbosity {
@@ -12,36 +17,130 @@ pub enum Verbosity {
}
/// Lightweight logger used by renderers and generators.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct Logger {
+#[derive(Debug)]
+pub struct Logger<W: Write> {
verbosity: Verbosity,
+ writer: RefCell<W>,
}
-impl Logger {
+impl Logger<io::Stderr> {
/// Creates a logger with the given verbosity.
pub fn new(verbosity: Verbosity) -> Self {
- Self { verbosity }
+ Self {
+ verbosity,
+ writer: RefCell::new(io::stderr()),
+ }
+ }
+}
+
+impl<W: Write> Logger<W> {
+ pub fn with_writer(verbosity: Verbosity, writer: W) -> Self {
+ Self {
+ verbosity,
+ writer: RefCell::new(writer),
+ }
}
/// Emits a warning unless verbosity is [`Verbosity::Quiet`].
pub fn warn(&self, message: impl AsRef<str>) {
if self.verbosity != Verbosity::Quiet {
- eprintln!("Warning: {}", message.as_ref());
+ let _ = writeln!(self.writer.borrow_mut(), "Warning: {}", message.as_ref());
}
}
/// Emits an informational message only when verbosity is [`Verbosity::Verbose`].
pub fn info(&self, message: impl AsRef<str>) {
if self.verbosity == Verbosity::Verbose {
- eprintln!("Info: {}", message.as_ref());
+ let _ = writeln!(self.writer.borrow_mut(), "Info: {}", message.as_ref());
}
}
}
-impl Default for Logger {
- fn default() -> Self {
- Self {
- verbosity: Verbosity::Quiet,
+#[cfg(test)]
+mod tests {
+ use std::io::{self, Write};
+
+ use super::{Logger, Verbosity};
+
+ fn logged_output(logger: &Logger<Vec<u8>>) -> String {
+ String::from_utf8(logger.writer.borrow().clone()).unwrap()
+ }
+
+ #[test]
+ fn quiet_suppresses_warnings() {
+ let logger = Logger::with_writer(Verbosity::Quiet, Vec::new());
+ logger.warn("skipped file");
+ assert_eq!(logged_output(&logger), "");
+ }
+
+ #[test]
+ fn quiet_suppresses_info_messages() {
+ let logger = Logger::with_writer(Verbosity::Quiet, Vec::new());
+ logger.info("rendering file");
+ assert_eq!(logged_output(&logger), "");
+ }
+
+ #[test]
+ fn normal_writes_warnings() {
+ let logger = Logger::with_writer(Verbosity::Normal, Vec::new());
+ logger.warn("skipped file");
+ assert_eq!(logged_output(&logger), "Warning: skipped file\n");
+ }
+
+ #[test]
+ fn normal_suppresses_info_messages() {
+ let logger = Logger::with_writer(Verbosity::Normal, Vec::new());
+ logger.info("rendering file");
+ assert_eq!(logged_output(&logger), "");
+ }
+
+ #[test]
+ fn verbose_writes_warnings() {
+ let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());
+ logger.warn("skipped file");
+ assert_eq!(logged_output(&logger), "Warning: skipped file\n");
+ }
+
+ #[test]
+ fn verbose_writes_info_messages() {
+ let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());
+ logger.info("rendering file");
+ assert_eq!(logged_output(&logger), "Info: rendering file\n");
+ }
+
+ #[test]
+ fn verbose_preserves_log_order() {
+ let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());
+
+ logger.info("rendering a.rs");
+ logger.warn("skipping duplicate file: a.rs");
+ logger.info("rendering b.rs");
+
+ assert_eq!(
+ logged_output(&logger),
+ "Info: rendering a.rs\n\
+ Warning: skipping duplicate file: a.rs\n\
+ Info: rendering b.rs\n"
+ );
+ }
+
+ struct FailingWriter;
+
+ impl Write for FailingWriter {
+ fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
+ Err(io::Error::other("intentional write failure"))
}
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+
+ #[test]
+ fn logging_ignores_writer_errors() {
+ let logger = Logger::with_writer(Verbosity::Verbose, FailingWriter);
+
+ logger.warn("this should not panic");
+ logger.info("this should not panic");
}
}