choco 0.2.2

markup language for dialogue systems
Documentation
use super::raw::{self, Range};
use ::core::ops;

fn remove_right(text: &str, range: ops::Range<usize>) -> ops::Range<usize> {
    match text[range.clone()].rfind(|ch: char| !ch.is_whitespace()) {
        Some(index) => {
            range.start
                ..text[range.start + index..range.end]
                    .char_indices()
                    .nth(1)
                    .map_or(range.end, |(whitespace_start, _)| {
                        range.start + index + whitespace_start
                    })
        }
        None => range.start..range.start,
    }
}

fn remove_left(text: &str, range: ops::Range<usize>) -> ops::Range<usize> {
    match text[range.clone()].find(|ch: char| !ch.is_whitespace()) {
        Some(index) => range.start + index..range.end,
        None => range.start..range.start,
    }
}

#[derive(Clone, Debug)]
pub(super) struct Iter<'a> {
    raw: raw::Iter<'a>,
    remove_left_next: bool,
    seen_signal: bool,
}

impl<'a> Iter<'a> {
    pub fn new(text: &'a str) -> Self {
        Self {
            raw: raw::Iter::new(text),
            remove_left_next: true,
            seen_signal: false,
        }
    }

    pub fn as_full_str(&self) -> &'a str {
        self.raw.as_full_str()
    }
}

impl<'a> Iterator for Iter<'a> {
    type Item = Range;

    fn next(&mut self) -> Option<Self::Item> {
        let next = self.raw.next()?;
        if let Range::Text(range) = &next {
            let mut range = remove_right(self.as_full_str(), range.clone());
            if self.remove_left_next {
                if self.seen_signal {
                    range = remove_left(self.as_full_str(), range);
                }
                self.remove_left_next = false;
            }
            if range.is_empty() {
                self.next()
            } else {
                Some(Range::Text(range))
            }
        } else {
            self.seen_signal = true;
            Some(next)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Iter, Range};

    #[test]
    fn no_trim_required() {
        const SAMPLE: &str = "Hello, world!";
        let mut iter = Iter::new(SAMPLE);
        let range_event0 = iter.next().expect("first range event");
        let Range::Text(range0) = &range_event0 else {
            panic!("expected text range, got {range_event0:?}");
        };
        assert_eq!(&SAMPLE[range0.clone()], "Hello, world!");
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn middle_trim() {
        const SAMPLE: &str = "Hello, @oops world!";
        let mut iter = Iter::new(SAMPLE);
        let range_event0 = iter.next().expect("first range event");
        let Range::Text(range0) = &range_event0 else {
            panic!("expected text range, got {range_event0:?}");
        };
        assert_eq!(&SAMPLE[range0.clone()], "Hello,");
        let range_event1 = iter.next().expect("second range event");
        let Range::Signal { .. } = &range_event1 else {
            panic!("expected signal range, got {range_event1:?}");
        };
        let range_event2 = iter.next().expect("first range event");
        let Range::Text(range2) = &range_event2 else {
            panic!("expected text range, got {range_event2:?}");
        };
        assert_eq!(&SAMPLE[range2.clone()], " world!");
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn left_trim() {
        const SAMPLE: &str = "@oops Hello, world!";
        let mut iter = Iter::new(SAMPLE);
        let range_event0 = iter.next().expect("first range event");
        let Range::Signal { .. } = &range_event0 else {
            panic!("expected signal range, got {range_event0:?}");
        };
        let range_event1 = iter.next().expect("second range event");
        let Range::Text(range1) = &range_event1 else {
            panic!("expected text range, got {range_event1:?}");
        };
        assert_eq!(&SAMPLE[range1.clone()], "Hello, world!");
        assert_eq!(iter.next(), None);
    }
}