use std::io::{Result, Write};
#[derive(Clone, Copy, Debug)]
pub enum IndentConfig {
Tab,
Space(usize),
}
#[derive(Clone, Copy, Default)]
pub struct Newlines {
pub after_open: usize,
pub after_close: usize,
}
impl Newlines {
pub const NONE: Self = Self {
after_open: 0,
after_close: 0,
};
pub const OPEN: Self = Self {
after_open: 1,
after_close: 0,
};
pub const CLOSE: Self = Self {
after_open: 0,
after_close: 1,
};
pub const BOTH: Self = Self {
after_open: 1,
after_close: 1,
};
}
pub trait IndentWrite: Write {
fn config(&self) -> IndentConfig;
fn indent(&mut self) {}
fn unindent(&mut self) {}
fn block(&mut self, newlines: Newlines) -> Result<Block<'_>>
where
Self: Sized,
{
write!(self, "{{")?;
for _ in 0..newlines.after_open {
writeln!(self)?;
}
self.indent();
Ok(Block {
writer: self,
newlines,
})
}
fn child<W: Write>(&self, out: W) -> IndentedWriter<W>
where
Self: Sized,
{
IndentedWriter::new(out, self.config())
}
fn child_writer(&mut self) -> IndentedWriter<&mut Self>
where
Self: Sized,
{
let config = self.config();
IndentedWriter::new(self, config)
}
}
pub struct Block<'a> {
writer: &'a mut dyn IndentWrite,
newlines: Newlines,
}
impl Write for Block<'_> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.writer.write(buf)
}
fn flush(&mut self) -> Result<()> {
self.writer.flush()
}
}
impl IndentWrite for Block<'_> {
fn config(&self) -> IndentConfig {
self.writer.config()
}
fn indent(&mut self) {
self.writer.indent();
}
fn unindent(&mut self) {
self.writer.unindent();
}
}
impl Drop for Block<'_> {
fn drop(&mut self) {
self.writer.unindent();
let _ = write!(self.writer, "}}");
for _ in 0..self.newlines.after_close {
let _ = writeln!(self.writer);
}
}
}
pub struct IndentedWriter<T> {
out: T,
indentation: Vec<u8>,
config: IndentConfig,
at_beginning_of_line: bool,
}
impl<T> IndentedWriter<T> {
pub const fn new(out: T, config: IndentConfig) -> Self {
Self {
out,
indentation: Vec::new(),
config,
at_beginning_of_line: true,
}
}
}
impl<T: Write> IndentWrite for IndentedWriter<T> {
fn config(&self) -> IndentConfig {
self.config
}
fn indent(&mut self) {
match self.config {
IndentConfig::Tab => {
self.indentation.push(b'\t');
}
IndentConfig::Space(n) => {
self.indentation.resize(self.indentation.len() + n, b' ');
}
}
}
fn unindent(&mut self) {
match self.config {
IndentConfig::Tab => {
self.indentation.pop();
}
IndentConfig::Space(n) => {
self.indentation
.truncate(self.indentation.len().saturating_sub(n));
}
}
}
}
impl<T: Write> Write for IndentedWriter<T> {
fn write(&mut self, mut buf: &[u8]) -> Result<usize> {
let mut bytes_written = 0;
while !buf.is_empty() {
let (before_newline, has_newline, after_newline) =
buf.iter().position(|&b| b == b'\n').map_or_else(
|| (buf, false, &buf[buf.len()..]),
|idx| (&buf[..idx], true, &buf[idx + 1..]),
);
if self.at_beginning_of_line && !before_newline.is_empty() {
self.out.write_all(&self.indentation)?;
self.at_beginning_of_line = false;
}
self.out.write_all(before_newline)?;
bytes_written += before_newline.len();
if has_newline {
self.out.write_all(b"\n")?;
bytes_written += 1;
self.at_beginning_of_line = true;
}
buf = after_newline;
}
Ok(bytes_written)
}
fn flush(&mut self) -> Result<()> {
self.out.flush()
}
}
pub fn with_block<F>(w: &mut dyn IndentWrite, newlines: Newlines, f: F) -> Result<()>
where
F: FnOnce(&mut dyn IndentWrite) -> Result<()>,
{
write!(w, "{{")?;
for _ in 0..newlines.after_open {
writeln!(w)?;
}
w.indent();
let result = f(w);
w.unindent();
write!(w, "}}")?;
for _ in 0..newlines.after_close {
writeln!(w)?;
}
result
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn basic() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut out = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
writeln!(out, "foo")?;
out.indent();
writeln!(out, "bar")?;
writeln!(out)?;
writeln!(out, "bar")?;
out.indent();
writeln!(out, "foobar")?;
writeln!(out)?;
writeln!(out, "foobar")?;
out.unindent();
out.unindent();
writeln!(out, "foo")?;
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @"
foo
bar
bar
foobar
foobar
foo
");
Ok(())
}
#[test]
fn function_block() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut w = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
write!(w, "fn foo() ")?;
{
let mut w = w.block(Newlines::BOTH)?;
writeln!(w, r#"let _ = "hello";"#)?;
}
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @r#"
fn foo() {
let _ = "hello";
}
"#);
Ok(())
}
#[test]
fn closure_block() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut w = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
write!(w, "fn foo() ")?;
{
let mut w = w.block(Newlines::OPEN)?;
write!(w, r#"let _ = "hello";"#)?;
}
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @r#"
fn foo() {
let _ = "hello";}
"#);
Ok(())
}
#[test]
fn trailing_block() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut w = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
write!(w, "fn foo() ")?;
{
let mut w = w.block(Newlines::CLOSE)?;
write!(w, r#"let _ = "hello";"#)?;
}
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @r#"fn foo() {let _ = "hello";}"#);
Ok(())
}
#[test]
fn child_inherits_config() -> Result<()> {
let mut parent_buf: Vec<u8> = Vec::new();
let parent = IndentedWriter::new(&mut parent_buf, IndentConfig::Space(3));
let mut child_buf: Vec<u8> = Vec::new();
let mut child = parent.child(&mut child_buf);
writeln!(child, "top")?;
child.indent();
writeln!(child, "indented")?;
child.unindent();
writeln!(child, "bottom")?;
drop(child);
assert!(parent_buf.is_empty());
insta::assert_snapshot!(String::from_utf8(child_buf).unwrap(), @"
top
indented
bottom
");
Ok(())
}
#[test]
fn child_writer_uses_parents_buffer() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut parent = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
writeln!(parent, "outer")?;
parent.indent();
{
let mut child = parent.child_writer();
writeln!(child, "middle")?; child.indent();
writeln!(child, "inner")?; }
parent.unindent();
writeln!(parent, "end")?;
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @"
outer
middle
inner
end
");
Ok(())
}
#[test]
fn child_writer_through_dyn_indent_write() -> Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let mut parent = IndentedWriter::new(&mut buffer, IndentConfig::Space(4));
parent.indent();
let w: &mut dyn IndentWrite = &mut parent;
{
let config = w.config();
let mut child = IndentedWriter::new(&mut *w, config);
writeln!(child, "content")?; child.indent();
writeln!(child, "nested")?; }
insta::assert_snapshot!(String::from_utf8(buffer).unwrap(), @"
content
nested
");
Ok(())
}
}