From 894219dd77103781f6cef4d57f4951a97612efe0 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 24 Mar 2024 14:47:14 +0900 Subject: [PATCH] formatter: add wrapper that writes labeled heading once with content This will be used to label "Error: " heading and content differently. I want to see an error message in the default (white) color because it's easier to read, but I still want to highlight the "Error: " heading. We can achieve that without introducing new wrapper, but the resulting code would look something like "writeln!(ui.error("Error: ")?, ..)?", and it would get messier if the caller had to suppress io::Error. --- cli/src/formatter.rs | 77 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/cli/src/formatter.rs b/cli/src/formatter.rs index 9adf4dbec..d425188ca 100644 --- a/cli/src/formatter.rs +++ b/cli/src/formatter.rs @@ -73,9 +73,50 @@ where S: AsRef, { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { + self.with_labeled(|formatter| formatter.write_fmt(args)) + } + + fn with_labeled( + &mut self, + write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>, + ) -> io::Result<()> { self.formatter .borrow_mut() - .with_label(self.label.as_ref(), |formatter| formatter.write_fmt(args)) + .with_label(self.label.as_ref(), write_inner) + } +} + +/// Like `LabeledWriter`, but also prints the `heading` once. +/// +/// The `heading` will be printed within the first `write!()` or `writeln!()` +/// invocation, which is handy because `io::Error` can be handled there. +pub struct HeadingLabeledWriter { + writer: LabeledWriter, + heading: Option, +} + +impl HeadingLabeledWriter { + pub fn new(formatter: T, label: S, heading: H) -> Self { + HeadingLabeledWriter { + writer: LabeledWriter::new(formatter, label), + heading: Some(heading), + } + } +} + +impl<'a, T, S, H> HeadingLabeledWriter +where + T: BorrowMut, + S: AsRef, + H: fmt::Display, +{ + pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { + self.writer.with_labeled(|formatter| { + if let Some(heading) = self.heading.take() { + write!(formatter.labeled("heading"), "{heading}")?; + } + formatter.write_fmt(args) + }) } } @@ -1054,6 +1095,40 @@ mod tests { insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" inside "); } + #[test] + fn test_heading_labeled_writer() { + let config = config_from_string( + r#" + colors.inner = "green" + colors."inner heading" = "red" + "#, + ); + let mut output: Vec = vec![]; + let mut formatter: Box = + Box::new(ColorFormatter::for_config(&mut output, &config).unwrap()); + HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Should be noop: "); + let mut writer = HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Heading: "); + write!(writer, "Message").unwrap(); + writeln!(writer, " continues").unwrap(); + drop(formatter); + insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" + Heading: Message continues + "###); + } + + #[test] + fn test_heading_labeled_writer_empty_string() { + let mut output: Vec = vec![]; + let mut formatter: Box = Box::new(PlainTextFormatter::new(&mut output)); + let mut writer = HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Heading: "); + // write_fmt() is called even if the format string is empty. I don't + // know if that's guaranteed, but let's record the current behavior. + write!(writer, "").unwrap(); + write!(writer, "").unwrap(); + drop(formatter); + insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"Heading: "); + } + #[test] fn test_format_recorder() { let mut recorder = FormatRecorder::new();