errgonomic/types/
prefixer.rs

1use std::fmt;
2use std::io::{self, Write};
3
4/// A [`Write`] adapter that prefixes each written line.
5///
6/// This type uses a `dyn Write` instead of `impl Write` to avoid a trait-recursion explosion in
7/// [`crate::writeln_error_to_writer`].
8pub struct Prefixer<'w> {
9    /// Prefix for the very first line.
10    pub first_line_prefix: String,
11    /// Prefix for subsequent lines.
12    pub next_line_prefix: String,
13    /// The underlying writer.
14    pub writer: &'w mut dyn Write,
15    /// Whether the next write is still on the first line.
16    pub is_first_line: bool,
17    /// Whether the next write should include a prefix.
18    pub needs_prefix: bool,
19}
20
21impl<'w> fmt::Debug for Prefixer<'w> {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        f.debug_struct("Prefixer")
24            .field("first_line_prefix", &self.first_line_prefix)
25            .field("next_line_prefix", &self.next_line_prefix)
26            .field("is_first_line", &self.is_first_line)
27            .field("needs_prefix", &self.needs_prefix)
28            .finish()
29    }
30}
31
32impl<'w> Prefixer<'w> {
33    /// Creates a new prefixing writer with the provided line prefixes.
34    pub fn new(first_line_prefix: impl Into<String>, next_line_prefix: impl Into<String>, writer: &'w mut dyn Write) -> Self {
35        Self {
36            first_line_prefix: first_line_prefix.into(),
37            next_line_prefix: next_line_prefix.into(),
38            writer,
39            is_first_line: true,
40            needs_prefix: true,
41        }
42    }
43}
44
45impl<'w> Write for Prefixer<'w> {
46    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
47        if buf.is_empty() {
48            return Ok(0);
49        }
50
51        let mut start = 0;
52        while start < buf.len() {
53            if self.needs_prefix {
54                let prefix = if self.is_first_line { &self.first_line_prefix } else { &self.next_line_prefix };
55                self.writer.write_all(prefix.as_bytes())?;
56                self.is_first_line = false;
57                self.needs_prefix = false;
58            }
59
60            match buf[start..].iter().position(|&b| b == b'\n') {
61                Some(relative_idx) => {
62                    let end = start + relative_idx + 1;
63                    self.writer.write_all(&buf[start..end])?;
64                    start = end;
65                    self.needs_prefix = true;
66                }
67                None => {
68                    self.writer.write_all(&buf[start..])?;
69                    start = buf.len();
70                }
71            }
72        }
73
74        Ok(buf.len())
75    }
76
77    fn flush(&mut self) -> io::Result<()> {
78        self.writer.flush()
79    }
80}