use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Env {
vars: HashMap<String, String>,
}
impl Env {
pub fn from_env() -> Self {
Self {
vars: std::env::vars().collect(),
}
}
pub fn of(pairs: Vec<(String, String)>) -> Env {
Env {
vars: pairs.into_iter().collect(),
}
}
#[cfg(test)]
pub(crate) fn from_pairs<I, K, V>(pairs: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
Env::of(
pairs
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
pub(crate) fn with_overrides(&self, overrides: &[(&str, &str)]) -> Env {
let mut vars = self.vars.clone();
for &(k, v) in overrides {
vars.insert(k.to_owned(), v.to_owned());
}
Env { vars }
}
pub(crate) fn without(&self, key: &str) -> Env {
let mut vars = self.vars.clone();
vars.remove(key);
Env { vars }
}
pub(crate) fn var(&self, key: &str) -> Option<String> {
self.get(key).map(str::to_owned)
}
pub fn wall(&self) -> Option<PathBuf> {
self.get(crate::world::wall::YOG_WALL).map(PathBuf::from)
}
pub(crate) fn pairs(&self) -> Vec<(String, String)> {
self.vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
fn get(&self, key: &str) -> Option<&str> {
self.vars
.get(key)
.map(String::as_str)
.filter(|v| !v.is_empty())
}
fn home(&self, tail: &str) -> PathBuf {
match self.get("HOME") {
Some(h) => PathBuf::from(h).join(tail),
None => PathBuf::from(tail),
}
}
fn xdg(&self, var: &str, default_tail: &str, sub: &str) -> PathBuf {
let base = match self.get(var) {
Some(base) => PathBuf::from(base),
None => self.home(default_tail),
};
base.join(sub)
}
}
mod substrate;
impl Env {
pub fn home_dir(&self) -> PathBuf {
self.get("HOME")
.map_or_else(|| PathBuf::from("/"), PathBuf::from)
}
pub fn yog_data_root(&self) -> PathBuf {
self.xdg("XDG_DATA_HOME", ".local/share", "yog")
}
pub fn yog_cache_root(&self) -> PathBuf {
self.xdg("XDG_CACHE_HOME", ".cache", "yog")
}
pub fn yog_state_root(&self) -> PathBuf {
self.xdg("XDG_STATE_HOME", ".local/state", "yog")
}
pub fn user(&self) -> Option<String> {
self.get("USER").map(str::to_owned)
}
pub fn search_path(&self) -> Option<String> {
self.get("PATH").map(str::to_owned)
}
pub fn litany_conv_repo(&self) -> Option<PathBuf> {
self.get("LITANY_CONV_REPO").map(PathBuf::from)
}
pub fn yog_stage_root(&self) -> PathBuf {
stage_root_under(&self.yog_state_root())
}
}
pub fn stage_root_under(yog_state_root: &Path) -> PathBuf {
yog_state_root.join("stage")
}
fn hex_val(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
pub fn percent_decode(s: &str) -> String {
let b = s.as_bytes();
let mut out = Vec::with_capacity(b.len());
let mut i = 0;
while let Some(&cur) = b.get(i) {
if cur == b'%'
&& let (Some(&hi), Some(&lo)) = (b.get(i + 1), b.get(i + 2))
&& let (Some(h), Some(l)) = (hex_val(hi), hex_val(lo))
{
out.push((h << 4) | l);
i += 3;
continue;
}
out.push(cur);
i += 1;
}
String::from_utf8_lossy(&out).into_owned()
}
#[cfg(test)]
mod tests;