kxio 3.2.0

Provides injectable Filesystem and Network resources to make code more testable
Documentation
//! Provides an injectable interface for standard print operations.
//!
//! This module also includes macros for println and print operations that
//! are similar to the standard library but work with the Print trait.
//!
//! This module offers a trait-based abstraction over printing operations,
//! allowing for dependency injection and easier testing of code that performs
//! printing operations.
//!
//! # Examples
//!
//! ```
//! use kxio::print::{Print, StandardPrint};
//!
//! fn print_hello(printer: &impl Print) {
//!     printer.println("Hello, World!");
//! }
//!
//! let printer = StandardPrint;
//! print_hello(&printer);
//! ```

mod macros;
mod printer;

pub use printer::Printer;
use std::fmt::Arguments;

/// Standard implementation of the Print trait that uses the std::print! and std::println! macros.
pub fn standard() -> Printer {
    Printer::standard()
}

/// A no-op implementation of the Print trait that discards all output.
pub fn null() -> Printer {
    Printer::null()
}

/// A test implementation that captures output in a String.
pub fn test() -> Printer {
    Printer::test()
}

/// Trait defining print operations that can be performed by implementors.
pub trait Print: Clone {
    /// Prints a formatted line to the standard output.
    ///
    /// # Arguments
    ///
    /// * `args` - Format arguments to print
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.print_fmt(format_args!("Hello"));
    /// ```
    fn print_fmt(&self, args: Arguments<'_>);

    /// Prints a formatted line followed by a newline to the standard output.
    ///
    /// # Arguments
    ///
    /// * `args` - Format arguments to print
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.println_fmt(format_args!("Hello"));
    /// ```
    fn println_fmt(&self, args: Arguments<'_>);

    /// Prints a string slice to the standard output.
    ///
    /// # Arguments
    ///
    /// * `s` - The string slice to print
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.print("Hello");
    /// ```
    fn print(&self, s: &str) {
        self.print_fmt(format_args!("{}", s));
    }

    /// Prints a string slice followed by a newline to the standard output.
    ///
    /// # Arguments
    ///
    /// * `s` - The string slice to print
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.println("Hello");
    /// ```
    fn println(&self, s: &str) {
        self.println_fmt(format_args!("{}", s));
    }

    /// Prints a string slice to the standard error.
    ///
    /// # Arguments
    ///
    /// * `s` - The string slice to print to stderr
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.eprint("Error");
    /// ```
    fn eprint(&self, s: &str) {
        self.eprint_fmt(format_args!("{}", s));
    }

    /// Prints a string slice followed by a newline to the standard error.
    ///
    /// # Arguments
    ///
    /// * `s` - The string slice to print to stderr
    ///
    /// # Examples
    ///
    /// ```
    /// use kxio::print::{Print, StandardPrint};
    ///
    /// let printer = StandardPrint;
    /// printer.eprintln("Error");
    /// ```
    fn eprintln(&self, s: &str) {
        self.eprintln_fmt(format_args!("{}", s));
    }

    /// Prints to stderr with a format string.
    ///
    /// This method is the base method for printing to stderr. The other stderr printing
    /// methods are implemented in terms of this one.
    ///
    /// # Arguments
    ///
    /// * `args` - The format arguments to print
    fn eprint_fmt(&self, args: Arguments<'_>);

    /// Prints to stderr with a format string, followed by a newline.
    ///
    /// This method is the base method for printing to stderr with a newline. The other stderr printing
    /// methods are implemented in terms of this one.
    ///
    /// # Arguments
    ///
    /// * `args` - The format arguments to print
    fn eprintln_fmt(&self, args: Arguments<'_>);
}

/// Standard implementation of the Print trait that uses the std::print! and std::println! macros.
#[derive(Clone, Debug, Default, Copy)]
pub struct StandardPrint;

#[cfg_attr(test, mutants::skip)]
impl Print for StandardPrint {
    fn print_fmt(&self, args: Arguments<'_>) {
        std::print!("{}", args);
    }

    fn println_fmt(&self, args: Arguments<'_>) {
        std::println!("{}", args);
    }

    fn eprint_fmt(&self, args: Arguments<'_>) {
        std::eprint!("{}", args);
    }

    fn eprintln_fmt(&self, args: Arguments<'_>) {
        std::eprintln!("{}", args);
    }
}

/// A no-op implementation of the Print trait that discards all output.
#[derive(Clone, Debug, Default, Copy)]
pub struct NullPrint;

impl Print for NullPrint {
    fn print_fmt(&self, _args: Arguments<'_>) {}
    fn println_fmt(&self, _args: Arguments<'_>) {}
    fn eprint_fmt(&self, _args: Arguments<'_>) {}
    fn eprintln_fmt(&self, _args: Arguments<'_>) {}
}

/// A test implementation that captures output in a String.
#[derive(Clone, Debug, Default)]
pub struct TestPrint {
    stdout: std::sync::Arc<std::sync::Mutex<String>>,
    stderr: std::sync::Arc<std::sync::Mutex<String>>,
}

impl TestPrint {
    /// Creates a new TestPrint instance.
    pub fn new() -> Self {
        Self {
            stdout: std::sync::Arc::new(std::sync::Mutex::new(String::new())),
            stderr: std::sync::Arc::new(std::sync::Mutex::new(String::new())),
        }
    }

    /// Returns the captured stdout output as a String.
    pub fn output(&self) -> String {
        self.stdout.lock().unwrap().clone()
    }

    /// Returns the captured stderr output as a String.
    pub fn stderr(&self) -> String {
        self.stderr.lock().unwrap().clone()
    }

    /// Clears both the captured stdout and stderr output.
    pub fn clear(&self) {
        self.stdout.lock().unwrap().clear();
        self.stderr.lock().unwrap().clear();
    }
}

impl Print for TestPrint {
    fn print_fmt(&self, args: Arguments<'_>) {
        (&self).print_fmt(args)
    }

    fn println_fmt(&self, args: Arguments<'_>) {
        (&self).println_fmt(args)
    }

    fn eprint_fmt(&self, args: Arguments<'_>) {
        (&self).eprint_fmt(args)
    }

    fn eprintln_fmt(&self, args: Arguments<'_>) {
        (&self).eprintln_fmt(args)
    }
}
impl Print for &TestPrint {
    fn print_fmt(&self, args: Arguments<'_>) {
        let mut output = self.stdout.lock().unwrap();
        output.push_str(&format!("{}", args));
    }

    fn println_fmt(&self, args: Arguments<'_>) {
        let mut output = self.stdout.lock().unwrap();
        output.push_str(&format!("{}\n", args));
    }

    fn eprint_fmt(&self, args: Arguments<'_>) {
        let mut output = self.stderr.lock().unwrap();
        output.push_str(&format!("{}", args));
    }

    fn eprintln_fmt(&self, args: Arguments<'_>) {
        let mut output = self.stderr.lock().unwrap();
        output.push_str(&format!("{}\n", args));
    }
}

#[cfg(test)]
mod tests {
    use crate::{kxprint, kxprintln};

    use super::*;

    #[test]
    fn test_standard_print() {
        let printer = Printer::standard();
        // Note: This will actually print to stdout
        printer.println("This is a test");
        kxprintln!(printer, "This is a {} test", "macro");
    }

    #[test]
    fn test_null_print() {
        let printer = Printer::null();
        printer.println("This should not appear anywhere");
        kxprintln!(printer, "This should also, {}", "not appear anywhere");
    }

    #[test]
    fn test_test_print() {
        let printer = Printer::test();
        // we are interleaving printing, with assertions, so just extraxt the TestPrinter reference now
        let test_print = printer.as_test().unwrap();

        // Test stdout functions
        printer.print("Hello");
        kxprint!(printer, " ");
        printer.println("World");
        assert_eq!(test_print.output(), "Hello World\n");

        // Test stderr functions
        printer.eprint("Error: ");
        printer.eprintln("something went wrong");
        assert_eq!(test_print.stderr(), "Error: something went wrong\n");

        // Test clear function clears both buffers
        test_print.clear();
        assert_eq!(test_print.output(), "");
        assert_eq!(test_print.stderr(), "");

        // Verify separate stdout/stderr streams
        printer.println("info: running task");
        printer.eprintln("error: task failed");
        assert_eq!(test_print.output(), "info: running task\n");
        assert_eq!(test_print.stderr(), "error: task failed\n");

        test_print.clear();
        printer.println("New message");
        kxprintln!(printer, "second line");
        assert_eq!(test_print.output(), "New message\nsecond line\n");
    }

    #[test]
    fn test_print_in_function() {
        fn print_message(printer: &Printer) {
            printer.println("Test message");
            kxprintln!(printer, "{} test message", "Second");
        }

        let printer = Printer::test();
        let test_print = printer.as_test().unwrap();
        print_message(&printer);
        assert_eq!(test_print.output(), "Test message\nSecond test message\n");
    }
}