spinoso_string/enc/binary/
inspect.rs

1use core::iter::FusedIterator;
2use core::slice;
3use core::str::Chars;
4
5use scolapasta_string_escape::Literal;
6
7use super::BinaryString;
8use crate::inspect::Flags;
9
10#[derive(Debug, Clone)]
11#[must_use = "this `Inspect` is an `Iterator`, which should be consumed if constructed"]
12pub struct Inspect<'a> {
13    flags: Flags,
14    literal: Chars<'static>,
15    bytes: slice::Iter<'a, u8>,
16}
17
18impl<'a> From<&'a BinaryString> for Inspect<'a> {
19    #[inline]
20    fn from(value: &'a BinaryString) -> Self {
21        Self::new(value.as_slice())
22    }
23}
24
25impl<'a> Inspect<'a> {
26    /// Construct a binary `Inspect` for the given byte slice.
27    ///
28    /// This constructor produces inspect contents like `"fred"`.
29    #[inline]
30    fn new(bytes: &'a [u8]) -> Self {
31        Self {
32            flags: Flags::DEFAULT,
33            literal: "".chars(),
34            bytes: bytes.iter(),
35        }
36    }
37}
38
39impl Default for Inspect<'_> {
40    /// Construct an `Inspect` that will render debug output for the empty
41    /// slice.
42    ///
43    /// This constructor produces inspect contents like `""`.
44    #[inline]
45    fn default() -> Self {
46        Self::new(b"")
47    }
48}
49
50impl Iterator for Inspect<'_> {
51    type Item = char;
52
53    #[inline]
54    fn next(&mut self) -> Option<Self::Item> {
55        if let Some(ch) = self.flags.emit_leading_quote() {
56            return Some(ch);
57        }
58        if let Some(ch) = self.literal.next() {
59            return Some(ch);
60        }
61        if let Some(&ch) = self.bytes.next() {
62            self.literal = Literal::debug_escape(ch).chars();
63        }
64        if let Some(ch) = self.literal.next() {
65            return Some(ch);
66        }
67        self.flags.emit_trailing_quote()
68    }
69}
70
71impl FusedIterator for Inspect<'_> {}
72
73#[cfg(test)]
74mod tests {
75    use alloc::string::String;
76
77    use super::{BinaryString, Inspect};
78
79    #[test]
80    fn empty() {
81        let s = "";
82        let s = BinaryString::from(s);
83        let inspect = Inspect::from(&s);
84
85        assert_eq!(inspect.collect::<String>(), r#""""#);
86    }
87
88    #[test]
89    fn fred() {
90        let s = "fred";
91        let s = BinaryString::from(s);
92        let inspect = Inspect::from(&s);
93
94        assert_eq!(inspect.collect::<String>(), r#""fred""#);
95    }
96
97    #[test]
98    fn invalid_utf8_byte() {
99        let s = b"\xFF";
100        let s = BinaryString::from(s);
101        let inspect = Inspect::from(&s);
102
103        assert_eq!(inspect.collect::<String>(), r#""\xFF""#);
104    }
105
106    #[test]
107    fn invalid_utf8() {
108        let s = b"invalid-\xFF-utf8";
109        let s = BinaryString::from(s);
110        let inspect = Inspect::from(&s);
111
112        assert_eq!(inspect.collect::<String>(), r#""invalid-\xFF-utf8""#);
113    }
114
115    #[test]
116    fn quote_collect() {
117        let s = r#"a"b"#;
118        let s = BinaryString::from(s);
119        let inspect = Inspect::from(&s);
120        assert_eq!(inspect.collect::<String>(), r#""a\"b""#);
121    }
122
123    #[test]
124    fn quote_iter() {
125        let s = r#"a"b"#;
126        let s = BinaryString::from(s);
127        let mut inspect = Inspect::from(&s);
128
129        assert_eq!(inspect.next(), Some('"'));
130        assert_eq!(inspect.next(), Some('a'));
131        assert_eq!(inspect.next(), Some('\\'));
132        assert_eq!(inspect.next(), Some('"'));
133        assert_eq!(inspect.next(), Some('b'));
134        assert_eq!(inspect.next(), Some('"'));
135        assert_eq!(inspect.next(), None);
136    }
137
138    #[test]
139    fn emoji() {
140        let s = "💎";
141        let s = BinaryString::from(s);
142        let inspect = Inspect::from(&s);
143
144        assert_eq!(inspect.collect::<String>(), r#""\xF0\x9F\x92\x8E""#);
145    }
146
147    #[test]
148    fn unicode_replacement_char() {
149        let s = "�";
150        let s = BinaryString::from(s);
151        let inspect = Inspect::from(&s);
152
153        assert_eq!(inspect.collect::<String>(), r#""\xEF\xBF\xBD""#);
154    }
155
156    #[test]
157    fn escape_slash() {
158        let s = r"\";
159        let s = BinaryString::from(s);
160        let inspect = Inspect::from(&s);
161
162        assert_eq!(inspect.collect::<String>(), r#""\\""#);
163    }
164
165    #[test]
166    fn escape_inner_slash() {
167        let s = r"foo\bar";
168        let s = BinaryString::from(s);
169        let inspect = Inspect::from(&s);
170
171        assert_eq!(inspect.collect::<String>(), r#""foo\\bar""#);
172    }
173
174    #[test]
175    fn nul() {
176        let s = "\0";
177        let s = BinaryString::from(s);
178        let inspect = Inspect::from(&s);
179
180        assert_eq!(inspect.collect::<String>(), r#""\x00""#);
181    }
182
183    #[test]
184    fn del() {
185        let s = "\x7F";
186        let s = BinaryString::from(s);
187        let inspect = Inspect::from(&s);
188
189        assert_eq!(inspect.collect::<String>(), r#""\x7F""#);
190    }
191
192    #[test]
193    fn ascii_control() {
194        let test_cases = [
195            ["\x00", r#""\x00""#],
196            ["\x01", r#""\x01""#],
197            ["\x02", r#""\x02""#],
198            ["\x03", r#""\x03""#],
199            ["\x04", r#""\x04""#],
200            ["\x05", r#""\x05""#],
201            ["\x06", r#""\x06""#],
202            ["\x07", r#""\a""#],
203            ["\x08", r#""\b""#],
204            ["\x09", r#""\t""#],
205            ["\x0A", r#""\n""#],
206            ["\x0B", r#""\v""#],
207            ["\x0C", r#""\f""#],
208            ["\x0D", r#""\r""#],
209            ["\x0E", r#""\x0E""#],
210            ["\x0F", r#""\x0F""#],
211            ["\x10", r#""\x10""#],
212            ["\x11", r#""\x11""#],
213            ["\x12", r#""\x12""#],
214            ["\x13", r#""\x13""#],
215            ["\x14", r#""\x14""#],
216            ["\x15", r#""\x15""#],
217            ["\x16", r#""\x16""#],
218            ["\x17", r#""\x17""#],
219            ["\x18", r#""\x18""#],
220            ["\x19", r#""\x19""#],
221            ["\x1A", r#""\x1A""#],
222            ["\x1B", r#""\e""#],
223            ["\x1C", r#""\x1C""#],
224            ["\x1D", r#""\x1D""#],
225            ["\x1E", r#""\x1E""#],
226            ["\x1F", r#""\x1F""#],
227            ["\x20", r#"" ""#],
228        ];
229        for [s, r] in test_cases {
230            let s = BinaryString::from(s);
231            let inspect = Inspect::from(&s);
232            assert_eq!(inspect.collect::<String>(), r, "For {s:?}, expected {r}");
233        }
234    }
235
236    #[test]
237    fn special_double_quote() {
238        let s = "\x22";
239        let s = BinaryString::from(s);
240        let inspect = Inspect::from(&s);
241
242        assert_eq!(inspect.collect::<String>(), r#""\"""#);
243
244        let s = "\"";
245        let s = BinaryString::from(s);
246        let inspect = Inspect::from(&s);
247
248        assert_eq!(inspect.collect::<String>(), r#""\"""#);
249    }
250
251    #[test]
252    fn special_backslash() {
253        let s = "\x5C";
254        let s = BinaryString::from(s);
255        let inspect = Inspect::from(&s);
256
257        assert_eq!(inspect.collect::<String>(), r#""\\""#);
258
259        let s = "\\";
260        let s = BinaryString::from(s);
261        let inspect = Inspect::from(&s);
262
263        assert_eq!(inspect.collect::<String>(), r#""\\""#);
264    }
265}