use crate::write_str::WriteStr;
use std::io;
#[expect(missing_debug_implementations)]
pub enum Format<'a> {
Uniform {
indentation: &'static str,
},
Numbered {
ind: usize,
},
Custom {
inserter: &'a mut Inserter,
},
}
#[expect(missing_debug_implementations)]
pub struct Indented<'a, D: ?Sized> {
inner: &'a mut D,
needs_indent: bool,
format: Format<'a>,
}
pub type Inserter = dyn FnMut(usize, &mut dyn WriteStr) -> io::Result<()>;
impl Format<'_> {
fn insert_indentation(&mut self, line: usize, f: &mut dyn WriteStr) -> io::Result<()> {
match self {
Format::Uniform { indentation } => write!(f, "{indentation}"),
Format::Numbered { ind } => {
if line == 0 {
write!(f, "{ind: >4}: ")
} else {
write!(f, " ")
}
}
Format::Custom { inserter } => inserter(line, f),
}
}
}
impl<'a, D: ?Sized> Indented<'a, D> {
pub fn ind(self, ind: usize) -> Self {
self.with_format(Format::Numbered { ind })
}
pub fn with_str(self, indentation: &'static str) -> Self {
self.with_format(Format::Uniform { indentation })
}
pub fn with_format(mut self, format: Format<'a>) -> Self {
self.format = format;
self
}
pub fn into_inner(self) -> &'a mut D {
self.inner
}
}
impl<T> WriteStr for Indented<'_, T>
where
T: WriteStr + ?Sized,
{
fn write_str(&mut self, s: &str) -> io::Result<()> {
for (ind, line) in s.split('\n').enumerate() {
if ind > 0 {
self.inner.write_char('\n')?;
self.needs_indent = true;
}
if self.needs_indent {
if line.is_empty() {
continue;
}
self.format.insert_indentation(ind, &mut self.inner)?;
self.needs_indent = false;
}
self.inner.write_fmt(format_args!("{line}"))?;
}
Ok(())
}
fn write_str_flush(&mut self) -> io::Result<()> {
self.inner.write_str_flush()
}
}
pub fn indented<D: ?Sized>(f: &mut D) -> Indented<'_, D> {
Indented {
inner: f,
needs_indent: true,
format: Format::Uniform {
indentation: " ",
},
}
}