简体   繁体   中英

How can I simplify repeating function logic

Question

I'm wondering if there is a way to improve the current structure of some functions in my program since I feel there is a fair amount of unwanted repetition happening.

Background

I'm writing a tiny logger so CLI applications can have prettier text in the terminal. I have a couple functions that add some icons to what is going to stdout such as success() , it takes a message and adds a green checkmark icon to it, same with error() , warn() etc. They can all either add a newline character at the end or ignore it depending on if the user called same() before it.

Currently they use the three functions defined below to decide whether or not to add a newline, and whether or not to add a timestamp.

Code

/// Outputs to stdout with an icon
fn output<T: Display>(&mut self, message: T, icon: LogIcon) {
    let timestamp = self.timestamp();

    if self.same_line {
        print!("{} {}{}", icon, timestamp, message);
    } else {
        println!("{} {}{}", icon, timestamp, message);
    }

    self.same_line = false;
}

/// Outputs to stderr with an icon
fn output_error<T: Display>(&mut self, message: T, icon: LogIcon) {
    let timestamp = self.timestamp();

    if self.same_line {
        eprint!("{} {}{}", icon, timestamp, message);
    } else {
        eprintln!("{} {}{}", icon, timestamp, message);
    }

    self.same_line = false;
}

/// Outputs to stdout normally
fn output_normal<T: Display>(&mut self, message: T) {
    let timestamp = self.timestamp();

    if self.same_line {
        print!("{}{}", timestamp, message);
    } else {
        println!("{}{}", timestamp, message);
    }

    self.same_line = false;
}

This is how the success function makes use of output function at the moment:

pub fn success<T: Display>(&mut self, message: T) {
    self.output(message, LogIcon::CheckMark);   
} 

The same applies for all other functions, they either output to stderr or stdout .

You could change same_line into line_ending . Instead of storing true, you would store \\n and always use print!("... {}", ..., &self.line_ending) . I would also add a function pop_line_ending() which returns the stored line ending and clears it.

You can make a function that is generic over implementations of std::io::Writer , and also accepts std::fmt::Arguments , which is the formatting arguments applied at compile time to the input string in a way that can be conveniently passed around.

use std::{fmt, fmt::Display, io};

fn write_output<W: io::Write>(&mut self, args: fmt::Arguments, newline: bool, mut writer: W) {
    write!(writer, "{}", args).unwrap();
    if newline {
        write!(writer, "\n").unwrap();
    }
    writer.flush().unwrap();
}

Then pass either stdout() or stderr() as required in each call, along with the other arguments:

fn output<T: Display>(&mut self, message: T, icon: LogIcon) {
    let timestamp = self.timestamp();
    self.write_output(
        format_args!("{} {}{}", icon, timestamp, message),
        !self.same_line,
        io::stdout(),
    );
    self.same_line = false;
}

fn output_error<T: Display>(&mut self, message: T, icon: LogIcon) {
    let timestamp = self.timestamp();
    self.write_output(
        format_args!("{} {}{}", icon, timestamp, message),
        !self.same_line,
        io::stderr(),
    );
    self.same_line = false;
}

fn output_normal<T: Display>(&mut self, message: T) {
    let timestamp = self.timestamp();
    self.write_output(
        format_args!("{}{}", timestamp, message),
        !self.same_line,
        io::stdout(),
    );
    self.same_line = false;
}

I like Peter Hall's solution, but I think it can be simplified. I got rid of the fmt::Arguments argument and passed in the message and optional icon. Also got rid of the newline argument and used the member variable directly.

use std::{fmt, fmt::Display, io};

struct LogIcon {}

impl Display for LogIcon {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(f, "[ICON]")
    }
}

struct Logger {
    same_line: bool,
}

impl Logger {
    fn write_output<T: Display, W: io::Write>(
        &mut self,
        message: T,
        icon: Option<LogIcon>,
        mut writer: W,
    ) {
        if let Some(icon) = icon {
            write!(writer, "{} ", icon);
        }
        write!(writer, "{}{}", self.timestamp(), message);

        if self.same_line {
            write!(writer, "\n").unwrap();
        }
        writer.flush().unwrap();
        self.same_line = false;
    }

    fn output<T: Display>(&mut self, message: T, icon: LogIcon) {
        self.write_output(message, Some(icon), io::stdout());
    }

    fn output_error<T: Display>(&mut self, message: T, icon: LogIcon) {
        self.write_output(message, Some(icon), io::stderr());
    }

    fn output_normal<T: Display>(&mut self, message: T) {
        self.write_output(message, None, io::stdout())
    }

    fn timestamp(&self) -> &'static str {
        "A TIMESTAMP"
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM