use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::model::FileExtraction;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Fingerprint(pub(crate) [u8; 32]);
pub(crate) fn compute_fingerprint(bytes: &[u8]) -> Fingerprint {
Fingerprint(*blake3::hash(bytes).as_bytes())
}
#[derive(Debug)]
pub(crate) struct IncrementalCache {
pub(crate) extractions: HashMap<PathBuf, Arc<FileExtraction>>,
pub(crate) fingerprints: HashMap<PathBuf, Fingerprint>,
}
fn max_raw(ids: impl Iterator<Item = u32>) -> u32 {
ids.max().unwrap_or(0)
}
impl IncrementalCache {
pub(crate) fn new() -> Self {
Self {
extractions: HashMap::new(),
fingerprints: HashMap::new(),
}
}
pub(crate) fn max_symbol_id(&self) -> u32 {
max_raw(
self.extractions
.values()
.flat_map(|ext| ext.symbols.iter().map(|s| s.id.to_raw())),
)
}
#[cfg(feature = "dataflow")]
pub(crate) fn max_data_node_id(&self) -> u32 {
max_raw(
self.extractions
.values()
.flat_map(|ext| ext.data_nodes.iter().map(|d| d.id.to_raw())),
)
}
pub(crate) fn update(
&mut self,
path: PathBuf,
fp: Fingerprint,
extraction: Arc<FileExtraction>,
) {
self.fingerprints.insert(path.clone(), fp);
self.extractions.insert(path, extraction);
}
pub(crate) fn remove(&mut self, path: &Path) {
self.fingerprints.remove(path);
self.extractions.remove(path);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blake3_fingerprint_is_deterministic() {
let content = b"fn main() { println!(\"hello\"); }";
let fp1 = compute_fingerprint(content);
let fp2 = compute_fingerprint(content);
assert_eq!(fp1, fp2);
assert_eq!(
blake3::hash(content).as_bytes(),
&fp1.0,
"Fingerprint must match BLAKE3 hash"
);
}
#[test]
fn cache_update_and_remove() {
let mut cache = IncrementalCache::new();
let path = PathBuf::from("foo.py");
let bytes = b"def foo(): pass\n";
let fp = compute_fingerprint(bytes);
let mut base = FileExtraction::empty(path.clone(), crate::language::LangId::Python);
base.ast_node_count = 5;
let extraction = Arc::new(base);
cache.update(path.clone(), fp, Arc::clone(&extraction));
assert_eq!(cache.extractions.len(), 1);
assert_eq!(cache.fingerprints.len(), 1);
let cached = cache.extractions.get(&path).unwrap();
assert!(Arc::ptr_eq(&extraction, cached));
cache.remove(&path);
assert!(cache.extractions.is_empty());
assert!(cache.fingerprints.is_empty());
}
}