spinoso_string/inspect.rs
1use core::fmt;
2use core::iter::FusedIterator;
3
4use crate::enc;
5
6/// An iterator that yields a debug representation of a `String` and its byte
7/// contents as a sequence of `char`s.
8///
9/// This struct is created by the [`inspect`] method on [`String`]. See its
10/// documentation for more.
11///
12/// To format a `String` directly into a writer, see [`format_into`] or
13/// [`write_into`].
14///
15/// # Examples
16///
17/// To inspect an empty byte string:
18///
19/// ```
20/// # extern crate alloc;
21/// use alloc::string::String;
22/// # use spinoso_string::Inspect;
23/// let inspect = Inspect::default();
24/// let debug = inspect.collect::<String>();
25/// assert_eq!(debug, r#""""#);
26/// ```
27///
28/// To inspect a well-formed UTF-8 byte string:
29///
30/// ```
31/// # extern crate alloc;
32/// use alloc::string::String;
33/// # use spinoso_string::Inspect;
34/// let s = spinoso_string::String::from("spinoso");
35/// let inspect = s.inspect();
36/// let debug = inspect.collect::<String>();
37/// assert_eq!(debug, "\"spinoso\"");
38/// ```
39///
40/// To inspect a byte string with invalid UTF-8 bytes:
41///
42/// ```
43/// # extern crate alloc;
44/// use alloc::string::String;
45/// # use spinoso_string::Inspect;
46/// let s = spinoso_string::String::utf8(b"invalid-\xFF-utf8".to_vec());
47/// let inspect = s.inspect();
48/// let debug = inspect.collect::<String>();
49/// assert_eq!(debug, r#""invalid-\xFF-utf8""#);
50/// ```
51///
52/// To inspect a binary string:
53///
54/// ```
55/// # extern crate alloc;
56/// use alloc::string::String;
57/// # use spinoso_string::Inspect;
58/// let s = spinoso_string::String::binary("💎".as_bytes().to_vec());
59/// let inspect = s.inspect();
60/// let debug = inspect.collect::<String>();
61/// assert_eq!(debug, r#""\xF0\x9F\x92\x8E""#);
62/// ```
63///
64/// [`inspect`]: crate::String::inspect
65/// [`String`]: crate::String
66/// [`format_into`]: Self::format_into
67/// [`write_into`]: Self::write_into
68#[derive(Default, Debug, Clone)]
69#[must_use = "this `Inspect` is an `Iterator`, which should be consumed if constructed"]
70pub struct Inspect<'a>(enc::Inspect<'a>);
71
72impl Iterator for Inspect<'_> {
73 type Item = char;
74
75 fn next(&mut self) -> Option<Self::Item> {
76 self.0.next()
77 }
78}
79
80impl FusedIterator for Inspect<'_> {}
81
82impl<'a> Inspect<'a> {
83 pub(crate) fn new(value: enc::Inspect<'a>) -> Self {
84 Self(value)
85 }
86
87 /// Write an `Inspect` iterator into the given destination using the debug
88 /// representation of the byte buffer associated with a source `String`.
89 ///
90 /// This formatter writes content like `"spinoso"` and `"invalid-\xFF-utf8"`.
91 /// To see example output of the underlying iterator, see the `Inspect`
92 /// documentation.
93 ///
94 /// To write binary output, use [`write_into`], which requires the **std**
95 /// feature to be activated.
96 ///
97 /// # Errors
98 ///
99 /// If the given writer returns an error as it is being written to, that
100 /// error is returned.
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// # extern crate alloc;
106 /// # use core::fmt::Write;
107 /// use alloc::string::String;
108 /// # use spinoso_string::Inspect;
109 /// let mut buf = String::new();
110 /// let s = spinoso_string::String::from("spinoso");
111 /// let iter = s.inspect();
112 /// iter.format_into(&mut buf);
113 /// assert_eq!(buf, "\"spinoso\"");
114 ///
115 /// let mut buf = String::new();
116 /// let s = spinoso_string::String::utf8(b"\xFF".to_vec());
117 /// let iter = s.inspect();
118 /// iter.format_into(&mut buf);
119 /// assert_eq!(buf, r#""\xFF""#);
120 /// ```
121 ///
122 /// [`write_into`]: Self::write_into
123 #[inline]
124 pub fn format_into<W>(self, mut dest: W) -> fmt::Result
125 where
126 W: fmt::Write,
127 {
128 for ch in self {
129 dest.write_char(ch)?;
130 }
131 Ok(())
132 }
133
134 /// Write an `Inspect` iterator into the given destination using the debug
135 /// representation of the byte buffer associated with a source `String`.
136 ///
137 /// This formatter writes content like `"spinoso"` and `"invalid-\xFF-utf8"`.
138 /// To see example output of the underlying iterator, see the `Inspect`
139 /// documentation.
140 ///
141 /// To write to a [formatter], use [`format_into`].
142 ///
143 /// # Errors
144 ///
145 /// If the given writer returns an error as it is being written to, that
146 /// error is returned.
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// # use std::io::Write;
152 /// # use spinoso_string::{Inspect, String};
153 /// let mut buf = Vec::new();
154 /// let s = String::from("spinoso");
155 /// let iter = s.inspect();
156 /// iter.write_into(&mut buf);
157 /// assert_eq!(buf, &b"\"spinoso\""[..]);
158 ///
159 /// let mut buf = Vec::new();
160 /// let s = String::utf8(b"\xFF".to_vec());
161 /// let iter = s.inspect();
162 /// iter.write_into(&mut buf);
163 /// assert_eq!(buf, &[b'"', b'\\', b'x', b'F', b'F', b'"']);
164 /// ```
165 ///
166 /// [formatter]: fmt::Write
167 /// [`format_into`]: Self::format_into
168 #[inline]
169 #[cfg(feature = "std")]
170 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
171 pub fn write_into<W>(self, mut dest: W) -> std::io::Result<()>
172 where
173 W: std::io::Write,
174 {
175 let mut buf = [0; 4];
176 for ch in self {
177 let utf8 = ch.encode_utf8(&mut buf);
178 dest.write_all(utf8.as_bytes())?;
179 }
180 Ok(())
181 }
182}
183
184/// Helper iterator-ish struct for tracking when to emit wrapping quotes for
185/// inspect iterators.
186#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
187pub struct Flags {
188 bits: u8,
189}
190
191impl Flags {
192 // Bit flags
193 const EMIT_LEADING_QUOTE: Self = Self { bits: 0b0000_0001 };
194 const EMIT_TRAILING_QUOTE: Self = Self { bits: 0b0000_0010 };
195
196 // Initial states
197 pub const DEFAULT: Self = Self {
198 bits: Self::EMIT_LEADING_QUOTE.bits | Self::EMIT_TRAILING_QUOTE.bits,
199 };
200
201 #[inline]
202 pub fn emit_leading_quote(&mut self) -> Option<char> {
203 if (self.bits & Self::EMIT_LEADING_QUOTE.bits) == Self::EMIT_LEADING_QUOTE.bits {
204 self.bits &= !Self::EMIT_LEADING_QUOTE.bits;
205 Some('"')
206 } else {
207 None
208 }
209 }
210
211 #[inline]
212 pub fn emit_trailing_quote(&mut self) -> Option<char> {
213 if (self.bits & Self::EMIT_TRAILING_QUOTE.bits) == Self::EMIT_TRAILING_QUOTE.bits {
214 self.bits &= !Self::EMIT_TRAILING_QUOTE.bits;
215 Some('"')
216 } else {
217 None
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::Flags;
225
226 #[test]
227 fn flags_default_emit_quotes() {
228 let mut flags = Flags::DEFAULT;
229
230 assert_eq!(flags.emit_leading_quote(), Some('"'));
231 assert_eq!(flags.emit_leading_quote(), None);
232
233 assert_eq!(flags.emit_trailing_quote(), Some('"'));
234 assert_eq!(flags.emit_trailing_quote(), None);
235 }
236}