use std::{io::Read, slice, str};
pub(crate) trait IsPrintable {
fn is_printable(&self) -> bool;
}
impl IsPrintable for u8 {
#[inline]
fn is_printable(&self) -> bool {
(0x20..=0x7e).contains(self)
}
}
#[derive(Copy, Clone)]
struct Trailing {
chars: [u8; 4],
current: usize,
}
#[allow(dead_code)]
impl Trailing {
#[inline]
fn new() -> Trailing {
Trailing {
chars: [0; 4],
current: 0,
}
}
#[inline]
fn set(&mut self, b: u8) -> bool {
self.chars[self.current] = b;
self.current += 1;
self.is_complete()
}
#[inline]
fn reset(&mut self) {
self.current = 0;
}
#[inline]
fn is_complete(self) -> bool {
self.current == 4
}
#[inline]
fn chars(self) -> [u8; 4] {
self.chars
}
}
pub(crate) struct Strings<R>(R);
pub(crate) trait IntoStringsIter<T> {
fn into_strings_iter(self) -> Strings<T>;
}
impl<T: Read> IntoStringsIter<T> for T {
fn into_strings_iter(self) -> Strings<T> {
Strings(self)
}
}
impl<R: Read> Iterator for Strings<R> {
type Item = String;
fn next(&mut self) -> Option<String> {
let mut stanza = String::new();
let mut trailing = Trailing::new();
let mut byte = 0;
loop {
match self.0.read(slice::from_mut(&mut byte)) {
Ok(0) => {
if stanza.is_empty() {
return None;
}
return Some(stanza);
}
Ok(_) => {
if byte.is_printable() {
if trailing.is_complete() {
stanza.push_str(str::from_utf8(&[byte]).unwrap());
} else if trailing.set(byte) {
stanza.push_str(str::from_utf8(&trailing.chars()).unwrap());
}
} else {
if trailing.is_complete() {
return Some(stanza);
}
trailing.reset();
}
}
_ => continue,
};
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn printable() {
assert!(!b'\0'.is_printable());
assert!(!b'\t'.is_printable());
assert!(!b'\n'.is_printable());
assert!(!b'\r'.is_printable());
assert!(!b'\x1b'.is_printable());
assert!(b'a'.is_printable());
assert!(b'B'.is_printable());
assert!(b'x'.is_printable());
assert!(b'~'.is_printable());
}
#[test]
fn iterator() {
let bytes = std::io::Cursor::new(b"\0\tfoobar\r\tbarfoo");
let mut bytes = bytes.into_strings_iter();
assert_eq!(Some("foobar".to_string()), bytes.next());
assert_eq!(Some("barfoo".to_string()), bytes.next());
assert_eq!(None, bytes.next());
}
}