use std::io::{Read as _, Seek, SeekFrom};
use std::path::Path;
pub const TEXT_INSPECT_LEN: usize = 8 * 1024;
pub fn read_prefix(path: &Path) -> std::io::Result<Vec<u8>> {
read_prefix_n(path, TEXT_INSPECT_LEN)
}
pub fn read_prefix_n(path: &Path, n: usize) -> std::io::Result<Vec<u8>> {
let mut file = std::fs::File::open(path)?;
let mut buf = vec![0u8; n];
let read = file.read(&mut buf)?;
buf.truncate(read);
Ok(buf)
}
pub fn read_suffix_n(path: &Path, n: usize) -> std::io::Result<Vec<u8>> {
let mut file = std::fs::File::open(path)?;
let len = file.seek(SeekFrom::End(0))?;
let to_read = usize::try_from(len).unwrap_or(n).min(n);
file.seek(SeekFrom::Start(len - to_read as u64))?;
let mut buf = vec![0u8; to_read];
file.read_exact(&mut buf)?;
Ok(buf)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Classification {
Text,
Binary,
}
pub fn classify_bytes(bytes: &[u8]) -> Classification {
match content_inspector::inspect(bytes) {
content_inspector::ContentType::BINARY => Classification::Binary,
_ => Classification::Text,
}
}
pub const MAX_ANALYZE_BYTES: u64 = 256 * 1024 * 1024;
#[derive(Debug)]
pub enum ReadCapError {
TooLarge(u64),
Io(std::io::Error),
}
pub(crate) fn read_capped_with(path: &Path, max: u64) -> Result<Vec<u8>, ReadCapError> {
match std::fs::metadata(path) {
Ok(m) if m.len() > max => Err(ReadCapError::TooLarge(m.len())),
Ok(_) => std::fs::read(path).map_err(ReadCapError::Io),
Err(e) => Err(ReadCapError::Io(e)),
}
}
pub fn read_capped(path: &Path) -> Result<Vec<u8>, ReadCapError> {
read_capped_with(path, MAX_ANALYZE_BYTES)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_capped_returns_bytes_under_cap() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("f");
std::fs::write(&p, b"hello").unwrap();
match read_capped(&p) {
Ok(b) => assert_eq!(b, b"hello"),
_ => panic!("expected Bytes under the cap"),
}
}
#[test]
fn read_capped_with_rejects_over_cap_without_reading() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("big");
std::fs::write(&p, b"0123456789").unwrap();
match read_capped_with(&p, 4) {
Err(ReadCapError::TooLarge(n)) => assert_eq!(n, 10),
_ => panic!("a 10-byte file must exceed a 4-byte cap"),
}
}
#[test]
fn read_capped_missing_path_is_io_error() {
let dir = tempfile::tempdir().unwrap();
match read_capped(&dir.path().join("nope")) {
Err(ReadCapError::Io(_)) => {}
_ => panic!("a missing path must be an Io error"),
}
}
}