str_queue/iter/
chars.rs

1//! Characters iterator.
2
3use core::fmt;
4use core::mem;
5use core::ops::RangeBounds;
6
7use crate::range::CharsRange;
8use crate::utf8::{self, REPLACEMENT_CHAR};
9use crate::{PartialHandling, StrQueue};
10
11/// Iterator of characters in a `StrQueue`.
12#[derive(Debug, Clone)]
13pub struct IntoChars {
14    /// Iterator of the characters in the inner buffer.
15    inner: alloc::collections::vec_deque::IntoIter<u8>,
16    /// Whether an incomplete character (replaced with U+FFFD) should be emitted.
17    should_print_incomplete_bytes: bool,
18}
19
20impl IntoChars {
21    /// Creates an iterator of characters from a `StrQueue`.
22    #[inline]
23    #[must_use]
24    pub(crate) fn new(mut q: StrQueue, partial_handling: PartialHandling) -> Self {
25        let should_print_incomplete_bytes = partial_handling.is_emit() && (q.len_incomplete() != 0);
26        q.inner.truncate(q.len_complete());
27
28        Self {
29            inner: q.inner.into_iter(),
30            should_print_incomplete_bytes,
31        }
32    }
33}
34
35impl Iterator for IntoChars {
36    type Item = char;
37
38    #[inline]
39    fn next(&mut self) -> Option<Self::Item> {
40        utf8::take_char(&mut self.inner)
41            .map(|(c, _len)| c)
42            .or_else(|| {
43                if mem::replace(&mut self.should_print_incomplete_bytes, false) {
44                    Some(REPLACEMENT_CHAR)
45                } else {
46                    None
47                }
48            })
49    }
50}
51
52// TODO: Implement more efficiently.
53impl fmt::Display for IntoChars {
54    /// Note that this might be inefficient compared to [`Fragments::fmt`].
55    /// Use it if possible.
56    ///
57    /// [`Fragments::fmt`]: [`crate::Fragments`]
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        self.clone().try_for_each(|c| c.fmt(f))
60    }
61}
62
63/// Iterator of characters in a `StrQueue`.
64#[derive(Debug, Clone)]
65pub struct Chars<'a> {
66    /// Range.
67    range: CharsRange<'a>,
68}
69
70impl<'a> Chars<'a> {
71    /// Creates a new `CharsRange` for the queue.
72    ///
73    /// # Panics
74    ///
75    /// Panics if the start bound of the range does not lie on UTF-8 sequence boundary.
76    #[inline]
77    #[must_use]
78    pub(crate) fn new<R>(queue: &'a StrQueue, range: R, partial_handling: PartialHandling) -> Self
79    where
80        R: RangeBounds<usize>,
81    {
82        let mut range = queue.chars_range(range);
83        if partial_handling.is_ignore() {
84            range.trim_last_incomplete_char();
85        }
86        Self { range }
87    }
88
89    /// Creates a new `CharsRange` from the chars range.
90    #[inline]
91    #[must_use]
92    pub(crate) fn from_range(range: CharsRange<'a>) -> Self {
93        Self { range }
94    }
95}
96
97impl Iterator for Chars<'_> {
98    type Item = char;
99
100    #[inline]
101    fn next(&mut self) -> Option<Self::Item> {
102        self.range.pop_char_replaced()
103    }
104}
105
106impl fmt::Display for Chars<'_> {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        self.range.fmt(f)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use crate::{PartialHandling, StrQueue};
115
116    use alloc::string::String;
117
118    /// Asserts that the lossy string conversion is consistent with `String::from_utf8_lossy`.
119    fn assert_consistent_with_std(bytes: &[u8]) {
120        let mut queue = StrQueue::new();
121        queue.push_bytes(bytes);
122
123        assert_eq!(
124            queue.chars(PartialHandling::Emit).collect::<String>(),
125            String::from_utf8_lossy(bytes)
126        );
127        assert_eq!(
128            queue.into_chars(PartialHandling::Emit).collect::<String>(),
129            String::from_utf8_lossy(bytes)
130        );
131    }
132
133    // ```
134    // {
135    // let mut q = StrQueue::new();
136    // q.push_bytes(bytes);
137    // q.chars(PartialHandling::Emit).collect::<String>
138    // }
139    // ```
140    // should always be equal to `String::from_utf8_lossy(bytes)`.
141    #[test]
142    fn consistency_with_std_from_utf8_lossy() {
143        assert_consistent_with_std(b"hello");
144        assert_consistent_with_std(b"\xffhello");
145        assert_consistent_with_std(b"\xff\xffhello");
146        assert_consistent_with_std(b"\xff\xff\xffhello");
147        assert_consistent_with_std(b"\xff\xff\xff\xffhello");
148        assert_consistent_with_std(b"\xff\xff\xff\xff\xffhello");
149
150        assert_consistent_with_std(b"\xe3\x81\x82hello");
151        assert_consistent_with_std(b"\xe3\xe3\x81\x82hello");
152        assert_consistent_with_std(b"\xe3z\xe3\x81\x82hello");
153        assert_consistent_with_std(b"\xe3\x81\xe3\x81\x82hello");
154        assert_consistent_with_std(b"\xe3z\xe3\x81\x82hello");
155        assert_consistent_with_std(b"z\x81\xe3\x81\x82hello");
156    }
157}