use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
fn modules_root() -> PathBuf {
crate::persist::config_dir().join("modules")
}
pub fn registry_path() -> PathBuf {
crate::persist::config_dir().join("modules.json")
}
pub fn config_dir(id: &str) -> PathBuf {
modules_root().join("config").join(sanitize(id))
}
pub fn state_dir(id: &str) -> PathBuf {
modules_root().join("state").join(sanitize(id))
}
pub fn git_base() -> PathBuf {
modules_root().join("git")
}
pub fn git_dir(slug: &str, hash: &str) -> PathBuf {
git_base().join(format!("{}-{hash}", sanitize(slug)))
}
pub fn staging_dir() -> PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
git_base().join(format!(".staging-{}-{n}", std::process::id()))
}
pub fn is_managed_git_path(path: &std::path::Path) -> bool {
let base = git_base();
let base = base.canonicalize().unwrap_or(base);
let p = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
p.starts_with(&base)
}
pub fn sanitize(id: &str) -> String {
let mut out = String::with_capacity(id.len());
for &b in id.as_bytes() {
if b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'_' | b'-') {
out.push(b as char);
} else {
out.push_str(&format!("%{b:02x}"));
}
}
if out.ends_with('.') {
out.pop();
out.push_str("%2e");
}
if out.len() > 120 {
let mut h = DefaultHasher::new();
id.hash(&mut h);
let hash = format!("{:016x}", h.finish());
let head: String = out.chars().take(120 - hash.len() - 1).collect();
out = format!("{head}-{hash}");
}
if out.is_empty() {
out.push('_');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitizes_and_is_deterministic() {
assert_eq!(sanitize("you.git-status"), "you.git-status");
assert_eq!(sanitize("you:mod"), "you%3amod");
assert_eq!(sanitize("you:mod"), sanitize("you:mod"));
assert_ne!(sanitize("a:b"), sanitize("a.b"));
}
#[test]
fn long_ids_truncate_with_hash() {
let long = "a".repeat(300);
let s = sanitize(&long);
assert!(s.len() <= 120);
assert_eq!(sanitize(&long), s, "stable");
}
}