use std::path::{Path, PathBuf};
use std::{fs, process};
use crate::error::{StoreError, io_error};
pub const SPEC: &str = "spec";
#[must_use]
pub fn is_under_spec(root: &Path, path: &Path) -> bool {
let normal = |path: &Path| {
let mut parts: Vec<std::path::Component<'_>> = Vec::new();
for part in path.components() {
match part {
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
parts.pop();
}
other => parts.push(other),
}
}
parts.into_iter().collect::<PathBuf>()
};
normal(&root.join(path)).starts_with(normal(&root.join(SPEC)))
}
pub fn write_outputs(
root: &Path,
outputs: &[(PathBuf, Vec<u8>)],
) -> Result<Vec<PathBuf>, StoreError> {
let mut written = Vec::new();
for (path, bytes) in outputs {
let target = root.join(path);
if fs::read(&target).is_ok_and(|current| current == *bytes) {
continue;
}
if let Some(parent) = target.parent() {
fs::create_dir_all(parent).map_err(io_error(parent))?;
}
let name = target
.file_name()
.map_or_else(Default::default, |name| name.to_string_lossy());
let temporary = target.with_file_name(format!(".{name}.loft-{}", process::id()));
fs::write(&temporary, bytes).map_err(io_error(&temporary))?;
fs::rename(&temporary, &target).map_err(io_error(&target))?;
written.push(path.clone());
}
Ok(written)
}
pub const SIDECAR: &str = "v1-sidecar.json";
pub(crate) fn hidden(name: &str) -> bool {
name.starts_with('.')
}