ztheme 1.6.1

Fast asynchronous Zsh prompt
#[cfg(test)]
use std::ffi::OsStr;
#[cfg(test)]
use std::os::unix::ffi::OsStrExt as _;

const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;

#[derive(Clone, Debug)]
pub(crate) struct HashBuilder {
    state: u64,
}

impl HashBuilder {
    pub(crate) fn new(namespace: &[u8]) -> Self {
        let mut hash = Self { state: FNV_OFFSET };
        hash.add_bytes(b"domain", namespace);
        hash
    }

    pub(crate) fn add_bytes(&mut self, label: &[u8], value: &[u8]) {
        self.add_raw(&length(label).to_be_bytes());
        self.add_raw(label);
        self.add_raw(&length(value).to_be_bytes());
        self.add_raw(value);
    }

    #[cfg(test)]
    fn add_os(&mut self, label: &[u8], value: &OsStr) {
        self.add_bytes(label, value.as_bytes());
    }

    #[cfg(test)]
    fn add_optional_os(&mut self, label: &[u8], value: Option<&OsStr>) {
        match value {
            Some(value) => {
                self.add_bytes(b"present", b"1");
                self.add_os(label, value);
            }
            None => self.add_bytes(b"present", b"0"),
        }
    }

    pub(crate) const fn finish(self) -> u64 {
        self.state
    }

    fn add_raw(&mut self, bytes: &[u8]) {
        for byte in bytes {
            self.state ^= u64::from(*byte);
            self.state = self.state.wrapping_mul(FNV_PRIME);
        }
    }
}

fn length(value: &[u8]) -> u64 {
    u64::try_from(value.len()).unwrap_or(u64::MAX)
}

#[cfg(test)]
mod tests {
    use std::ffi::OsStr;

    use super::HashBuilder;

    #[test]
    fn outputs_are_stable() {
        assert_eq!(HashBuilder::new(b"one").finish(), 0x9020_a6e7_aadb_4182);

        let mut hash = HashBuilder::new(b"one");
        hash.add_bytes(b"a", b"bc");
        assert_eq!(hash.finish(), 0x1585_6e18_310c_25b9);
    }

    #[test]
    fn namespaces_labels_boundaries_and_optional_values_are_distinct() {
        let mut first = HashBuilder::new(b"one");
        first.add_bytes(b"a", b"bc");
        let mut second = HashBuilder::new(b"one");
        second.add_bytes(b"ab", b"c");
        let mut other_namespace = HashBuilder::new(b"two");
        other_namespace.add_bytes(b"a", b"bc");
        let mut missing = HashBuilder::new(b"optional");
        missing.add_optional_os(b"value", None);
        let mut present = HashBuilder::new(b"optional");
        present.add_optional_os(b"value", Some(OsStr::new("")));

        let first = first.finish();
        assert_ne!(first, second.finish());
        assert_ne!(first, other_namespace.finish());
        assert_ne!(missing.finish(), present.finish());
    }
}