use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::os::unix::ffi::OsStrExt; use std::sync::LazyLock; use std::time::Instant; use crate::BString; use crate::bstr; mod watch; pub use watch::WatchId; use watch::{WatchState, WeakWatchId}; pub struct Vars { simple: HashMap, magic: HashMap BString>, all: HashSet, export: HashSet, watches: HashMap, watched: HashMap>, } impl Vars { fn new( mut simple: HashMap, magic: HashMap BString>, ) -> Self { for (var, val) in std::env::vars_os() { // explicitly added variables get priority. if !simple.contains_key(var.as_bytes()) { simple.insert(var.into_encoded_bytes(), val.into_encoded_bytes()); } } let all = simple .keys() .cloned() .chain(magic.keys().cloned()) .collect(); let export = std::env::vars_os() .map(|x| x.0.into_encoded_bytes()) .collect(); Self { simple, magic, all, export, watches: HashMap::new(), watched: HashMap::new(), } } pub fn watch(&mut self, vars: Vec) -> WatchId { let watch = WatchId::new(); let special = vars.iter().any(|v| self.magic.contains_key(v)); self.watches.insert( watch.weak(), WatchState { special, dirty: true, }, ); for var in vars { self.watched .entry(var) .or_default() .push(watch.weak()); } watch } pub fn peek_dirty(&self, watch_set: &WatchId) -> bool { if let Some(w) = self.watches.get(&watch_set.weak()) { w.dirty || w.special } else { false } } pub fn pop_dirty(&mut self, watch_set: &WatchId) -> bool { if let Some(w) = self.watches.get_mut(&watch_set.weak()) { let dirty = w.dirty || w.special; w.dirty = false; dirty } else { false } } pub fn set(&mut self, var: BString, val: BString) { if let Some(watchers) = self.watched.get(&var) { let mut dead = false; for watcher in watchers { if watcher.is_dead() { dead = true; self.watches.remove(watcher); } else { self.watches.get_mut(watcher).unwrap().dirty = true; } } if dead { self.watched.get_mut(&var).unwrap().retain(|w| !w.is_dead()); } } self.simple.insert(var.clone(), val); self.all.insert(var); } pub fn lookup<'a>(&'a self, var: &bstr) -> Option> { if let Some(fun) = self.magic.get(var) { return Some(Cow::Owned(fun())); } if let Some(val) = self.simple.get(var) { return Some(Cow::Borrowed(val)); } None } pub fn allow_export(&mut self, var: &bstr, allow: bool) { if allow { self.export.insert(var.to_vec()); } else { self.export.remove(var); } } pub fn export<'a>(&'a self) -> HashMap<&'a bstr, Cow<'a, bstr>> { self.export .iter() .filter_map(|var| { let val = self.lookup(var)?; Some((var.as_ref(), val)) }) .collect() } pub fn vars(&self) -> &HashSet { &self.all } } macro_rules! map { ($($key:expr => $val:expr),* $(,)?) => {{ let mut map = HashMap::::new(); $(map.insert($key.into(), $val);)* map }}; } impl Default for Vars { fn default() -> Self { use crate::basedir::xdg; let simple = map! { b"PISH_VERSION" => crate::consts::PISH_VERSION.as_bytes().to_vec(), b"PISH_COMMIT" => crate::consts::PISH_COMMIT.as_bytes().to_vec(), b"PISH_DIRTY" => vec![crate::consts::PISH_DIRTY as u8 + b'0'], xdg::DATA_HOME => xdg::data_home().as_os_str().as_bytes().to_vec(), xdg::STATE_HOME => xdg::state_home().as_os_str().as_bytes().to_vec(), xdg::CONFIG_HOME => xdg::config_home().as_os_str().as_bytes().to_vec(), }; let magic = map! { b"CWD_PRETTY" => crate::pretty_cwd as _, b"SECONDS" => seconds_since_startup as _, }; // call it once such that lazylock gets initialized seconds_since_startup(); let mut this = Self::new(simple.clone(), magic); for var in simple.keys() { if var.starts_with(b"XDG_") { this.allow_export(var, true); } } this } } static START: LazyLock = LazyLock::new(Instant::now); fn seconds_since_startup() -> BString { format!("{}", START.elapsed().as_secs_f64() as u64).into_bytes() }