use std::fmt;
use std::str::FromStr;
use anyhow::{Context, Result, bail};
use serde::{Serialize, Serializer};
pub(crate) const HASHLINE_MODULUS: u32 = 0x1000;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct LineHash(u16);
impl LineHash {
pub(crate) fn new(value: u16) -> Result<Self> {
if u32::from(value) >= HASHLINE_MODULUS {
bail!("line hash {value:#x} is out of range");
}
Ok(Self(value))
}
pub(crate) fn as_u16(self) -> u16 {
self.0
}
}
impl fmt::Display for LineHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:03x}", self.0)
}
}
impl FromStr for LineHash {
type Err = anyhow::Error;
fn from_str(value: &str) -> Result<Self> {
let raw = u16::from_str_radix(value, 16)
.with_context(|| format!("invalid line hash `{value}`"))?;
Self::new(raw)
}
}
impl Serialize for LineHash {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
pub(crate) fn hash_line(text: &str) -> LineHash {
let text = text.strip_suffix('\r').unwrap_or(text);
let mut hasher = xxhash_rust::xxh32::Xxh32::new(0);
for token in text.split_whitespace() {
hasher.update(token.as_bytes());
}
let digest = hasher.digest() % HASHLINE_MODULUS;
LineHash(u16::try_from(digest).unwrap_or_default())
}
pub(crate) fn hash_text(text: &str) -> String {
blake3::hash(text.as_bytes()).to_string()
}
pub(crate) fn hash_bytes(bytes: &[u8]) -> String {
blake3::hash(bytes).to_string()
}