how.rs/src/report.rs

127 lines
3.2 KiB
Rust

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<const LINED: bool>;
impl<const LINED: bool> Indent for DetailIndent<LINED> {
#[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(())
}
}