aboutsummaryrefslogtreecommitdiffstats
path: root/src/run/var/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/run/var/mod.rs')
-rw-r--r--src/run/var/mod.rs188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/run/var/mod.rs b/src/run/var/mod.rs
new file mode 100644
index 0000000..1ccc195
--- /dev/null
+++ b/src/run/var/mod.rs
@@ -0,0 +1,188 @@
+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<BString, BString>,
+ magic: HashMap<BString, fn() -> BString>,
+ all: HashSet<BString>,
+ export: HashSet<BString>,
+ watches: HashMap<WeakWatchId, WatchState>,
+ watched: HashMap<BString, Vec<WeakWatchId>>,
+}
+
+impl Vars {
+ fn new(
+ mut simple: HashMap<BString, BString>,
+ magic: HashMap<BString, fn() -> 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<BString>) -> 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_insert_with(Vec::new)
+ .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<Cow<'a, bstr>> {
+ 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<BString> {
+ &self.all
+ }
+}
+
+macro_rules! map {
+ ($($key:expr => $val:expr),* $(,)?) => {{
+ let mut map = HashMap::<BString, _, _>::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<Instant> = LazyLock::new(Instant::now);
+
+fn seconds_since_startup() -> BString {
+ format!("{}", START.elapsed().as_secs_f64() as u64).into_bytes()
+}