1use sha2::{Digest, Sha256};
4use std::path::{Component, Path};
5
6pub const META_FILE: &str = "._meta";
8pub const SCHEMA_DIR: &str = "._schema";
10pub const CACHE_DIR: &str = "._cache";
12pub const LOCK_FILE: &str = ".lock";
14
15pub fn new_uuid_v7() -> String {
17 uuid::Uuid::now_v7().to_string()
18}
19
20pub fn new_uuid(version: usize) -> String {
24 match version {
25 4 => uuid::Uuid::new_v4().to_string(),
26 _ => new_uuid_v7(),
27 }
28}
29
30pub fn uuid_version(s: &str) -> Option<usize> {
32 uuid::Uuid::parse_str(s).ok().map(|u| u.get_version_num())
33}
34
35pub fn is_uuid(s: &str) -> bool {
37 uuid::Uuid::parse_str(s).is_ok()
38}
39
40pub fn hex(bytes: &[u8]) -> String {
42 const TABLE: &[u8; 16] = b"0123456789abcdef";
43 let mut out = String::with_capacity(bytes.len() * 2);
44 for b in bytes {
45 out.push(TABLE[(b >> 4) as usize] as char);
46 out.push(TABLE[(b & 0x0f) as usize] as char);
47 }
48 out
49}
50
51pub fn sha256_bytes(data: &[u8]) -> String {
53 let mut h = Sha256::new();
54 h.update(data);
55 hex(&h.finalize())
56}
57
58pub fn sha256_file(path: &Path) -> std::io::Result<String> {
60 let mut file = std::fs::File::open(path)?;
61 let mut h = Sha256::new();
62 std::io::copy(&mut file, &mut h)?;
63 Ok(hex(&h.finalize()))
64}
65
66pub fn now_rfc3339() -> String {
68 let now = time::OffsetDateTime::now_utc();
69 format!(
70 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}+00:00",
71 now.year(),
72 u8::from(now.month()),
73 now.day(),
74 now.hour(),
75 now.minute(),
76 now.second()
77 )
78}
79
80pub fn parse_rfc3339(s: &str) -> Option<time::OffsetDateTime> {
82 time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339).ok()
83}
84
85pub fn relative_depth(root: &Path, dir: &Path) -> usize {
87 dir.strip_prefix(root)
88 .map(|rel| {
89 rel.components()
90 .filter(|c| matches!(c, Component::Normal(_)))
91 .count()
92 })
93 .unwrap_or(0)
94}
95
96pub fn is_reserved_name(name: &str) -> bool {
98 name.starts_with("._")
99}
100
101pub fn is_meta_file(name: &str) -> bool {
103 name == META_FILE
104}
105
106pub fn is_lock_file(name: &str) -> bool {
108 name == LOCK_FILE
109}
110
111pub fn is_os_noise(name: &str) -> bool {
118 matches!(
119 name,
120 ".DS_Store"
121 | "Thumbs.db"
122 | "desktop.ini"
123 | ".git"
124 | ".gitignore"
125 | ".gitattributes"
126 | ".gitmodules"
127 | ".gitkeep"
128 | ".github"
129 | ".hg"
130 | ".hgignore"
131 | ".svn"
132 | ".jj"
133 ) || (name.starts_with("._") && name != META_FILE)
134}
135
136pub fn is_sub_bundle(name: &str) -> bool {
141 name.ends_with(".str")
142}
143
144pub fn is_other_dotfile(name: &str) -> bool {
146 name.starts_with('.') && !is_meta_file(name) && !is_lock_file(name)
147}
148
149pub fn rel_display(root: &Path, path: &Path) -> String {
151 let rel = path.strip_prefix(root).unwrap_or(path);
152 let s = rel.display().to_string();
153 if s.is_empty() { ".".to_string() } else { s }
154}