#[cfg(test)]
mod tests;
use std::fmt::Display;
pub(crate) const MAX_LINE: usize = 1024 * 1024;
pub(crate) struct Lines {
marker: Vec<u8>,
inbox: Vec<u8>,
start: usize,
scanned: usize,
skipping: bool,
}
impl Default for Lines {
fn default() -> Lines {
Lines::new("\n")
}
}
impl Lines {
pub(crate) fn new(marker: &str) -> Lines {
Lines {
marker: marker.as_bytes().to_vec(),
inbox: Vec::new(),
start: 0,
scanned: 0,
skipping: false,
}
}
pub(crate) fn set_marker(&mut self, marker: &str) {
self.marker = marker.as_bytes().to_vec();
self.scanned = self.start;
}
pub(crate) fn reset(&mut self) {
self.inbox = Vec::new();
self.start = 0;
self.scanned = 0;
self.skipping = false;
}
pub(crate) fn waiting(&self) -> bool {
self.start < self.inbox.len()
}
pub(crate) fn append(&mut self, bytes: &[u8]) {
self.compact();
self.inbox.extend_from_slice(bytes);
}
fn compact(&mut self) {
if self.start > 0 {
self.inbox.drain(..self.start);
self.scanned -= self.start;
self.start = 0;
self.inbox.shrink_to(64 * 1024);
}
}
pub(crate) fn next(&mut self, from: &dyn Display) -> Option<String> {
if self.marker.is_empty() {
self.inbox.drain(..self.start);
(self.start, self.scanned) = (0, 0);
let text = decode(&mut self.inbox);
return (!text.is_empty()).then_some(text);
}
let width = self.marker.len();
while let Some(found) = find(&self.inbox[self.scanned..], &self.marker) {
let end = self.scanned + found;
let line = &self.inbox[self.start..end];
let text = if std::mem::take(&mut self.skipping) {
None
} else if line.len() > MAX_LINE {
tracing::warn!(%from, "a line over 1 MiB was dropped");
None
} else {
Some(String::from_utf8_lossy(line).into_owned())
};
self.start = end + width;
self.scanned = self.start;
if text.is_some() {
return text;
}
}
let known = self.inbox.len().saturating_sub(width - 1).max(self.start);
self.scanned = known;
if known - self.start > MAX_LINE && !self.skipping {
tracing::warn!(%from, "a line over 1 MiB was dropped");
self.skipping = true;
}
if self.skipping {
self.start = known;
}
self.compact();
None
}
pub(crate) fn rest(&mut self, from: &dyn Display) -> Option<String> {
let rest = &self.inbox[self.start..];
let text = if rest.is_empty() || self.skipping {
None
} else if rest.len() > MAX_LINE {
tracing::warn!(%from, "a line over 1 MiB was dropped");
None
} else {
Some(String::from_utf8_lossy(rest).into_owned())
};
self.reset();
text
}
pub(crate) fn push(&mut self, bytes: &[u8], from: &dyn Display) -> Vec<String> {
self.append(bytes);
std::iter::from_fn(|| self.next(from)).collect()
}
pub(crate) fn finish(&mut self, from: &dyn Display) -> Vec<String> {
let mut lines: Vec<String> = std::iter::from_fn(|| self.next(from)).collect();
lines.extend(self.rest(from));
lines
}
}
fn find(bytes: &[u8], marker: &[u8]) -> Option<usize> {
bytes
.windows(marker.len())
.position(|window| window == marker)
}
pub(crate) fn decode(bytes: &mut Vec<u8>) -> String {
let mut text = String::new();
let mut rest: &[u8] = bytes;
loop {
match std::str::from_utf8(rest) {
Ok(valid) => {
text.push_str(valid);
rest = &[];
break;
}
Err(error) => {
let (valid, after) = rest.split_at(error.valid_up_to());
text.push_str(&String::from_utf8_lossy(valid));
match error.error_len() {
Some(bad) => {
text.push('\u{fffd}');
rest = &after[bad..];
}
None => {
rest = after;
break;
}
}
}
}
}
*bytes = rest.to_vec();
text
}
pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(message) = payload.downcast_ref::<&str>() {
(*message).to_owned()
} else if let Some(message) = payload.downcast_ref::<String>() {
message.clone()
} else {
String::from("(panic payload is not a string)")
}
}