use memchr::{memchr, memmem::Finder};
use smallvec::SmallVec;
pub type FxHashMap<K, V> = rustc_hash::FxHashMap<K, V>;
pub type FxHashSet<T> = rustc_hash::FxHashSet<T>;
pub type SmallIndexVec = SmallVec<[usize; 1]>;
#[derive(Clone, Copy)]
enum PrefilterMode {
All,
Any,
}
pub struct LinePrefilter {
finders: SmallVec<[Finder<'static>; 4]>,
mode: PrefilterMode,
}
impl LinePrefilter {
pub fn all(markers: &[&[u8]]) -> Self {
Self::new(markers, PrefilterMode::All)
}
pub fn any(markers: &[&[u8]]) -> Self {
Self::new(markers, PrefilterMode::Any)
}
fn new(markers: &[&[u8]], mode: PrefilterMode) -> Self {
let finders = markers
.iter()
.map(|marker| Finder::new(marker).into_owned())
.collect();
Self { finders, mode }
}
pub fn matches(&self, line: &[u8]) -> bool {
match self.mode {
PrefilterMode::All => self
.finders
.iter()
.all(|finder| finder.find(line).is_some()),
PrefilterMode::Any => self
.finders
.iter()
.any(|finder| finder.find(line).is_some()),
}
}
}
pub struct ByteLines<'a> {
bytes: &'a [u8],
}
impl<'a> ByteLines<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
}
}
impl<'a> Iterator for ByteLines<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.bytes.is_empty() {
return None;
}
if let Some(newline) = memchr(b'\n', self.bytes) {
let (line, rest) = self.bytes.split_at(newline);
self.bytes = &rest[1..];
Some(line)
} else {
let line = self.bytes;
self.bytes = &[];
Some(line)
}
}
}
pub fn byte_lines(bytes: &[u8]) -> ByteLines<'_> {
ByteLines::new(bytes)
}
pub fn suffix_string(value: &str, suffix: &str) -> String {
let mut output = String::with_capacity(value.len() + suffix.len());
output.push_str(value);
output.push_str(suffix);
output
}
#[cfg(test)]
mod tests {
use super::{LinePrefilter, byte_lines, suffix_string};
#[test]
fn line_prefilter_all_requires_every_marker() {
let prefilter = LinePrefilter::all(&[b"\"usage\"", b"\"message\""]);
assert!(prefilter.matches(br#"{"message":{"usage":{"input":1}}}"#));
assert!(!prefilter.matches(br#"{"message":{"role":"user"}}"#));
assert!(!prefilter.matches(br#"{"usage":{"input":1}}"#));
}
#[test]
fn line_prefilter_any_requires_one_marker() {
let prefilter = LinePrefilter::any(&[b"\"model_change\"", b"\"usage\""]);
assert!(prefilter.matches(br#"{"type":"model_change"}"#));
assert!(prefilter.matches(br#"{"message":{"usage":{}}}"#));
assert!(!prefilter.matches(br#"{"type":"message"}"#));
}
#[test]
fn byte_lines_returns_newline_delimited_slices() {
let lines = byte_lines(b"one\ntwo\nthree").collect::<Vec<_>>();
assert_eq!(
lines,
[b"one".as_slice(), b"two".as_slice(), b"three".as_slice()]
);
}
#[test]
fn suffix_string_builds_without_formatting() {
assert_eq!(
suffix_string("claude-sonnet-4", "-fast"),
"claude-sonnet-4-fast"
);
}
}