indent_write/fmt.rs
1use core::fmt;
2/// Adapter for writers to indent each line
3///
4/// An `IndentWriter` adapts a [`fmt::Write`] object to insert an indent before
5/// each non-empty line. Specifically, this means it will insert an indent
6/// between each newline when followed by a non-newline.
7///
8/// These writers can be nested to provide increasing levels of indentation.
9///
10/// # Example
11///
12/// ```
13/// # use std::fmt::Write;
14/// use indent_write::fmt::IndentWriter;
15///
16/// let output = String::new();
17///
18/// let mut indented = IndentWriter::new("\t", output);
19///
20/// // Lines will be indented
21/// write!(indented, "Line 1\nLine 2\n");
22///
23/// // Empty lines will not be indented
24/// write!(indented, "\n\nLine 3\n\n");
25///
26/// assert_eq!(indented.get_ref(), "\tLine 1\n\tLine 2\n\n\n\tLine 3\n\n");
27/// ```
28#[derive(Debug, Clone)]
29pub struct IndentWriter<'i, W> {
30 writer: W,
31 indent: &'i str,
32 need_indent: bool,
33}
34
35impl<'i, W: fmt::Write> IndentWriter<'i, W> {
36 /// Create a new [`IndentWriter`].
37 #[inline]
38 pub fn new(indent: &'i str, writer: W) -> Self {
39 Self {
40 writer,
41 indent,
42 need_indent: true,
43 }
44 }
45
46 /// Create a new [`IndentWriter`] which will not add an indent to the first
47 /// written line.
48 ///
49 /// # Example
50 ///
51 /// ```
52 /// # use std::fmt::Write;
53 /// use indent_write::fmt::IndentWriter;
54 ///
55 /// let mut buffer = String::new();
56 /// let mut writer = IndentWriter::new_skip_initial(" ", &mut buffer);
57 ///
58 /// writeln!(writer, "Line 1").unwrap();
59 /// writeln!(writer, "Line 2").unwrap();
60 /// writeln!(writer, "Line 3").unwrap();
61 ///
62 /// assert_eq!(buffer, "Line 1\n Line 2\n Line 3\n")
63 /// ```
64 #[inline]
65 pub fn new_skip_initial(indent: &'i str, writer: W) -> Self {
66 Self {
67 writer,
68 indent,
69 need_indent: false,
70 }
71 }
72
73 /// Extract the writer from the `IndentWriter`, discarding any in-progress
74 /// indent state.
75 #[inline]
76 pub fn into_inner(self) -> W {
77 self.writer
78 }
79
80 /// Get a reference to the wrapped writer
81 #[inline]
82 pub fn get_ref(&self) -> &W {
83 &self.writer
84 }
85
86 /// Get the string being used as an indent for each line
87 #[inline]
88 pub fn indent(&self) -> &'i str {
89 self.indent
90 }
91}
92
93impl<'i, W: fmt::Write> fmt::Write for IndentWriter<'i, W> {
94 fn write_str(&mut self, mut s: &str) -> fmt::Result {
95 loop {
96 match self.need_indent {
97 // We don't need an indent. Scan for the end of the line
98 false => match s.as_bytes().iter().position(|&b| b == b'\n') {
99 // No end of line in the input; write the entire string
100 None => break self.writer.write_str(s),
101
102 // We can see the end of the line. Write up to and including
103 // that newline, then request an indent
104 Some(len) => {
105 let (head, tail) = s.split_at(len + 1);
106 self.writer.write_str(head)?;
107 self.need_indent = true;
108 s = tail;
109 }
110 },
111 // We need an indent. Scan for the beginning of the next
112 // non-empty line.
113 true => match s.as_bytes().iter().position(|&b| b != b'\n') {
114 // No non-empty lines in input, write the entire string
115 None => break self.writer.write_str(s),
116
117 // We can see the next non-empty line. Write up to the
118 // beginning of that line, then insert an indent, then
119 // continue.
120 Some(len) => {
121 let (head, tail) = s.split_at(len);
122 self.writer.write_str(head)?;
123 self.writer.write_str(self.indent)?;
124 self.need_indent = false;
125 s = tail;
126 }
127 },
128 }
129 }
130 }
131
132 fn write_char(&mut self, c: char) -> fmt::Result {
133 // We need an indent, and this is the start of a non-empty line.
134 // Insert the indent.
135 if self.need_indent && c != '\n' {
136 self.writer.write_str(self.indent)?;
137 self.need_indent = false;
138 }
139
140 // This is the end of a non-empty line. Request an indent.
141 if !self.need_indent && c == '\n' {
142 self.need_indent = true;
143 }
144
145 self.writer.write_char(c)
146 }
147}