use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Serialize;
pub fn ensure_dirs(dirs: &[PathBuf]) -> Result<()> {
for dir in dirs {
fs::create_dir_all(dir).with_context(|| format!("failed to create {}", dir.display()))?;
}
Ok(())
}
pub fn write_json_file<T: Serialize>(path: &Path, value: &T) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| format!("failed to create {}", parent.display()))?;
}
let mut payload = serde_json::to_string_pretty(value).context("failed to serialize json")?;
payload.push('\n');
write_private_file(path, payload.as_bytes())
}
pub fn write_private_file(path: &Path, contents: &[u8]) -> Result<()> {
#[cfg(unix)]
{
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
.with_context(|| format!("failed to write {}", path.display()))?;
file.write_all(contents)?;
return Ok(());
}
#[cfg(not(unix))]
{
fs::write(path, contents).with_context(|| format!("failed to write {}", path.display()))?;
Ok(())
}
}
pub fn write_file_if_missing(path: &Path, contents: &[u8]) -> Result<()> {
if path.exists() {
return Ok(());
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| format!("failed to create {}", parent.display()))?;
}
fs::write(path, contents).with_context(|| format!("failed to write {}", path.display()))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_file_if_missing_is_idempotent() {
let tmp = tempfile::tempdir().expect("tempdir");
let path = tmp.path().join("marker.txt");
write_file_if_missing(&path, b"first").expect("first write");
write_file_if_missing(&path, b"second").expect("second write");
assert_eq!(fs::read_to_string(path).expect("read marker"), "first");
}
}