109 lines
3.2 KiB
Rust
109 lines
3.2 KiB
Rust
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<T> 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}"))
|
|
}
|
|
}
|
|
}
|