1use anyhow::Result;
4use std::fs;
5use std::path::Path;
6
7pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
9 Ok(fs::read_to_string(path)?)
10}
11
12pub fn read_bytes<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
14 Ok(fs::read(path)?)
15}
16
17pub fn write_atomic<P: AsRef<Path>>(path: P, contents: impl AsRef<[u8]>) -> Result<()> {
22 let path = path.as_ref();
23 let parent = path.parent().unwrap_or(Path::new("."));
24
25 let tmp = tempfile(parent)?;
27 fs::write(&tmp, contents)?;
28 fs::rename(&tmp, path)?;
29 Ok(())
30}
31
32pub fn ensure_dir<P: AsRef<Path>>(path: P) -> Result<()> {
34 Ok(fs::create_dir_all(path)?)
35}
36
37pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
39 path.as_ref().is_file()
40}
41
42pub fn is_dir<P: AsRef<Path>>(path: P) -> bool {
44 path.as_ref().is_dir()
45}
46
47pub fn copy_dir_all<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
63 let src = src.as_ref();
64 let dst = dst.as_ref();
65 fs::create_dir_all(dst)?;
66 for entry in fs::read_dir(src)? {
67 let entry = entry?;
68 let ty = entry.file_type()?;
69 if ty.is_dir() {
70 copy_dir_all(entry.path(), dst.join(entry.file_name()))?;
71 } else {
72 fs::copy(entry.path(), dst.join(entry.file_name()))?;
73 }
74 }
75 Ok(())
76}
77
78pub fn find_files<P: AsRef<Path>>(dir: P, ext: &str) -> Result<Vec<std::path::PathBuf>> {
96 let mut result = Vec::new();
97 if dir.as_ref().is_dir() {
98 find_files_inner(dir.as_ref(), ext, &mut result)?;
99 }
100 Ok(result)
101}
102
103fn find_files_inner(dir: &Path, ext: &str, acc: &mut Vec<std::path::PathBuf>) -> Result<()> {
104 for entry in fs::read_dir(dir)? {
105 let entry = entry?;
106 let path = entry.path();
107 if path.is_dir() {
108 find_files_inner(&path, ext, acc)?;
109 } else if path.extension().and_then(|e| e.to_str()) == Some(ext) {
110 acc.push(path);
111 }
112 }
113 Ok(())
114}
115
116fn tempfile(dir: &Path) -> Result<std::path::PathBuf> {
119 use std::time::{SystemTime, UNIX_EPOCH};
120 let ts = SystemTime::now()
121 .duration_since(UNIX_EPOCH)
122 .map(|d| d.subsec_nanos())
123 .unwrap_or(0);
124 Ok(dir.join(format!(".tmp.rok.{ts}")))
125}
126
127#[cfg(test)]
130mod tests {
131 use super::*;
132 use std::fs;
133
134 #[test]
135 fn round_trip_atomic_write() {
136 let dir = tempfile::tempdir().unwrap();
137 let p = dir.path().join("hello.txt");
138 write_atomic(&p, b"hello").unwrap();
139 assert_eq!(read_to_string(&p).unwrap(), "hello");
140 }
141
142 #[test]
143 fn ensure_dir_creates_nested() {
144 let dir = tempfile::tempdir().unwrap();
145 let nested = dir.path().join("a").join("b").join("c");
146 ensure_dir(&nested).unwrap();
147 assert!(nested.is_dir());
148 }
149
150 #[test]
151 fn is_file_and_is_dir() {
152 let dir = tempfile::tempdir().unwrap();
153 let f = dir.path().join("f.txt");
154 fs::write(&f, "x").unwrap();
155 assert!(is_file(&f));
156 assert!(!is_dir(&f));
157 assert!(is_dir(dir.path()));
158 assert!(!is_file(dir.path()));
159 }
160
161 #[test]
162 fn copy_dir_all_copies_nested() {
163 let src = tempfile::tempdir().unwrap();
164 let dst = tempfile::tempdir().unwrap();
165 let sub = src.path().join("sub");
166 fs::create_dir_all(&sub).unwrap();
167 fs::write(src.path().join("root.txt"), b"root").unwrap();
168 fs::write(sub.join("nested.txt"), b"nested").unwrap();
169
170 copy_dir_all(src.path(), dst.path()).unwrap();
171
172 assert_eq!(read_to_string(dst.path().join("root.txt")).unwrap(), "root");
173 assert_eq!(
174 read_to_string(dst.path().join("sub/nested.txt")).unwrap(),
175 "nested"
176 );
177 }
178
179 #[test]
180 fn find_files_by_extension() {
181 let dir = tempfile::tempdir().unwrap();
182 fs::write(dir.path().join("a.txt"), b"").unwrap();
183 fs::write(dir.path().join("b.rs"), b"").unwrap();
184 fs::write(dir.path().join("c.txt"), b"").unwrap();
185
186 let txt = find_files(dir.path(), "txt").unwrap();
187 assert_eq!(txt.len(), 2);
188 let rs = find_files(dir.path(), "rs").unwrap();
189 assert_eq!(rs.len(), 1);
190 }
191
192 #[test]
193 fn find_files_nonexistent_dir_returns_empty() {
194 let result = find_files("/nonexistent/path/xyz", "rs").unwrap();
195 assert!(result.is_empty());
196 }
197}