use std::fmt::Write; #[derive(Debug, Clone, Copy, Default)] pub struct ReportOpts { indentation: usize, was_nl: bool, } impl ReportOpts { #[inline] pub fn indent(mut self) -> Self { self.indentation += 1; self.next_line() } #[inline] pub fn next_line(mut self) -> Self { self.was_nl = true; self } #[inline] pub fn next(mut self) -> Self { self.was_nl = false; self } fn should_indent(&self) -> bool { self.was_nl } } macro_rules! report_write { ($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => { <::std::fmt::Arguments<'_> as $crate::Report>::fmt(&::std::format_args!($msg$(, $($tt)+)?), $f, $opts) }; } pub(crate) use report_write; fn write_indent(n: usize, f: &mut impl Write) -> std::fmt::Result { for _ in 0..n { f.write_str(" ")?; } Ok(()) } /// `Report` should format the output in a manner suitable for debugging, similar to [`std::fmt::Debug`] in terms of detail, but similar to [`std::fmt::Display`] in terms of readability. The options passed to [`Self::fmt`] must be respected by all implementations. pub trait Report { /// Formats the value using the given formatter and options. fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result; } impl Report for T where T: std::fmt::Display, { fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result { use std::fmt::Error; struct IndentedWrite<'a, W: Write> { f: &'a mut W, n: usize, } impl<'a, W> Write for IndentedWrite<'a, W> where W: Write, { fn write_str(&mut self, s: &str) -> Result<(), Error> { // TODO: any room for optimization? // iterates over the lines where each str ends with the line terminator. // after giving this a bit of thought I think it is best to indent after any // trailing newline. let mut ss = s.split_inclusive('\n'); if let Some(mut s) = ss.next() { self.f.write_str(s)?; for s_next in ss { write_indent(self.n, &mut self.f)?; self.f.write_str(s_next)?; s = s_next; } if matches!(s.chars().rev().next(), Some('\n')) { write_indent(self.n, &mut self.f)?; } } Ok(()) } fn write_char(&mut self, c: char) -> Result<(), Error> { self.f.write_char(c)?; if c == '\n' { write_indent(self.n, &mut self.f)?; } Ok(()) } } if opts.indentation > 0 { if opts.should_indent() { write_indent(opts.indentation, f)?; } IndentedWrite { f, n: opts.indentation, } .write_fmt(format_args!("{self}")) } else { f.write_fmt(format_args!("{self}")) } } }