embedded_cli/
writer.rs

1use core::{convert::Infallible, fmt::Debug};
2
3use embedded_io::{Error, ErrorType, Write};
4use ufmt::uWrite;
5
6use crate::codes;
7
8pub struct Writer<'a, W: Write<Error = E>, E: Error> {
9    last_bytes: [u8; 2],
10    dirty: bool,
11    writer: &'a mut W,
12}
13
14impl<'a, W: Write<Error = E>, E: Error> Debug for Writer<'a, W, E> {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        f.debug_struct("Writer")
17            .field("last_bytes", &self.last_bytes)
18            .field("dirty", &self.dirty)
19            .finish()
20    }
21}
22
23impl<'a, W: Write<Error = E>, E: Error> Writer<'a, W, E> {
24    pub fn new(writer: &'a mut W) -> Self {
25        Self {
26            last_bytes: [0; 2],
27            dirty: false,
28            writer,
29        }
30    }
31
32    pub(crate) fn is_dirty(&self) -> bool {
33        self.dirty
34            && (self.last_bytes[0] != codes::CARRIAGE_RETURN
35                || self.last_bytes[1] != codes::LINE_FEED)
36    }
37
38    pub fn write_str(&mut self, mut text: &str) -> Result<(), E> {
39        while !text.is_empty() {
40            if let Some(pos) = text.as_bytes().iter().position(|&b| b == codes::LINE_FEED) {
41                // SAFETY: pos is inside text slice
42                let line = unsafe { text.get_unchecked(..pos) };
43
44                self.writer.write_str(line)?;
45                self.writer.write_str(codes::CRLF)?;
46                // SAFETY: pos is index of existing element so pos + 1 in worst case will be
47                // outside of slice by 1, which is safe (will give empty slice as result)
48                text = unsafe { text.get_unchecked(pos + 1..) };
49                self.dirty = false;
50                self.last_bytes = [0; 2];
51            } else {
52                self.writer.write_str(text)?;
53                self.dirty = true;
54
55                if text.len() > 1 {
56                    self.last_bytes[0] = text.as_bytes()[text.len() - 2];
57                    self.last_bytes[1] = text.as_bytes()[text.len() - 1];
58                } else {
59                    self.last_bytes[0] = self.last_bytes[1];
60                    self.last_bytes[1] = text.as_bytes()[text.len() - 1];
61                }
62                break;
63            }
64        }
65        Ok(())
66    }
67
68    pub fn writeln_str(&mut self, text: &str) -> Result<(), E> {
69        self.writer.write_str(text)?;
70        self.writer.write_str(codes::CRLF)?;
71        self.dirty = false;
72        Ok(())
73    }
74
75    pub fn write_list_element(
76        &mut self,
77        name: &str,
78        description: &str,
79        longest_name: usize,
80    ) -> Result<(), E> {
81        self.write_str("  ")?;
82        self.write_str(name)?;
83        if name.len() < longest_name {
84            for _ in 0..longest_name - name.len() {
85                self.write_str(" ")?;
86            }
87        }
88        self.write_str("  ")?;
89        self.writeln_str(description)?;
90
91        Ok(())
92    }
93
94    pub fn write_title(&mut self, title: &str) -> Result<(), E> {
95        //TODO: add formatting
96        self.write_str(title)?;
97        Ok(())
98    }
99}
100
101impl<'a, W: Write<Error = E>, E: Error> uWrite for Writer<'a, W, E> {
102    type Error = E;
103
104    fn write_str(&mut self, s: &str) -> Result<(), E> {
105        self.write_str(s)
106    }
107}
108
109impl<'a, W: Write<Error = E>, E: Error> core::fmt::Write for Writer<'a, W, E> {
110    fn write_str(&mut self, s: &str) -> core::fmt::Result {
111        self.write_str(s).map_err(|_| core::fmt::Error)?;
112        Ok(())
113    }
114}
115
116pub(crate) trait WriteExt: ErrorType {
117    /// Write and flush all given bytes
118    fn flush_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
119
120    fn flush_str(&mut self, text: &str) -> Result<(), Self::Error>;
121
122    fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
123
124    fn write_str(&mut self, text: &str) -> Result<(), Self::Error>;
125}
126
127impl<W: Write> WriteExt for W {
128    fn flush_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
129        self.write_bytes(bytes)?;
130        self.flush()
131    }
132
133    fn flush_str(&mut self, text: &str) -> Result<(), Self::Error> {
134        self.flush_bytes(text.as_bytes())
135    }
136
137    fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
138        self.write_all(bytes)
139    }
140
141    fn write_str(&mut self, text: &str) -> Result<(), Self::Error> {
142        self.write_bytes(text.as_bytes())
143    }
144}
145
146#[derive(Debug)]
147pub struct EmptyWriter;
148
149impl ErrorType for EmptyWriter {
150    type Error = Infallible;
151}
152
153impl Write for EmptyWriter {
154    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
155        Ok(buf.len())
156    }
157
158    fn flush(&mut self) -> Result<(), Self::Error> {
159        Ok(())
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use crate::writer::{EmptyWriter, Writer};
166
167    #[test]
168    fn detect_dirty() {
169        let mut writer = EmptyWriter;
170        let mut writer = Writer::new(&mut writer);
171
172        assert!(!writer.is_dirty());
173
174        writer.write_str("abc").unwrap();
175        assert!(writer.is_dirty());
176
177        writer.write_str("\r").unwrap();
178        assert!(writer.is_dirty());
179
180        writer.write_str("\n").unwrap();
181        assert!(!writer.is_dirty());
182
183        writer.write_str("abc\r\n").unwrap();
184        assert!(!writer.is_dirty());
185    }
186}