use std::collections::HashSet;
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
pub const CODE_EXTS: &[&str] = &[
"rs", "go", "py", "ts", "tsx", "js", "jsx", "c", "h", "cpp", "hpp", "cc", "java", "rb", "php",
"swift", "kt", "scala", "sh", "toml", "json", "md",
];
pub fn is_code_file(path: &Path) -> bool {
match path.extension().and_then(|e| e.to_str()) {
Some(ext) => CODE_EXTS.contains(&ext.to_ascii_lowercase().as_str()),
None => false,
}
}
pub fn content_hash(bytes: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(bytes);
h.finalize().into()
}
#[derive(Default)]
pub struct Deduper {
seen: HashSet<[u8; 32]>,
}
impl Deduper {
pub fn new() -> Self {
Self {
seen: HashSet::new(),
}
}
pub fn accept(&mut self, bytes: &[u8]) -> bool {
self.seen.insert(content_hash(bytes))
}
pub fn len(&self) -> usize {
self.seen.len()
}
pub fn is_empty(&self) -> bool {
self.seen.is_empty()
}
}
pub fn collect_files(root: &Path, max_files: usize, max_file_bytes: u64) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
if out.len() >= max_files {
break;
}
let rd = match std::fs::read_dir(&dir) {
Ok(rd) => rd,
Err(_) => continue,
};
for entry in rd.flatten() {
let p = entry.path();
let name = entry.file_name();
let name = name.to_string_lossy();
if p.is_dir() {
if name == "target" || name.starts_with('.') {
continue;
}
stack.push(p);
} else if is_code_file(&p) {
if let Ok(meta) = entry.metadata() {
if meta.len() <= max_file_bytes && meta.len() > 0 {
out.push(p);
if out.len() >= max_files {
break;
}
}
}
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn detects_code_extensions() {
assert!(is_code_file(Path::new("a/b.rs")));
assert!(is_code_file(Path::new("x.py")));
assert!(!is_code_file(Path::new("img.png")));
assert!(!is_code_file(Path::new("noext")));
}
#[test]
fn deduper_rejects_duplicate_content() {
let mut d = Deduper::new();
assert!(d.accept(b"fn main() {}"));
assert!(!d.accept(b"fn main() {}"));
assert!(d.accept(b"different"));
assert_eq!(d.len(), 2);
}
#[test]
fn collect_files_finds_only_code_under_root() {
let tmp = std::env::temp_dir().join(format!("axiom_corpus_test_{}", std::process::id()));
let _ = std::fs::create_dir_all(tmp.join("sub"));
std::fs::write(tmp.join("a.rs"), b"fn a() {}").unwrap();
std::fs::write(tmp.join("b.png"), b"\x89PNG").unwrap();
std::fs::write(tmp.join("sub/c.py"), b"def c(): pass").unwrap();
let files = collect_files(&tmp, 100, 1_000_000);
let names: Vec<String> = files
.iter()
.map(|p| p.file_name().unwrap().to_string_lossy().to_string())
.collect();
assert!(names.contains(&"a.rs".to_string()));
assert!(names.contains(&"c.py".to_string()));
assert!(!names.contains(&"b.png".to_string()));
let _ = std::fs::remove_dir_all(&tmp);
}
}