1use anyhow::{Context, Result};
4use std::fs;
5use std::path::{Component, Path, PathBuf};
6use tracing::debug;
7
8pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
12 let path = path.as_ref();
13 fs::read_to_string(path).with_context(|| format!("failed to read `{}`", path.display()))
14}
15
16pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
21 let path = path.as_ref();
22 debug!("Writing `{}`", path.display());
23 if let Some(parent) = path.parent() {
24 create_dir_all(parent)?;
25 }
26 fs::write(path, contents.as_ref())
27 .with_context(|| format!("failed to write `{}`", path.display()))
28}
29
30pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
32 let p = p.as_ref();
33 fs::create_dir_all(p)
34 .with_context(|| format!("failed to create directory `{}`", p.display()))?;
35 Ok(())
36}
37
38pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
56 path.into()
59 .parent()
60 .expect("")
61 .components()
62 .fold(String::new(), |mut s, c| {
63 match c {
64 Component::Normal(_) => s.push_str("../"),
65 _ => {
66 debug!("Other path component... {:?}", c);
67 }
68 }
69 s
70 })
71}
72
73pub fn remove_dir_content(dir: &Path) -> Result<()> {
75 for item in fs::read_dir(dir)
76 .with_context(|| format!("failed to read directory `{}`", dir.display()))?
77 .flatten()
78 {
79 let item = item.path();
80 if item.is_dir() {
81 fs::remove_dir_all(&item)
82 .with_context(|| format!("failed to remove `{}`", item.display()))?;
83 } else {
84 fs::remove_file(&item)
85 .with_context(|| format!("failed to remove `{}`", item.display()))?;
86 }
87 }
88 Ok(())
89}
90
91pub fn copy_files_except_ext(
94 from: &Path,
95 to: &Path,
96 recursive: bool,
97 avoid_dir: Option<&PathBuf>,
98 ext_blacklist: &[&str],
99) -> Result<()> {
100 debug!(
101 "Copying all files from {} to {} (blacklist: {:?}), avoiding {:?}",
102 from.display(),
103 to.display(),
104 ext_blacklist,
105 avoid_dir
106 );
107
108 if from == to {
110 return Ok(());
111 }
112
113 for entry in fs::read_dir(from)? {
114 let entry = entry?.path();
115 let metadata = entry
116 .metadata()
117 .with_context(|| format!("Failed to read {entry:?}"))?;
118
119 let entry_file_name = entry.file_name().unwrap();
120 let target_file_path = to.join(entry_file_name);
121
122 if metadata.is_dir() && recursive {
124 if entry == to.as_os_str() {
125 continue;
126 }
127
128 if let Some(avoid) = avoid_dir {
129 if entry == *avoid {
130 continue;
131 }
132 }
133
134 if !target_file_path.exists() {
136 fs::create_dir(&target_file_path)?;
137 }
138
139 copy_files_except_ext(&entry, &target_file_path, true, avoid_dir, ext_blacklist)?;
140 } else if metadata.is_file() {
141 if let Some(ext) = entry.extension() {
143 if ext_blacklist.contains(&ext.to_str().unwrap()) {
144 continue;
145 }
146 }
147 debug!("Copying {entry:?} to {target_file_path:?}");
148 copy(&entry, &target_file_path)?;
149 }
150 }
151 Ok(())
152}
153
154fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
156 let from = from.as_ref();
157 let to = to.as_ref();
158 return copy_inner(from, to)
159 .with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()));
160
161 #[cfg(target_os = "macos")]
172 fn copy_inner(from: &Path, to: &Path) -> Result<()> {
173 use std::fs::OpenOptions;
174 use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
175
176 let mut reader = std::fs::File::open(from)?;
177 let metadata = reader.metadata()?;
178 if !metadata.is_file() {
179 anyhow::bail!(
180 "expected a file, `{}` appears to be {:?}",
181 from.display(),
182 metadata.file_type()
183 );
184 }
185 let perm = metadata.permissions();
186 let mut writer = OpenOptions::new()
187 .mode(perm.mode())
188 .write(true)
189 .create(true)
190 .truncate(true)
191 .open(to)?;
192 let writer_metadata = writer.metadata()?;
193 if writer_metadata.is_file() {
194 writer.set_permissions(perm)?;
198 }
199 std::io::copy(&mut reader, &mut writer)?;
200 Ok(())
201 }
202
203 #[cfg(not(target_os = "macos"))]
204 fn copy_inner(from: &Path, to: &Path) -> Result<()> {
205 fs::copy(from, to)?;
206 Ok(())
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use std::io::Result;
214 use std::path::Path;
215
216 #[cfg(target_os = "windows")]
217 fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
218 std::os::windows::fs::symlink_file(src, dst)
219 }
220
221 #[cfg(not(target_os = "windows"))]
222 fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
223 std::os::unix::fs::symlink(src, dst)
224 }
225
226 #[test]
227 fn copy_files_except_ext_test() {
228 let tmp = match tempfile::TempDir::new() {
229 Ok(t) => t,
230 Err(e) => panic!("Could not create a temp dir: {e}"),
231 };
232
233 write(tmp.path().join("file.txt"), "").unwrap();
235 write(tmp.path().join("file.md"), "").unwrap();
236 write(tmp.path().join("file.png"), "").unwrap();
237 write(tmp.path().join("sub_dir/file.png"), "").unwrap();
238 write(tmp.path().join("sub_dir_exists/file.txt"), "").unwrap();
239 if let Err(err) = symlink(tmp.path().join("file.png"), tmp.path().join("symlink.png")) {
240 panic!("Could not symlink file.png: {err}");
241 }
242
243 create_dir_all(tmp.path().join("output")).unwrap();
245 create_dir_all(tmp.path().join("output/sub_dir_exists")).unwrap();
246
247 if let Err(e) =
248 copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
249 {
250 panic!("Error while executing the function:\n{e:?}");
251 }
252
253 if !tmp.path().join("output/file.txt").exists() {
255 panic!("output/file.txt should exist")
256 }
257 if tmp.path().join("output/file.md").exists() {
258 panic!("output/file.md should not exist")
259 }
260 if !tmp.path().join("output/file.png").exists() {
261 panic!("output/file.png should exist")
262 }
263 if !tmp.path().join("output/sub_dir/file.png").exists() {
264 panic!("output/sub_dir/file.png should exist")
265 }
266 if !tmp.path().join("output/sub_dir_exists/file.txt").exists() {
267 panic!("output/sub_dir/file.png should exist")
268 }
269 if !tmp.path().join("output/symlink.png").exists() {
270 panic!("output/symlink.png should exist")
271 }
272 }
273}