Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Style::with_writer() method, or an RAII guard equivalent #41

Open
sunshowers opened this issue Dec 14, 2021 · 1 comment
Open

Comments

@sunshowers
Copy link
Contributor

sunshowers commented Dec 14, 2021

I have an interesting use case:

  • I'd like to print out a long string representing stdout from a process
  • I'd like to apply a color to the entire string
  • I'd like to strip ANSI escape codes within the string

This doesn't seem possible with owo-colors, but it seems like it would be possible with a little bit of infrastructure:

impl Style {
    fn with_writer<F, T, E, W>(writer: W, f: F) -> Result<T, WithWriterError<E>>
        where F: FnOnce(W) -> Result<T, E>, W: std::io::Write,
}

// this is optional but more generic than forcing F to return std::io::Write 
enum WithWriterError<E> {
    Internal(std::io::Error),
    Callback(E),
}

impl WithWriterError<std::io::Error> {
    // flattens the error in case f returns std::io::Error, which is the common case
    pub fn flatten(self) -> std::io::Error {
        ...
    }
}

This would make it possible to write:

style.with_writer(&mut writer, |writer| {
    let mut no_color = strip_ansi_escapes::Writer(writer);
    no_color.write_all(stdout);
}).map_err(|err| err.flatten())?;

The callback pattern can also makes it more robust to panics, assuming an RAII guard is used. (Or actually, it might be a better design to just make an RAII guard part of the public API!)

What do you think?

@sunshowers sunshowers changed the title Add a Style::with_writer() method Add a Style::with_writer() method, or an RAII guard equivalent Dec 14, 2021
@frantisekhanzlikbl
Copy link

frantisekhanzlikbl commented Jul 13, 2022

This would be very useful to me too.

An alternative way to achieve the same thing (although I am not sure about the exception safety of such a solution) would be to have a writer wrapper in the spirit of (not tested, but it compiles)

struct StyledWrite<'a, T: Write> {
	style: Style,
	inner: &'a mut T,
}

impl<'a, T: Write> StyledWrite<'a, T> {
	fn new(inner: &'a mut T, style: Style) -> Self {
		// write the style prefix sequence into inner here
		Self { inner, style }
	}
}

impl<T: Write> Drop for StyledWrite<'_, T> {
	fn drop(&mut self) {
		// write the style suffix sequence into inner here
	}
}

impl<T: Write> Write for StyledWrite<'_, T> {
	fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
		self.inner.write(buf)
	}

	fn flush(&mut self) -> std::io::Result<()> {
		self.inner.flush()
	}
}

trait WriteExt<T: Write> {
	fn styled(&mut self, style: Style) -> StyledWrite<'_, T>;

	fn red(&mut self) -> StyledWrite<'_, T>;
}

impl<T> WriteExt<T> for T
where
	T: Write,
{
	fn styled(&mut self, style: Style) -> StyledWrite<'_, T> {
		StyledWrite::new(self, style)
	}

	fn red(&mut self) -> StyledWrite<'_, T> {
		self.styled(Style::new().red())
	}
}

which would enable an even nicer API for styling the whole writer for a limited time:

write!(myWrite.red(), "everything is red!");
write!(myWrite.styled(Style::new().bold()), "everything is bold!");

I have not filled out the parts which write the boundary sequences because I am not sure how to properly use the fmt_prefix methods. In my code, I have a special struct that just wraps a style and defers to fmt_prefix in its Display implementation, because that's the only way I have managed to get my hands on the Formatter instance that fmt_prefix requires.

UPDATE: playing with this a little more, it works pretty well, but the writing of a suffix sequence in drop is not infallible, and I am not sure what the proper way of handling errors in drop is. unwrap is IMO pretty unacceptable, because it makes proper error handling impossible, but ignoring the error also does not feel right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants