use std::fmt::{self, Display, 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 } #[inline] fn should_indent(&self) -> bool { self.was_nl } } pub trait Indent { fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result; } impl<'a, T: Indent> Indent for &'a T { #[inline] fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result { T::write(*self, n, f) } } #[derive(Debug, Clone, Copy)] pub struct DetailIndent; impl Indent for DetailIndent { #[inline] fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result { for _ in 0..n { f.write_str(if LINED { "┆ " } else { " " })?; } 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; } pub fn fmt_report( f: &mut impl Write, opts: &ReportOpts, indent: impl Indent, t: &impl Display, ) -> std::fmt::Result { if opts.indentation > 0 { if opts.should_indent() { indent.write(opts.indentation, f)?; } IndentedWrite { f, n: opts.indentation, indent, } .write_fmt(format_args!("{t}")) } else { f.write_fmt(format_args!("{t}")) } } pub struct IndentedWrite<'a, I: Indent, W: Write> { f: &'a mut W, indent: I, n: usize, } impl<'a, I, W> Write for IndentedWrite<'a, I, W> where I: Indent, W: Write, { #[inline] fn write_str(&mut self, s: &str) -> Result<(), fmt::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 { self.indent.write(self.n, &mut self.f)?; self.f.write_str(s_next)?; s = s_next; } if matches!(s.chars().rev().next(), Some('\n')) { self.indent.write(self.n, &mut self.f)?; } } Ok(()) } #[inline] fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { self.f.write_char(c)?; if c == '\n' { self.indent.write(self.n, &mut self.f)?; } Ok(()) } }