use std::fmt::{self, Write};
const DEFAULT_INDENT: usize = 4;
#[derive(Debug)]
pub struct Formatter<'a> {
dst: &'a mut String,
spaces: usize,
scope: Vec<String>,
}
impl<'a> Formatter<'a> {
pub fn new(dst: &'a mut String) -> Self {
Self {
dst,
spaces: 0,
scope: vec![],
}
}
pub fn get_indent(&self) -> usize {
self.spaces
}
pub fn scope<F, R>(&mut self, name: &str, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.scope.push(name.to_string());
let ret = f(self);
self.scope.pop();
ret
}
pub fn write_scoped_name(&mut self, name: &str) -> fmt::Result {
write!(self, " ")?;
for s in &self.scope {
self.dst.push_str(s);
self.dst.push_str("::");
}
write!(self, "{name}")
}
pub fn block<F>(&mut self, f: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
if !self.is_start_of_line() {
write!(self, " ")?;
}
writeln!(self, "{{")?;
self.indent(f)?;
write!(self, "}}")?;
Ok(())
}
pub fn indent<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.spaces += DEFAULT_INDENT;
let ret = f(self);
self.spaces -= DEFAULT_INDENT;
ret
}
pub fn is_start_of_line(&self) -> bool {
self.dst.is_empty() || self.dst.ends_with('\n')
}
fn push_spaces(&mut self) {
for _ in 0..self.spaces {
self.dst.push(' ');
}
}
}
impl fmt::Write for Formatter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut first = true;
let mut should_indent = self.is_start_of_line();
for line in s.lines() {
if !first {
self.dst.push('\n');
}
first = false;
let do_indent = should_indent && !line.is_empty() && line.as_bytes()[0] != b'\n';
if do_indent {
self.push_spaces();
}
should_indent = true;
self.dst.push_str(line);
}
if s.as_bytes().last() == Some(&b'\n') {
self.dst.push('\n');
}
Ok(())
}
}