use std::ffi::OsString;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
pub const LAYOUT_MARKER_FILENAME: &str = "CACHE_LAYOUT_VERSION";
pub const CACHE_LAYOUT_VERSION: u32 = 1;
#[must_use]
pub fn k8s_cache_path(root: &Path, source_id: &str, version_dir: &str, filename: &str) -> PathBuf {
root.join(source_id).join(version_dir).join(filename)
}
#[must_use]
pub(crate) fn crd_cache_path(root: &Path, source_id: &str, relative_path: &str) -> PathBuf {
root.join(source_id).join(relative_path)
}
#[must_use]
pub(crate) fn not_found_marker_path(schema_path: &Path) -> PathBuf {
let mut file_name = schema_path
.file_name()
.map(OsString::from)
.unwrap_or_else(|| OsString::from("schema"));
file_name.push(".not-found");
schema_path.with_file_name(file_name)
}
#[must_use]
pub fn not_found_marker_exists(schema_path: &Path) -> bool {
not_found_marker_path(schema_path).exists()
}
pub(crate) fn write_not_found_marker(schema_path: &Path) -> io::Result<()> {
let marker = not_found_marker_path(schema_path);
if let Some(parent) = marker.parent() {
fs::create_dir_all(parent)?;
}
fs::write(marker, b"not-found\n")
}
#[must_use]
pub(crate) fn layout_marker_path(root: &Path) -> PathBuf {
root.join(LAYOUT_MARKER_FILENAME)
}
pub(crate) fn default_cache_dir(env_var: &str, leaf: &str) -> PathBuf {
if let Some(path) = env_path(env_var) {
return path;
}
cache_home_for(cfg!(windows), env_path)
.unwrap_or_else(std::env::temp_dir)
.join("helm-schema")
.join(leaf)
}
fn cache_home_for(windows: bool, get: impl Fn(&str) -> Option<PathBuf>) -> Option<PathBuf> {
let absolute = |var: &str| get(var).filter(|path| path.is_absolute());
if windows {
return absolute("LOCALAPPDATA")
.or_else(|| absolute("USERPROFILE").map(|home| home.join("AppData").join("Local")));
}
absolute("XDG_CACHE_HOME").or_else(|| absolute("HOME").map(|home| home.join(".cache")))
}
fn env_path(var: &str) -> Option<PathBuf> {
let value = std::env::var_os(var)?;
(!value.is_empty()).then(|| PathBuf::from(value))
}
pub(crate) fn cache_root_has_legacy_layout(
root: &Path,
legacy_dir_name: impl Fn(&str) -> bool,
) -> bool {
let Ok(entries) = fs::read_dir(root) else {
return false;
};
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name == LAYOUT_MARKER_FILENAME || !entry.path().is_dir() {
continue;
}
if legacy_dir_name(&name) {
return true;
}
}
false
}
pub(crate) fn subdirs(dir: &Path) -> Vec<(String, PathBuf)> {
let Ok(entries) = fs::read_dir(dir) else {
return Vec::new();
};
let mut out = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
if let Some(name) = entry.file_name().to_str() {
out.push((name.to_string(), path));
}
}
out
}
pub(crate) fn json_files(dir: &Path) -> Vec<PathBuf> {
let Ok(entries) = fs::read_dir(dir) else {
return Vec::new();
};
let mut out = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
out.push(path);
}
}
out
}
#[cfg(test)]
#[path = "tests/layout.rs"]
mod tests;