use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use memmap2::Mmap;
pub enum Source {
Mapped(Mmap),
Buffered(String), }
const MAPPED_MIN: u64 = 64 * 1024;
impl Source {
pub fn open(path: &Path) -> io::Result<Self> {
let file = File::open(path)?;
if file.metadata()?.len() < MAPPED_MIN {
let mut buf = String::new();
(&file).read_to_string(&mut buf)?;
return Ok(Source::Buffered(buf));
}
let map = unsafe { Mmap::map(&file)? };
std::str::from_utf8(&map).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(Source::Mapped(map))
}
pub fn stdin() -> io::Result<Self> {
let mut buf = String::new();
io::stdin().lock().read_to_string(&mut buf)?;
Ok(Source::Buffered(buf))
}
pub fn text(&self) -> &str {
let text = match self {
Source::Buffered(s) => s,
Source::Mapped(m) => unsafe { std::str::from_utf8_unchecked(m) },
};
text.strip_prefix('\u{feff}').unwrap_or(text)
}
pub fn base(&self) -> usize {
let raw: &[u8] = match self {
Source::Buffered(s) => s.as_bytes(),
Source::Mapped(m) => m,
};
raw.len() - self.text().len()
}
pub fn set_byte(&mut self, at: usize, byte: u8) -> bool {
let base = self.base();
match self {
Source::Buffered(s)
if byte.is_ascii() && s.as_bytes().get(base + at).is_some_and(u8::is_ascii) =>
{
let at = base + at;
s.replace_range(at..at + 1, (byte as char).encode_utf8(&mut [0; 4]));
true
}
_ => false,
}
}
}