pub mod catalog_io;
pub mod content_block_io;
pub mod frontmatter;
use crate::error::{Error, Result};
use std::path::{Path, PathBuf};
pub(crate) fn validate_resource_name(kind_label: &str, name: &str) -> Result<()> {
let bad = name.is_empty()
|| name == "."
|| name == ".."
|| name.contains('/')
|| name.contains('\\')
|| name.contains('\0');
if bad {
return Err(Error::InvalidFormat {
path: PathBuf::from(name),
message: format!("{kind_label} name '{name}' contains invalid characters"),
});
}
Ok(())
}
pub(crate) fn write_atomic(path: &Path, contents: &[u8]) -> Result<()> {
use std::io::Write;
let parent = path.parent().unwrap_or_else(|| Path::new("."));
std::fs::create_dir_all(parent)?;
let file_name = path.file_name().ok_or_else(|| Error::InvalidFormat {
path: path.to_path_buf(),
message: "atomic write target has no file name".into(),
})?;
let mut tmp_name = file_name.to_os_string();
tmp_name.push(format!(".{}.tmp", std::process::id()));
let tmp_path = parent.join(tmp_name);
let mut file = std::fs::File::create(&tmp_path)?;
if let Err(e) = file.write_all(contents).and_then(|_| file.sync_all()) {
drop(file);
let _ = std::fs::remove_file(&tmp_path);
return Err(e.into());
}
drop(file);
if let Err(e) = std::fs::rename(&tmp_path, path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(e.into());
}
Ok(())
}