use std::str::CharIndices; /// `CharacterReader` may be used for iterating characters from left-to-right /// in a string with miscellaneous operation methods. #[derive(Clone)] pub struct CharacterReader<'a> { length: usize, start_offset: usize, char_indices: CharIndices<'a>, } impl<'a> CharacterReader<'a> { /// Constructs a `CharacterReader` from a string starting at the given offset. pub fn from_offset(value: &'a str, offset: usize) -> Self { CharacterReader { length: value.len(), start_offset: offset, char_indices: value[offset..].char_indices() } } /// Indicates if there are remaining code points to read. pub fn has_remaining(&self) -> bool { self.clone().char_indices.next().is_some() } /// Indicates if the reader has reached the end of the string. pub fn reached_end(&self) -> bool { self.clone().char_indices.next().is_none() } /// Skips a code point in the reader. This is equivalent to /// calling `next()`. pub fn skip_in_place(&mut self) { self.next(); } /// Skips the given number of code points in the reader. pub fn skip_count_in_place(&mut self, count: usize) { for _ in 0..count { if self.next().is_none() { break; } } } /// Returns the current byte offset in the string. pub fn index(&self) -> usize { self.clone().char_indices.next().map_or(self.length, |(i, _)| self.start_offset + i) } /// Returns the next code point. If there are no code points /// available, returns U+00. pub fn next_or_zero(&mut self) -> char { self.char_indices.next().map_or('\x00', |(_, cp)| cp) } /// Peeks the next code point. pub fn peek(&self) -> Option { self.clone().char_indices.next().map(|(_, cp)| cp) } /// Peeks the next code point. If there are no code points /// available, returns U+00. pub fn peek_or_zero(&self) -> char { self.clone().next_or_zero() } /// Peeks the next code point at the given zero based code point index. pub fn peek_at(&self, index: usize) -> Option { let mut indices = self.clone().char_indices; for _ in 0..index { if indices.next().is_none() { break; } } indices.next().map(|(_, cp)| cp) } /// Peeks the next code point at the given zero based code point index. /// If there are no code points available, returns U+00. pub fn peek_at_or_zero(&self, index: usize) -> char { self.peek_at(index).unwrap_or('\x00') } /// Peeks a number of code points until the string's end. pub fn peek_seq(&self, num_code_points: u64) -> String { let mut r = String::new(); let mut next_indices = self.char_indices.clone(); for _ in 0..num_code_points { match next_indices.next() { None => { break; }, Some(cp) => { r.push(cp.1); } } } r } } impl<'a> From<&'a str> for CharacterReader<'a> { /// Constructs a `CharacterReader` from a string. fn from(value: &'a str) -> Self { CharacterReader { length: value.len(), start_offset: 0, char_indices: value.char_indices() } } } impl<'a> From<&'a String> for CharacterReader<'a> { /// Constructs a `CharacterReader` from a string. fn from(value: &'a String) -> Self { CharacterReader { length: value.len(), start_offset: 0, char_indices: value.char_indices() } } } impl<'a> Iterator for CharacterReader<'a> { type Item = char; fn next(&mut self) -> Option { self.char_indices.next().map(|(_, cp)| cp) } } #[cfg(test)] mod test { use super::CharacterReader; #[test] fn test() { let mut reader = CharacterReader::from("foo"); assert!(reader.has_remaining()); assert_eq!(reader.peek_seq(5), "foo"); assert_eq!(reader.peek_seq(1), "f"); assert_eq!(reader.peek_or_zero(), 'f'); for _ in 0..3 { reader.next(); } assert_eq!(reader.peek_or_zero(), '\x00'); assert!(reader.reached_end()); } }