1use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6use std::path::{Path, PathBuf};
7use std::time::SystemTime;
8
9use yog_ids::Format;
10
11pub(crate) fn under(dir: &Path, wanted: &dyn Fn(&Path) -> bool) -> Vec<PathBuf> {
13 fn walk(dir: &Path, wanted: &dyn Fn(&Path) -> bool, out: &mut Vec<PathBuf>) {
14 let Ok(entries) = std::fs::read_dir(dir) else {
15 return;
16 };
17 for path in entries.flatten().map(|e| e.path()) {
18 if path.is_dir() {
19 walk(&path, wanted, out);
20 } else if wanted(&path) {
21 out.push(path);
22 }
23 }
24 }
25 let mut out = Vec::new();
26 walk(dir, wanted, &mut out);
27 out.sort();
28 out
29}
30
31pub fn with_ext(dir: &Path, ext: &str) -> Vec<PathBuf> {
33 under(dir, &|p| p.extension().is_some_and(|e| e == ext))
34}
35
36pub fn of(dir: &Path, format: Format) -> Vec<PathBuf> {
38 with_ext(dir, format.ext())
39}
40
41pub(crate) fn fingerprint(files: &[PathBuf]) -> u64 {
43 let mut h = DefaultHasher::new();
44 for f in files {
45 f.hash(&mut h);
46 if let Ok(m) = std::fs::metadata(f) {
47 m.len().hash(&mut h);
48 m.modified()
49 .ok()
50 .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
51 .hash(&mut h);
52 }
53 }
54 h.finish()
55}
56
57pub fn read_source(path: impl AsRef<Path>) -> std::io::Result<String> {
60 let bytes = std::fs::read(path)?;
61 yog_data::source::text(&bytes)
62 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
63}
64
65pub fn write_source(path: &Path, text: &str) -> Result<(), String> {
67 let bytes = yog_data::source::binary(text).map_err(|e| format!("{}: {e}", path.display()))?;
68 std::fs::write(path, bytes).map_err(|e| format!("{}: {e}", path.display()))
69}