1mod progress;
2
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use anyhow::Result;
7use serde::Serialize;
8
9pub use progress::InitProgress;
10
11pub fn ensure_dirs(dirs: &[PathBuf]) -> Result<()> {
13 for dir in dirs {
14 fs::create_dir_all(dir)?;
15 }
16 Ok(())
17}
18
19pub fn write_json_file<T: Serialize>(path: &Path, value: &T) -> Result<()> {
21 if let Some(parent) = path.parent() {
22 fs::create_dir_all(parent)?;
23 }
24
25 let mut payload = serde_json::to_string_pretty(value)?;
26 payload.push('\n');
27
28 write_private_file(path, payload.as_bytes())
29}
30
31pub fn write_private_file(path: &Path, contents: &[u8]) -> Result<()> {
33 #[cfg(unix)]
34 {
35 use std::fs::OpenOptions;
36 use std::io::Write;
37 use std::os::unix::fs::OpenOptionsExt;
38
39 let mut file = OpenOptions::new().write(true).create_new(true).mode(0o600).open(path)?;
40 file.write_all(contents)?;
41 return Ok(());
42 }
43
44 #[cfg(not(unix))]
45 {
46 fs::write(path, contents)?;
47 Ok(())
48 }
49}
50
51pub fn write_file_if_missing(path: &Path, contents: &[u8]) -> Result<()> {
53 if path.exists() {
54 return Ok(());
55 }
56
57 if let Some(parent) = path.parent() {
58 fs::create_dir_all(parent)?;
59 }
60
61 fs::write(path, contents)?;
62 Ok(())
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn write_file_if_missing_is_idempotent() {
71 let tmp = tempfile::tempdir().expect("tempdir");
72 let path = tmp.path().join("marker.txt");
73
74 write_file_if_missing(&path, b"first").expect("first write");
75 write_file_if_missing(&path, b"second").expect("second write");
76
77 assert_eq!(fs::read_to_string(path).expect("read marker"), "first");
78 }
79}