use anyhow::Result;
use std::fs;
use std::path::Path;
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
Ok(fs::read_to_string(path)?)
}
pub fn read_bytes<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
Ok(fs::read(path)?)
}
pub fn write_atomic<P: AsRef<Path>>(path: P, contents: impl AsRef<[u8]>) -> Result<()> {
let path = path.as_ref();
let parent = path.parent().unwrap_or(Path::new("."));
let tmp = tempfile(parent)?;
fs::write(&tmp, contents)?;
fs::rename(&tmp, path)?;
Ok(())
}
pub fn ensure_dir<P: AsRef<Path>>(path: P) -> Result<()> {
Ok(fs::create_dir_all(path)?)
}
pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().is_file()
}
pub fn is_dir<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().is_dir()
}
pub fn copy_dir_all<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
let src = src.as_ref();
let dst = dst.as_ref();
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
pub fn find_files<P: AsRef<Path>>(dir: P, ext: &str) -> Result<Vec<std::path::PathBuf>> {
let mut result = Vec::new();
if dir.as_ref().is_dir() {
find_files_inner(dir.as_ref(), ext, &mut result)?;
}
Ok(result)
}
fn find_files_inner(dir: &Path, ext: &str, acc: &mut Vec<std::path::PathBuf>) -> Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
find_files_inner(&path, ext, acc)?;
} else if path.extension().and_then(|e| e.to_str()) == Some(ext) {
acc.push(path);
}
}
Ok(())
}
fn tempfile(dir: &Path) -> Result<std::path::PathBuf> {
use std::time::{SystemTime, UNIX_EPOCH};
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0);
Ok(dir.join(format!(".tmp.rok.{ts}")))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn round_trip_atomic_write() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("hello.txt");
write_atomic(&p, b"hello").unwrap();
assert_eq!(read_to_string(&p).unwrap(), "hello");
}
#[test]
fn ensure_dir_creates_nested() {
let dir = tempfile::tempdir().unwrap();
let nested = dir.path().join("a").join("b").join("c");
ensure_dir(&nested).unwrap();
assert!(nested.is_dir());
}
#[test]
fn is_file_and_is_dir() {
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("f.txt");
fs::write(&f, "x").unwrap();
assert!(is_file(&f));
assert!(!is_dir(&f));
assert!(is_dir(dir.path()));
assert!(!is_file(dir.path()));
}
#[test]
fn copy_dir_all_copies_nested() {
let src = tempfile::tempdir().unwrap();
let dst = tempfile::tempdir().unwrap();
let sub = src.path().join("sub");
fs::create_dir_all(&sub).unwrap();
fs::write(src.path().join("root.txt"), b"root").unwrap();
fs::write(sub.join("nested.txt"), b"nested").unwrap();
copy_dir_all(src.path(), dst.path()).unwrap();
assert_eq!(read_to_string(dst.path().join("root.txt")).unwrap(), "root");
assert_eq!(
read_to_string(dst.path().join("sub/nested.txt")).unwrap(),
"nested"
);
}
#[test]
fn find_files_by_extension() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("a.txt"), b"").unwrap();
fs::write(dir.path().join("b.rs"), b"").unwrap();
fs::write(dir.path().join("c.txt"), b"").unwrap();
let txt = find_files(dir.path(), "txt").unwrap();
assert_eq!(txt.len(), 2);
let rs = find_files(dir.path(), "rs").unwrap();
assert_eq!(rs.len(), 1);
}
#[test]
fn find_files_nonexistent_dir_returns_empty() {
let result = find_files("/nonexistent/path/xyz", "rs").unwrap();
assert!(result.is_empty());
}
}