use sha2::{Digest, Sha256};
use std::path::{Component, Path};
pub const META_FILE: &str = "._meta";
pub const SCHEMA_DIR: &str = "._schema";
pub const CACHE_DIR: &str = "._cache";
pub const LOCK_FILE: &str = ".lock";
pub fn new_uuid_v7() -> String {
uuid::Uuid::now_v7().to_string()
}
pub fn new_uuid(version: usize) -> String {
match version {
4 => uuid::Uuid::new_v4().to_string(),
_ => new_uuid_v7(),
}
}
pub fn uuid_version(s: &str) -> Option<usize> {
uuid::Uuid::parse_str(s).ok().map(|u| u.get_version_num())
}
pub fn is_uuid(s: &str) -> bool {
uuid::Uuid::parse_str(s).is_ok()
}
pub fn hex(bytes: &[u8]) -> String {
const TABLE: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
out.push(TABLE[(b >> 4) as usize] as char);
out.push(TABLE[(b & 0x0f) as usize] as char);
}
out
}
pub fn sha256_bytes(data: &[u8]) -> String {
let mut h = Sha256::new();
h.update(data);
hex(&h.finalize())
}
pub fn sha256_file(path: &Path) -> std::io::Result<String> {
let mut file = std::fs::File::open(path)?;
let mut h = Sha256::new();
std::io::copy(&mut file, &mut h)?;
Ok(hex(&h.finalize()))
}
pub fn now_rfc3339() -> String {
let now = time::OffsetDateTime::now_utc();
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}+00:00",
now.year(),
u8::from(now.month()),
now.day(),
now.hour(),
now.minute(),
now.second()
)
}
pub fn parse_rfc3339(s: &str) -> Option<time::OffsetDateTime> {
time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339).ok()
}
pub fn relative_depth(root: &Path, dir: &Path) -> usize {
dir.strip_prefix(root)
.map(|rel| {
rel.components()
.filter(|c| matches!(c, Component::Normal(_)))
.count()
})
.unwrap_or(0)
}
pub fn is_reserved_name(name: &str) -> bool {
name.starts_with("._")
}
pub fn is_meta_file(name: &str) -> bool {
name == META_FILE
}
pub fn is_lock_file(name: &str) -> bool {
name == LOCK_FILE
}
pub fn is_os_noise(name: &str) -> bool {
matches!(
name,
".DS_Store"
| "Thumbs.db"
| "desktop.ini"
| ".git"
| ".gitignore"
| ".gitattributes"
| ".gitmodules"
| ".gitkeep"
| ".github"
| ".hg"
| ".hgignore"
| ".svn"
| ".jj"
) || (name.starts_with("._") && name != META_FILE)
}
pub fn is_sub_bundle(name: &str) -> bool {
name.ends_with(".str")
}
pub fn is_other_dotfile(name: &str) -> bool {
name.starts_with('.') && !is_meta_file(name) && !is_lock_file(name)
}
pub fn rel_display(root: &Path, path: &Path) -> String {
let rel = path.strip_prefix(root).unwrap_or(path);
let s = rel.display().to_string();
if s.is_empty() { ".".to_string() } else { s }
}