use std::collections::HashMap;
use std::io::Write;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::ported::zsh_h::{nameddir, ND_USERNAME, PRINT_LIST, PRINT_NAMEONLY};
static NAMEDDIRTAB_INNER: OnceLock<Mutex<HashMap<String, nameddir>>> = OnceLock::new();
#[allow(non_snake_case)]
pub fn nameddirtab() -> &'static Mutex<HashMap<String, nameddir>> { NAMEDDIRTAB_INNER.get_or_init(|| Mutex::new(HashMap::new()))
}
#[allow(non_upper_case_globals)]
pub static allusersadded: AtomicI32 = AtomicI32::new(0);
pub fn createnameddirtable() { let _ = nameddirtab();
allusersadded.store(0, Ordering::Relaxed); }
pub fn emptynameddirtable() { if let Ok(mut t) = nameddirtab().lock() { t.clear(); }
allusersadded.store(0, Ordering::Relaxed); }
pub fn fillnameddirtable() { if allusersadded.load(Ordering::Relaxed) != 0 { return;
}
#[cfg(unix)]
unsafe {
libc::setpwent(); loop {
let pw = libc::getpwent();
if pw.is_null() {
break;
}
if crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 {
break;
}
let name = std::ffi::CStr::from_ptr((*pw).pw_name)
.to_string_lossy()
.into_owned();
let dir = std::ffi::CStr::from_ptr((*pw).pw_dir)
.to_string_lossy()
.into_owned();
crate::ported::utils::adduserdir(&name, &dir, ND_USERNAME, true);
}
libc::endpwent(); }
allusersadded.store(1, Ordering::Relaxed); }
pub fn addnameddirnode(nam: &str, mut nd: nameddir) { nd.diff = nd.dir.len() as i32 - nam.len() as i32;
if let Ok(mut t) = nameddirtab().lock() {
nd.node.nam = nam.to_string();
t.insert(nam.to_string(), nd);
}
}
pub fn removenameddirnode(nam: &str) -> Option<nameddir> { let removed = nameddirtab().lock().ok().and_then(|mut t| t.remove(nam));
if removed.is_some() { }
removed }
pub fn freenameddirnode(hn: nameddir) { }
pub fn printnameddirnode(hn: &nameddir, printflags: i32) { let stdout = std::io::stdout();
let mut out = stdout.lock();
if (printflags & PRINT_NAMEONLY) != 0 { let _ = writeln!(out, "{}", hn.node.nam); return;
}
if (printflags & PRINT_LIST) != 0 { let _ = write!(out, "hash -d "); if hn.node.nam.starts_with('-') { let _ = write!(out, "-- "); }
}
let _ = write!(
out,
"{}={}",
crate::ported::utils::quotedzputs(&hn.node.nam), crate::ported::utils::quotedzputs(&hn.dir), );
let _ = writeln!(out); }
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::zsh_h::{hashnode, nameddir};
fn fresh_table() {
if let Ok(mut t) = nameddirtab().lock() {
t.clear();
}
allusersadded.store(0, Ordering::Relaxed);
}
fn make_nd(name: &str, dir: &str, flags: i32) -> nameddir {
nameddir {
node: hashnode {
next: None,
nam: name.to_string(),
flags,
},
dir: dir.to_string(),
diff: 0,
}
}
#[test]
fn addnameddirnode_sets_diff_and_inserts() {
fresh_table();
addnameddirnode("p", make_nd("p", "/home/user/projects", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("p").expect("entry inserted");
assert_eq!(nd.dir, "/home/user/projects");
assert_eq!(nd.diff, 18);
assert_eq!(nd.node.nam, "p");
}
#[test]
fn removenameddirnode_returns_node_and_clears_entry() {
fresh_table();
addnameddirnode("k", make_nd("k", "/tmp/k", 0));
assert!(nameddirtab().lock().unwrap().contains_key("k"));
let dropped = removenameddirnode("k");
assert!(dropped.is_some());
assert_eq!(dropped.unwrap().dir, "/tmp/k");
assert!(!nameddirtab().lock().unwrap().contains_key("k"));
}
#[test]
fn removenameddirnode_missing_returns_none() {
fresh_table();
assert!(removenameddirnode("absent").is_none());
}
#[test]
fn emptynameddirtable_clears_and_resets_allusersadded() {
fresh_table();
addnameddirnode("a", make_nd("a", "/a", 0));
addnameddirnode("b", make_nd("b", "/b", 0));
allusersadded.store(1, Ordering::Relaxed);
emptynameddirtable();
assert!(nameddirtab().lock().unwrap().is_empty());
assert_eq!(allusersadded.load(Ordering::Relaxed), 0);
}
#[test]
fn createnameddirtable_resets_allusersadded() {
allusersadded.store(1, Ordering::Relaxed);
createnameddirtable();
assert_eq!(allusersadded.load(Ordering::Relaxed), 0);
}
#[test]
fn nd_username_value_matches_zsh_h() {
assert_eq!(ND_USERNAME, 1 << 1);
}
}