feat(print): add print module
This commit is contained in:
parent
436ad890d8
commit
bc7219755e
5 changed files with 528 additions and 3 deletions
34
examples/printer.rs
Normal file
34
examples/printer.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use kxio::{kxprintln, print::Printer};
|
||||
|
||||
// Example using kxprintln macro
|
||||
fn greet(printer: &Printer, name: &str) {
|
||||
kxprintln!(printer, "Hello, {}!", name);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Production code example
|
||||
let printer = Printer::standard();
|
||||
kxprintln!(printer, "Macro says: Hello, {}!", "Carol");
|
||||
greet(&printer, "Carol");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use kxio::print::TestPrint;
|
||||
|
||||
#[test]
|
||||
fn test_printer() {
|
||||
// Test code
|
||||
let printer = Printer::test();
|
||||
// Get reference to TestPrinter so we can make assertions
|
||||
let test_print = printer.as_test().unwrap();
|
||||
greet(&printer, "Bob");
|
||||
assert_eq!(test_print.output(), "Hello, Bob!\n");
|
||||
test_print.clear();
|
||||
|
||||
greet(&printer, "Dave");
|
||||
assert_eq!(test_print.output(), "Hello, Dave!\n");
|
||||
}
|
||||
}
|
13
src/lib.rs
13
src/lib.rs
|
@ -1,14 +1,15 @@
|
|||
//! # kxio
|
||||
//!
|
||||
//! `kxio` is a Rust library that provides injectable `FileSystem` and `Network`
|
||||
//! `kxio` is a Rust library that provides injectable `FileSystem`, `Network` and `Printer`
|
||||
//! resources to enhance the testability of your code. By abstracting system-level
|
||||
//! interactions, `kxio` enables easier mocking and testing of code that relies on
|
||||
//! file system and network operations.
|
||||
//! file system, network and print operations.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! - Filesystem Abstraction
|
||||
//! - Network Abstraction
|
||||
//! - Print Abstraction
|
||||
//! - Enhanced Testability
|
||||
//!
|
||||
//! ## Filesystem
|
||||
|
@ -30,6 +31,11 @@
|
|||
//! comprehensive documentation and usage examples, please refer to
|
||||
//! <https://docs.rs/kxio/latest/kxio/net/>
|
||||
//!
|
||||
//! ## Printer
|
||||
//!
|
||||
//! No, not a hardware printer, but console output via the family of `println` macros from the
|
||||
//! Standard Library.
|
||||
//!
|
||||
//! ## Getting Started
|
||||
//!
|
||||
//! Add `kxio` to your `Cargo.toml`:
|
||||
|
@ -42,7 +48,7 @@
|
|||
//! ## Usage
|
||||
//!
|
||||
//! See the example [get.rs](https://git.kemitix.net/kemitix/kxio/src/branch/main/examples/get.rs) for an annotated example on how to use the `kxio` library.
|
||||
//! It covers both the `net` and `fs` modules.
|
||||
//! It covers the `net`, `fs` and `print` modules.
|
||||
//!
|
||||
//! ## Development
|
||||
//!
|
||||
|
@ -65,6 +71,7 @@
|
|||
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
pub mod print;
|
||||
mod result;
|
||||
|
||||
pub use result::{Error, Result};
|
||||
|
|
73
src/print/macros.rs
Normal file
73
src/print/macros.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
#[macro_export]
|
||||
macro_rules! kxprintln {
|
||||
($printer:expr, $($arg:tt)*) => {{
|
||||
$crate::print::Print::println_fmt(&$printer.clone(), format_args!($($arg)*))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kxprint {
|
||||
($printer:expr, $($arg:tt)*) => {{
|
||||
$crate::print::Print::print_fmt(&$printer.clone(), format_args!($($arg)*))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kxeprintln {
|
||||
($printer:expr, $($arg:tt)*) => {{
|
||||
$crate::print::Print::eprintln_fmt(&$printer.clone(), format_args!($($arg)*))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kxeprint {
|
||||
($printer:expr, $($arg:tt)*) => {{
|
||||
$crate::print::Print::eprint_fmt(&$printer.clone(), format_args!($($arg)*))
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::TestPrint;
|
||||
|
||||
#[test]
|
||||
fn test_macro_with_ref_and_owned() {
|
||||
// Test with reference
|
||||
let printer = TestPrint::new();
|
||||
kxprintln!(&printer, "ref test");
|
||||
assert_eq!(printer.output(), "ref test\n");
|
||||
printer.clear();
|
||||
|
||||
// Test with owned value
|
||||
kxprintln!(printer, "owned test");
|
||||
assert_eq!(printer.output(), "owned test\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kxprint_macro() {
|
||||
let printer = TestPrint::new();
|
||||
kxprint!(printer, "Hello {}", "world");
|
||||
assert_eq!(printer.output(), "Hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kxprintln_macro() {
|
||||
let printer = TestPrint::new();
|
||||
kxprintln!(printer, "Hello {}", "world");
|
||||
assert_eq!(printer.output(), "Hello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kxeprint_macro() {
|
||||
let printer = TestPrint::new();
|
||||
kxeprint!(printer, "Error: {}", "file not found");
|
||||
assert_eq!(printer.stderr(), "Error: file not found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kxeprintln_macro() {
|
||||
let printer = TestPrint::new();
|
||||
kxeprintln!(printer, "Error: {}", "permission denied");
|
||||
assert_eq!(printer.stderr(), "Error: permission denied\n");
|
||||
}
|
||||
}
|
343
src/print/mod.rs
Normal file
343
src/print/mod.rs
Normal file
|
@ -0,0 +1,343 @@
|
|||
//! 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;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
68
src/print/printer.rs
Normal file
68
src/print/printer.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use super::{NullPrint, Print, StandardPrint, TestPrint};
|
||||
|
||||
/// A wrapper struct that can contain any implementation of the Print trait
|
||||
#[derive(Clone)]
|
||||
pub enum Printer {
|
||||
Standard(StandardPrint),
|
||||
Null(NullPrint),
|
||||
Test(TestPrint),
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
/// Creates a new Printer wrapping a StandardPrint implementation
|
||||
pub fn standard() -> Self {
|
||||
Self::Standard(StandardPrint)
|
||||
}
|
||||
|
||||
/// Creates a new Printer wrapping a NullPrint implementation
|
||||
pub fn null() -> Self {
|
||||
Self::Null(NullPrint)
|
||||
}
|
||||
|
||||
/// Creates a new Printer wrapping a TestPrint implementation
|
||||
pub fn test() -> Self {
|
||||
Self::Test(TestPrint::new())
|
||||
}
|
||||
|
||||
/// Returns a reference to the wrapped TestPrint implementation if this Printer contains one
|
||||
pub fn as_test(&self) -> Option<&TestPrint> {
|
||||
match self {
|
||||
Self::Test(test_print) => Some(test_print),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Print for Printer {
|
||||
fn print_fmt(&self, args: std::fmt::Arguments<'_>) {
|
||||
match self {
|
||||
Self::Standard(p) => p.print_fmt(args),
|
||||
Self::Null(p) => p.print_fmt(args),
|
||||
Self::Test(p) => p.print_fmt(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn println_fmt(&self, args: std::fmt::Arguments<'_>) {
|
||||
match self {
|
||||
Self::Standard(p) => p.println_fmt(args),
|
||||
Self::Null(p) => p.println_fmt(args),
|
||||
Self::Test(p) => p.println_fmt(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn eprint_fmt(&self, args: std::fmt::Arguments<'_>) {
|
||||
match self {
|
||||
Self::Standard(p) => p.eprint_fmt(args),
|
||||
Self::Null(p) => p.eprint_fmt(args),
|
||||
Self::Test(p) => p.eprint_fmt(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn eprintln_fmt(&self, args: std::fmt::Arguments<'_>) {
|
||||
match self {
|
||||
Self::Standard(p) => p.eprintln_fmt(args),
|
||||
Self::Null(p) => p.eprintln_fmt(args),
|
||||
Self::Test(p) => p.eprintln_fmt(args),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue