#[derive(Debug, Clone, Copy)] pub struct ReportFmt { indentation: usize, is_first: bool, indent_first: bool, } impl ReportFmt { #[inline] pub fn indent(mut self) -> Self { self.indentation += 1; self } #[inline] pub fn next(mut self) -> Self { self.is_first = false; self } #[inline] pub fn indent_first(mut self, indent_first: bool) -> Self { self.indent_first = indent_first; self } /// Returns the amount of indentation. #[inline] pub fn indentation(&self) -> usize { self.indentation } #[inline] pub fn should_indent(&self) -> bool { self.indent_first || !self.is_first } } impl Default for ReportFmt { #[inline] fn default() -> Self { Self { indentation: 0, indent_first: true, is_first: true, } } } macro_rules! report_write { ($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => { as $crate::Report>::fmt(&format_args!($msg$(, $($tt)+)?), $f, $opts) }; } pub(crate) use report_write; #[derive(Debug, Clone, Copy)] pub(crate) struct Indentation(pub usize); impl std::fmt::Display for Indentation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use std::fmt::Write; for _ in 0..self.0 { f.write_char(' ')?; } Ok(()) } } /// A more flexible formatting type that is a cross between [`std::fmt::Debug`] and /// [`std::fmt::Display`]. pub trait Report { fn fmt(&self, f: &mut std::fmt::Formatter<'_>, opts: &ReportFmt) -> std::fmt::Result; } impl Report for T where T: std::fmt::Display, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>, opts: &ReportFmt) -> std::fmt::Result { if opts.should_indent() { write!(f, "{}", Indentation(opts.indentation()))?; } ::fmt(self, f)?; Ok(()) } }