str-queue 0.0.1

Queue for a string
Documentation
//! Characters iterator.

use core::fmt;
use core::mem;
use core::ops::RangeBounds;

use crate::range::CharsRange;
use crate::utf8::{self, REPLACEMENT_CHAR};
use crate::{PartialHandling, StrQueue};

/// Iterator of characters in a `StrQueue`.
#[derive(Debug, Clone)]
pub struct IntoChars {
    /// Iterator of the characters in the inner buffer.
    inner: alloc::collections::vec_deque::IntoIter<u8>,
    /// Whether an incomplete character (replaced with U+FFFD) should be emitted.
    should_print_incomplete_bytes: bool,
}

impl IntoChars {
    /// Creates an iterator of characters from a `StrQueue`.
    #[inline]
    #[must_use]
    pub(crate) fn new(mut q: StrQueue, partial_handling: PartialHandling) -> Self {
        let should_print_incomplete_bytes = partial_handling.is_emit() && (q.len_incomplete() != 0);
        q.inner.truncate(q.len_complete());

        Self {
            inner: q.inner.into_iter(),
            should_print_incomplete_bytes,
        }
    }
}

impl Iterator for IntoChars {
    type Item = char;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        utf8::take_char(&mut self.inner)
            .map(|(c, _len)| c)
            .or_else(|| {
                if mem::replace(&mut self.should_print_incomplete_bytes, false) {
                    Some(REPLACEMENT_CHAR)
                } else {
                    None
                }
            })
    }
}

// TODO: Implement more efficiently.
impl fmt::Display for IntoChars {
    /// Note that this might be inefficient compared to [`Fragments::fmt`].
    /// Use it if possible.
    ///
    /// [`Fragments::fmt`]: [`crate::Fragments`]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.clone().try_for_each(|c| c.fmt(f))
    }
}

/// Iterator of characters in a `StrQueue`.
#[derive(Debug, Clone)]
pub struct Chars<'a> {
    /// Range.
    range: CharsRange<'a>,
}

impl<'a> Chars<'a> {
    /// Creates a new `CharsRange` for the queue.
    ///
    /// # Panics
    ///
    /// Panics if the start bound of the range does not lie on UTF-8 sequence boundary.
    #[inline]
    #[must_use]
    pub(crate) fn new<R>(queue: &'a StrQueue, range: R, partial_handling: PartialHandling) -> Self
    where
        R: RangeBounds<usize>,
    {
        let mut range = queue.chars_range(range);
        if partial_handling.is_ignore() {
            range.trim_last_incomplete_char();
        }
        Self { range }
    }

    /// Creates a new `CharsRange` from the chars range.
    #[inline]
    #[must_use]
    pub(crate) fn from_range(range: CharsRange<'a>) -> Self {
        Self { range }
    }
}

impl Iterator for Chars<'_> {
    type Item = char;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.range.pop_char_replaced()
    }
}

impl fmt::Display for Chars<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.range.fmt(f)
    }
}

#[cfg(test)]
mod tests {
    use crate::{PartialHandling, StrQueue};

    use alloc::string::String;

    /// Asserts that the lossy string conversion is consistent with `String::from_utf8_lossy`.
    fn assert_consistent_with_std(bytes: &[u8]) {
        let mut queue = StrQueue::new();
        queue.push_bytes(bytes);

        assert_eq!(
            queue.chars(PartialHandling::Emit).collect::<String>(),
            String::from_utf8_lossy(bytes)
        );
        assert_eq!(
            queue.into_chars(PartialHandling::Emit).collect::<String>(),
            String::from_utf8_lossy(bytes)
        );
    }

    // ```
    // {
    // let mut q = StrQueue::new();
    // q.push_bytes(bytes);
    // q.chars(PartialHandling::Emit).collect::<String>
    // }
    // ```
    // should always be equal to `String::from_utf8_lossy(bytes)`.
    #[test]
    fn consistency_with_std_from_utf8_lossy() {
        assert_consistent_with_std(b"hello");
        assert_consistent_with_std(b"\xffhello");
        assert_consistent_with_std(b"\xff\xffhello");
        assert_consistent_with_std(b"\xff\xff\xffhello");
        assert_consistent_with_std(b"\xff\xff\xff\xffhello");
        assert_consistent_with_std(b"\xff\xff\xff\xff\xffhello");

        assert_consistent_with_std(b"\xe3\x81\x82hello");
        assert_consistent_with_std(b"\xe3\xe3\x81\x82hello");
        assert_consistent_with_std(b"\xe3z\xe3\x81\x82hello");
        assert_consistent_with_std(b"\xe3\x81\xe3\x81\x82hello");
        assert_consistent_with_std(b"\xe3z\xe3\x81\x82hello");
        assert_consistent_with_std(b"z\x81\xe3\x81\x82hello");
    }
}