use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Var {
pub value: String,
pub exported: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct ShellState {
pub vars: HashMap<String, Var>,
pub cwd: PathBuf,
pub last_exit: i32,
}
impl ShellState {
pub fn new(env: Vec<(String, String)>, cwd: PathBuf) -> Self {
let vars = env
.into_iter()
.map(|(name, value)| {
(
name,
Var {
value,
exported: true,
},
)
})
.collect();
Self {
vars,
cwd,
last_exit: 0,
}
}
pub fn get(&self, name: &str) -> Option<&str> {
self.vars.get(name).map(|v| v.value.as_str())
}
pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
let name = name.into();
let value = value.into();
match self.vars.get_mut(&name) {
Some(var) => var.value = value,
None => {
self.vars.insert(
name,
Var {
value,
exported: false,
},
);
}
}
}
pub fn export(&mut self, name: &str, value: Option<String>) {
match self.vars.get_mut(name) {
Some(var) => {
var.exported = true;
if let Some(value) = value {
var.value = value;
}
}
None => {
self.vars.insert(
name.to_string(),
Var {
value: value.unwrap_or_default(),
exported: true,
},
);
}
}
}
pub fn unset(&mut self, name: &str) {
self.vars.remove(name);
}
pub fn exported_env(&self) -> Vec<(String, String)> {
self.vars
.iter()
.filter(|(_, var)| var.exported)
.map(|(name, var)| (name.clone(), var.value.clone()))
.collect()
}
pub fn path(&self) -> Option<&str> {
self.get("PATH").or_else(|| {
self.vars
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case("PATH"))
.map(|(_, var)| var.value.as_str())
})
}
pub fn home(&self) -> Option<&str> {
self.get("HOME").or_else(|| self.get("USERPROFILE"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_falls_back_to_the_spelling_windows_uses() {
let state = ShellState::new(vec![("Path".into(), "/bin".into())], PathBuf::from("/"));
assert_eq!(state.path(), Some("/bin"));
}
#[test]
fn path_prefers_the_exact_spelling_when_both_are_present() {
let state = ShellState::new(
vec![
("Path".into(), "/fallback".into()),
("PATH".into(), "/exact".into()),
],
PathBuf::from("/"),
);
assert_eq!(state.path(), Some("/exact"));
}
}