use std::io::{Read, self};
use std::prelude::v1::*;
use std::str::from_utf8;
use super::Hyperlink;
fn slice_find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).enumerate()
.find(|(_, x)| x == &needle)
.map(|(i, _)| i)
}
#[derive(Debug, PartialEq, Clone)]
pub enum Event<'a> {
Text(&'a str),
Link(Hyperlink<'a>),
Bytes(&'a [u8]),
BadLink(&'a [u8]),
End,
}
pub struct LinkReader<T: Read> {
processed: usize,
buf: Vec<u8>,
stream: T,
max_buf_len: usize,
}
impl<T: Read> LinkReader<T> {
pub fn new(stream: T) -> Self {
Self{
stream,
buf: Vec::with_capacity(128),
processed: 0,
max_buf_len: 1048576, }
}
fn remove_processed(&mut self) {
self.buf.copy_within(self.processed.., 0);
self.buf.truncate(self.buf.len() - self.processed);
self.processed = 0;
}
fn fill(&mut self) -> io::Result<bool> {
let l = self.buf.len();
self.buf.resize(self.buf.capacity(), 0);
let n = self.stream.read(&mut self.buf[l..])?;
self.buf.truncate(l+n);
Ok(self.buf.len() == 0)
}
fn text_event<'a>(&'a mut self, l: usize) -> Event<'a> {
match from_utf8(&self.buf[..l]) {
Ok(s) => {
self.processed = l;
Event::Text(s)
}
Err(e) => {
let n = e.valid_up_to();
if n > 0 {
self.processed = n;
Event::Text(from_utf8(&self.buf[..n]).unwrap())
} else {
let errl = e.error_len().unwrap_or(l);
self.processed = errl;
Event::Bytes(&self.buf[..errl])
}
}
}
}
fn bad_link<'a>(&'a mut self, l: usize) -> Event<'a> {
self.processed = l;
Event::BadLink(&self.buf[..l])
}
pub fn next_event<'a>(&'a mut self) -> io::Result<Event<'a>> {
static ST: &[u8] = super::ST.as_bytes();
self.remove_processed();
if self.buf.len() == 0 {
if self.fill()? {
return Ok(Event::End);
}
}
let Some(start) = slice_find(&self.buf, super::OSC8.as_bytes())
else {
return Ok(self.text_event(self.buf.len()));
};
if start != 0 {
return Ok(self.text_event(start));
}
let Some(end) = slice_find(&self.buf, ST)
else {
if self.buf.len() == self.buf.capacity() {
if self.buf.capacity() >= self.max_buf_len {
return Ok(self.bad_link(self.buf.len()));
}
self.buf.reserve(self.buf.capacity());
}
let old_len = self.buf.len();
if self.fill()? ||
self.buf.len() == old_len
{
return Ok(self.bad_link(self.buf.len()));
}
return self.next_event();
};
let link_bytes = &self.buf[start..end+ST.len()];
macro_rules! bad_link {
() => ({
self.processed = link_bytes.len();
return Ok(Event::BadLink(link_bytes));
});
}
let link_str = match from_utf8(link_bytes) {
Ok(s) => s,
Err(_) => bad_link!(),
};
match Hyperlink::parse(link_str) {
Ok(Some((link, r))) => {
self.processed = r.end;
return Ok(Event::Link(link));
}
Ok(None) => unreachable!("hyperlink does not contain hyperlink"),
Err(err) => {
debug_assert!(err != super::ParseError::Partial);
bad_link!();
}
}
}
#[must_use = "LinkReader::with_max_len does not modify in place"]
#[inline]
pub fn with_max_len(self, max_buf_len: usize) -> Self {
Self{
max_buf_len,
.. self
}
}
}
#[cfg(test)]
mod tests {
use std::format;
use super::*;
#[test]
fn non_utf() -> io::Result<()> {
let mut s1: &[u8] = b"foo\xFFbar";
let mut r1 = LinkReader::new(&mut s1);
assert_eq!(r1.next_event()?,
Event::Text("foo"));
assert_eq!(r1.next_event()?,
Event::Bytes(b"\xFF"));
assert_eq!(r1.next_event()?,
Event::Text("bar"));
assert_eq!(r1.next_event()?,
Event::End);
Ok(())
}
#[test]
fn basic_url() -> io::Result<()> {
let link = Hyperlink::new("gopher://website.example/");
let text = format!("{link}some gopher site{link:#}");
let mut textb = text.as_bytes();
let mut lrdr = LinkReader::new(&mut textb);
assert_eq!(lrdr.next_event()?,
Event::Link(link));
assert_eq!(lrdr.next_event()?,
Event::Text("some gopher site"));
assert_eq!(lrdr.next_event()?,
Event::Link(Hyperlink::END));
assert_eq!(lrdr.next_event()?,
Event::End);
Ok(())
}
#[test]
fn incomplete() -> io::Result<()> {
let mut textb = crate::OSC8.as_bytes();
let mut lrdr = LinkReader::new(&mut textb);
assert_eq!(lrdr.next_event()?,
Event::BadLink(crate::OSC8.as_bytes()));
assert_eq!(lrdr.next_event()?,
Event::End);
Ok(())
}
}