Defining a custom ColorDisplay trait

I want to define my own custom trait similar to display, but with the semantics that the output should be colorized.

I also want to implement the Display trait by making use of my custom trait and stripping out the colors.

I'm getting stuck because I'm not sure how I can directly call std::fmt::Display::fmt.

use std::fmt::Write;
use colored::Colorize;
use console::strip_ansi_codes;

/// A trait that mimics the display trait but has the semantics of the output being in color
pub trait ColorDisplay {
    fn color_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;

    fn noncolor_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut buf = String::new();

        // how to explicitly call colored fmt?
        write!(buf, "{}", ...);
        let non_color = strip_ansi_codes(&buf);

        write!(f, "{}", &non_color)
    }
}

struct Foo;
impl ColorDisplay for Foo {
    fn color_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", &"Foo".color(colored::Color::Red).to_string())
    }
}
impl std::fmt::Display for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", ...)
    }
}

I have ... in the sections of code where I'm a bit stuck about how I can make use of the fmt style interface directly. I could make it work by having ColorDisplay return String, but I think that might end up resulting in making extra allocations.

I'm using linux and not worried about supporting windows.

I don't think trying to imitate the API of a formatting trait is a good idea. As a user, you don't interact with fmt::Formatter<'_> directly, it is something that is only ever passed to formatting traits like Display or Debug, when you invoke them through format_args! (or the other formatting macros like write!, println!, etc., which all use format_args! under the hood). You can find a list of all formatting traits here. Since Rust doesn't support custom formatters, your users will never be able to call the methods of your ColorDisplay, as they are unable to get their hands on a &mut std::fmt::Formatter<'_> instance. So I think having your methods return a String or take a buffer they can write to as an argument is a better idea.

2 Likes

You could do something like

pub struct Color<T>(T);
impl<T: ColorDisplay> fmt::Display for Color<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.color_fmt(f)
    }
}

pub struct UnColor<T>(T);
impl<T: ColorDisplay> fmt::Display for UnColor<T> {
    // ... call `noncolor_fmt` this time

And also

impl<T: ?Sized + ColorDisplay> ColorDisplay for &T {
    fn color_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        (**self).color_fmt(f)
    }
    fn noncolor_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        (**self).noncolor_fmt(f)
    }
}

Then you can

pub trait ColorDisplay {
    fn color_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
    fn noncolor_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use fmt::Write;
        let mut buf = String::new();

        write!(buf, "{}", Color(self)).expect("could not write to String");
        let non_color = strip_ansi_codes(&buf);

        write!(f, "{}", &non_color)
    }
}
struct Foo;
impl ColorDisplay for Foo {
    fn color_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", &"Foo".color(colored::Color::Red).to_string())
    }
}

impl std::fmt::Display for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", UnColor(self))
    }
}
2 Likes

Thanks for the suggestions. After jofas pointed out it's difficult to interact with fmt::Formatter I decided to have the interface accept an impl Write:

/// A trait that mimics the display trait but has the semantics of the output being in color
pub trait ColorDisplay {
    fn color_fmt(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result;
    fn noncolor_fmt(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
        let mut buf = String::new();
        self.color_fmt(&mut buf)?;
        write!(f, "{}", &console::strip_ansi_codes(&buf))
    }
}

macro_rules! impl_display_from_color_display {
    ($T:ty) => {
        impl std::fmt::Display for $T {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                self.noncolor_fmt(f)
            }
        }
    };
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.