1use std::io::{Result, Write};
5
6#[derive(Clone, Copy)]
7pub enum IndentConfig {
8 Tab,
9 Space(usize),
10}
11
12pub struct IndentedWriter<T> {
13 out: T,
14 indentation: Vec<u8>,
15 config: IndentConfig,
16 at_begining_of_line: bool,
17}
18
19impl<T> IndentedWriter<T> {
20 pub fn new(out: T, config: IndentConfig) -> Self {
21 Self {
22 out,
23 indentation: Vec::new(),
24 config,
25 at_begining_of_line: true,
26 }
27 }
28
29 pub fn indent(&mut self) {
30 match self.config {
31 IndentConfig::Tab => {
32 self.indentation.push(b'\t');
33 }
34 IndentConfig::Space(n) => {
35 self.indentation.resize(self.indentation.len() + n, b' ');
36 }
37 }
38 }
39
40 pub fn unindent(&mut self) {
41 match self.config {
42 IndentConfig::Tab => {
43 self.indentation.pop();
44 }
45 IndentConfig::Space(n) => {
46 self.indentation
47 .truncate(self.indentation.len().saturating_sub(n));
48 }
49 }
50 }
51}
52
53impl<T: Write> Write for IndentedWriter<T> {
54 fn write(&mut self, mut buf: &[u8]) -> Result<usize> {
55 let mut bytes_written = 0;
56
57 while !buf.is_empty() {
58 let (before_newline, has_newline, after_newline) =
59 if let Some(idx) = buf.iter().position(|&b| b == b'\n') {
60 (&buf[..idx], true, &buf[idx + 1..])
61 } else {
62 (buf, false, &buf[buf.len()..])
63 };
64
65 if self.at_begining_of_line && !before_newline.is_empty() {
66 self.out.write_all(&self.indentation)?;
67 self.at_begining_of_line = false;
68 }
69
70 self.out.write_all(before_newline)?;
71 bytes_written += before_newline.len();
72
73 if has_newline {
74 self.out.write_all(b"\n")?;
75 bytes_written += 1;
76 self.at_begining_of_line = true;
77 }
78
79 buf = after_newline;
80 }
81
82 Ok(bytes_written)
83 }
84
85 fn flush(&mut self) -> Result<()> {
86 self.out.flush()
87 }
88}
89
90#[cfg(test)]
91mod test {
92 use super::*;
93
94 #[test]
95 fn basic() -> Result<()> {
96 let mut buffer: Vec<u8> = Vec::new();
97
98 let mut out = IndentedWriter::new(&mut buffer, IndentConfig::Space(2));
99
100 writeln!(out, "foo")?;
101 out.indent();
102 writeln!(out, "bar")?;
103 writeln!(out)?;
104 writeln!(out, "bar")?;
105 out.indent();
106 writeln!(out, "foobar")?;
107 writeln!(out)?;
108 writeln!(out, "foobar")?;
109 out.unindent();
110 out.unindent();
111 writeln!(out, "foo")?;
112
113 let expect: &[u8] = b"\
114foo
115 bar
116
117 bar
118 foobar
119
120 foobar
121foo
122";
123 assert_eq!(buffer, expect);
124
125 Ok(())
126 }
127}