use super::*;
pub(crate) const TAIL_CHUNK: usize = 64 * 1024;
pub fn mmap_bytes(path: &Path) -> Result<Option<Mmap>> {
mmap_file(path)
}
pub(crate) fn mmap_file(path: &Path) -> Result<Option<Mmap>> {
let file = File::open(path).with_context(|| format!("cannot open {}", path.display()))?;
let len = file
.metadata()
.with_context(|| format!("cannot stat {}", path.display()))?
.len();
if len == 0 {
return Ok(None);
}
#[allow(unsafe_code)]
let mmap =
unsafe { Mmap::map(&file) }.with_context(|| format!("cannot mmap {}", path.display()))?;
Ok(Some(mmap))
}
pub(crate) fn line_role_value_matches(
line: &[u8],
accept_user: bool,
accept_assistant: bool,
) -> bool {
const KEY: &[u8] = br#""role""#;
static KEY_FINDER: std::sync::LazyLock<memchr::memmem::Finder<'static>> =
std::sync::LazyLock::new(|| memchr::memmem::Finder::new(br#""role""#));
let mut at = 0usize;
while let Some(rel) = KEY_FINDER.find(&line[at..]) {
let mut j = at + rel + KEY.len();
while line.get(j).is_some_and(|b| b.is_ascii_whitespace()) {
j += 1;
}
if line.get(j) == Some(&b':') {
j += 1;
while line.get(j).is_some_and(|b| b.is_ascii_whitespace()) {
j += 1;
}
let rest = &line[j.min(line.len())..];
if (accept_user && rest.starts_with(br#""user""#))
|| (accept_assistant && rest.starts_with(br#""assistant""#))
{
return true;
}
}
at += rel + KEY.len();
}
false
}
pub fn line_has_role_marker(line: &[u8]) -> bool {
line_role_value_matches(line, true, true)
}
pub fn line_has_user_role_marker(line: &[u8]) -> bool {
line_role_value_matches(line, true, false)
}
pub(crate) fn line_payload(line: &[u8]) -> Option<&[u8]> {
let line = line.strip_suffix(b"\n").unwrap_or(line);
let line = line.strip_suffix(b"\r").unwrap_or(line);
if line.iter().all(u8::is_ascii_whitespace) {
None
} else {
Some(line)
}
}
pub fn parse_line(line: &[u8]) -> Result<Option<Record>> {
let Some(payload) = line_payload(line) else {
return Ok(None);
};
let rec: Record = serde_json::from_slice(payload)?;
Ok(Some(rec))
}
pub fn validate_line_syntax(line: &[u8]) -> Result<()> {
let Some(payload) = line_payload(line) else {
return Ok(());
};
serde_json::from_slice::<serde::de::IgnoredAny>(payload)?;
Ok(())
}
pub fn scan_lines_bytes<F>(bytes: &[u8], mut visit: F) -> Result<()>
where
F: FnMut(&[u8]),
{
let mut start = 0usize;
for nl in memchr_iter(b'\n', bytes) {
visit(&bytes[start..nl]);
start = nl + 1;
}
if start < bytes.len() {
visit(&bytes[start..]);
}
Ok(())
}
pub enum LineVerdict<T> {
Keep(T),
Skip,
Ignore,
}