use super::*;
pub struct RevLines<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) hi: usize,
pub(crate) carry: Vec<u8>,
pub(crate) pending: std::collections::VecDeque<Vec<u8>>,
pub(crate) chunk: usize,
pub(crate) done: bool,
}
impl<'a> RevLines<'a> {
#[must_use]
pub fn with_chunk(bytes: &'a [u8], chunk: usize) -> Self {
Self {
bytes,
hi: bytes.len(),
carry: Vec::new(),
pending: std::collections::VecDeque::new(),
chunk: chunk.max(1),
done: false,
}
}
pub(crate) fn fill(&mut self) -> bool {
loop {
if self.hi == 0 {
if self.carry.is_empty() {
return false;
}
let first = std::mem::take(&mut self.carry);
self.pending.push_back(first);
return true;
}
let mut take = self.chunk;
let mut lo = self.hi.saturating_sub(take);
while lo > 0 && memrchr(b'\n', &self.bytes[lo..self.hi]).is_none() {
take = take.saturating_mul(2);
lo = self.hi.saturating_sub(take);
}
let mut buf = Vec::with_capacity((self.hi - lo) + self.carry.len());
buf.extend_from_slice(&self.bytes[lo..self.hi]);
buf.extend_from_slice(&self.carry);
self.carry.clear();
self.hi = lo;
let at_bof = lo == 0;
let nls: Vec<usize> = memchr_iter(b'\n', &buf).collect();
if nls.is_empty() {
if at_bof {
if buf.is_empty() {
return false;
}
self.pending.push_back(buf);
return true;
}
self.carry = buf;
continue; }
let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(nls.len());
for w in 1..nls.len() {
ranges.push((nls[w - 1] + 1, nls[w]));
}
let tail_start = nls[nls.len() - 1] + 1;
if tail_start <= buf.len() {
ranges.push((tail_start, buf.len()));
}
for &(s, e) in ranges.iter().rev() {
self.pending.push_back(buf[s..e].to_vec());
}
let seg0 = buf[..nls[0]].to_vec();
if at_bof {
self.pending.push_back(seg0); } else {
self.carry = seg0;
}
return true;
}
}
}
impl Iterator for RevLines<'_> {
type Item = Vec<u8>;
fn next(&mut self) -> Option<Vec<u8>> {
loop {
if let Some(line) = self.pending.pop_front() {
return Some(line);
}
if self.done {
return None;
}
if !self.fill() {
self.done = true;
return None;
}
}
}
}
pub fn head_records<F>(path: &Path, keep: F) -> Result<(usize, usize)>
where
F: FnMut(&Record) -> bool,
{
head_records_prefiltered(path, |_| true, keep)
}
pub fn head_records_prefiltered<P, F>(path: &Path, pre: P, mut keep: F) -> Result<(usize, usize)>
where
P: Fn(&[u8]) -> bool,
F: FnMut(&Record) -> bool,
{
let Some(mmap) = mmap_file(path)? else {
return Ok((0, 0));
};
let bytes: &[u8] = &mmap;
let mut skipped = 0usize;
let mut start = 0usize;
let mut stop = false;
let mut handle = |line: &[u8], skipped: &mut usize, stop: &mut bool| {
if !pre(line) {
if line_shape_malformed(line) {
*skipped += 1;
}
return;
}
match parse_line(line) {
Ok(Some(rec)) => {
if !keep(&rec) {
*stop = true;
}
}
Ok(None) => {}
Err(_) => *skipped += 1,
}
};
for nl in memchr_iter(b'\n', bytes) {
handle(&bytes[start..nl], &mut skipped, &mut stop);
if stop {
return Ok((skipped, nl + 1));
}
start = nl + 1;
}
if start < bytes.len() {
handle(&bytes[start..], &mut skipped, &mut stop);
}
Ok((skipped, bytes.len()))
}
pub fn tail_records<F>(path: &Path, floor: usize, keep: F) -> Result<usize>
where
F: FnMut(&Record) -> bool,
{
tail_records_chunked(path, TAIL_CHUNK, floor, keep)
}
pub fn tail_records_prefiltered<P, F>(
path: &Path,
pre: P,
floor: usize,
mut keep: F,
) -> Result<usize>
where
P: Fn(&[u8]) -> bool,
F: FnMut(&Record) -> bool,
{
let Some(mmap) = mmap_file(path)? else {
return Ok(0);
};
let bytes: &[u8] = &mmap;
let floor = floor.min(bytes.len());
let mut skipped = 0usize;
let mut stopped = false;
for raw in RevLines::with_chunk(&bytes[floor..], TAIL_CHUNK) {
if !pre(&raw) {
if line_shape_malformed(&raw) {
skipped += 1;
}
continue;
}
match parse_line(&raw) {
Ok(Some(rec)) => {
if !keep(&rec) {
stopped = true;
break;
}
}
Ok(None) => {}
Err(_) => skipped += 1,
}
}
if !stopped && floor > 0 {
for raw in RevLines::with_chunk(&bytes[..floor], TAIL_CHUNK) {
if !pre(&raw) {
continue;
}
if let Ok(Some(rec)) = parse_line(&raw) {
if !keep(&rec) {
break;
}
}
}
}
Ok(skipped)
}
pub fn tail_records_chunked<F>(
path: &Path,
chunk: usize,
floor: usize,
mut keep: F,
) -> Result<usize>
where
F: FnMut(&Record) -> bool,
{
let Some(mmap) = mmap_file(path)? else {
return Ok(0);
};
let bytes: &[u8] = &mmap;
let floor = floor.min(bytes.len());
let mut skipped = 0usize;
let mut stopped = false;
for raw in RevLines::with_chunk(&bytes[floor..], chunk) {
match parse_line(&raw) {
Ok(Some(rec)) => {
if !keep(&rec) {
stopped = true;
break;
}
}
Ok(None) => {}
Err(_) => skipped += 1,
}
}
if !stopped && floor > 0 {
for raw in RevLines::with_chunk(&bytes[..floor], chunk) {
if let Ok(Some(rec)) = parse_line(&raw) {
if !keep(&rec) {
break;
}
}
}
}
Ok(skipped)
}