pub(super) struct Formatter {
out: String,
indent: usize,
}
impl Formatter {
pub(super) const fn new() -> Self {
Self {
out: String::new(),
indent: 0,
}
}
pub(super) fn finish(mut self) -> String {
let trimmed_len = self.out.trim_end_matches([' ', '\t']).len();
self.out.truncate(trimmed_len);
self.out
}
pub(super) fn write_str(&mut self, s: &str) {
self.out.push_str(s);
}
pub(super) fn write_char(&mut self, c: char) {
self.out.push(c);
}
pub(super) fn write_indent(&mut self, n: usize) {
for _ in 0..n {
self.out.push(' ');
}
}
pub(super) fn indent_here(&mut self) {
self.write_indent(self.indent);
}
pub(super) fn with_indent<F: FnOnce(&mut Self)>(&mut self, delta: usize, f: F) {
self.indent += delta;
f(self);
self.indent -= delta;
}
pub(super) fn out(&self) -> &str {
&self.out
}
pub(super) fn insert_at(&mut self, pos: usize, s: &str) {
self.out.insert_str(pos, s);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finish_trims_trailing_spaces_and_tabs_but_keeps_newlines() {
let mut f = Formatter::new();
f.write_str("hello\n \t ");
assert_eq!(f.finish(), "hello\n");
let mut f = Formatter::new();
f.write_str("body\n");
assert_eq!(f.finish(), "body\n");
let mut f = Formatter::new();
f.write_str(" ");
assert_eq!(f.finish(), "");
}
#[test]
fn with_indent_restores_on_normal_return() {
let mut f = Formatter::new();
f.with_indent(4, |g| {
g.indent_here();
g.write_str("x");
});
f.write_str("|after");
assert_eq!(f.finish(), " x|after");
}
}