use self::EnvError as E;
use crate::utils::files;
use color_eyre::eyre::bail;
use color_eyre::eyre::eyre;
use color_eyre::eyre::Result;
use displaydoc::Display;
use std::collections::HashMap;
use thiserror::Error;
use tracing::debug;
use tracing::trace;
pub const UP_HARDWARE_UUID: &str = "UP_HARDWARE_UUID";
#[allow(clippy::implicit_hasher)]
pub fn get_env(
inherit_env: Option<&Vec<String>>,
input_env: Option<&HashMap<String, String>>,
) -> Result<HashMap<String, String>> {
let mut env: HashMap<String, String> = HashMap::new();
if let Some(inherited_env) = inherit_env {
for inherited_var in inherited_env {
if let Ok(value) = std::env::var(inherited_var) {
env.insert(inherited_var.clone(), value);
}
}
}
add_builtin_env_vars(&mut env)?;
let mut unresolved_env = Vec::new();
if let Some(config_env) = input_env {
trace!("Provided env: {config_env:#?}");
let mut calculated_env = HashMap::new();
let home_dir = files::home_dir()?;
for (key, val) in config_env {
calculated_env.insert(
key.clone(),
shellexpand::full_with_context(
val,
|| Some(&home_dir),
|k| {
env.get(k).map_or_else(
|| {
if config_env.contains_key(k) {
unresolved_env.push(key.clone());
Ok(None)
} else {
Err(eyre!("Value {k} not found in inherited_env or env vars."))
}
},
|val| Ok(Some(val)),
)
},
)
.map_err(|e| E::EnvLookup {
var: e.var_name,
source: e.cause,
})?
.into_owned(),
);
}
for (k, v) in calculated_env.drain() {
env.insert(k, v);
}
}
debug!("Unresolved env: {unresolved_env:?}");
while !unresolved_env.is_empty() {
trace!("Env so far: {env:#?}");
trace!("Still unresolved env: {unresolved_env:#?}");
let mut resolved_indices = Vec::new();
for (index, key) in unresolved_env.iter().enumerate() {
let val = env.get(key).ok_or_else(|| eyre!("How did we get here?"))?;
let resolved_val = shellexpand::env_with_context(val, |k| {
if unresolved_env.iter().any(|s| s == k) {
Ok(None)
} else if let Some(v) = env.get(k) {
resolved_indices.push(index);
Ok(Some(v))
} else {
Err(eyre!("Shouldn't be possible to hit this."))
}
})
.map_err(|e| E::EnvLookup {
var: e.var_name,
source: e.cause,
})?
.into_owned();
let val_ref = env
.get_mut(key)
.ok_or_else(|| eyre!("How did we get here?"))?;
*val_ref = resolved_val;
}
trace!("resolved indices: {resolved_indices:?}");
if resolved_indices.is_empty() {
bail!("Errors resolving env, do you have cycles? Unresolved env: {unresolved_env:#?}",);
}
unresolved_env = unresolved_env
.into_iter()
.enumerate()
.filter_map(|(index, value)| {
if resolved_indices.contains(&index) {
None
} else {
Some(value)
}
})
.collect();
}
debug!("Expanded config env: {env:#?}");
Ok(env)
}
fn add_builtin_env_vars(env: &mut HashMap<String, String>) -> Result<()> {
env.insert(
UP_HARDWARE_UUID.to_owned(),
if cfg!(target_os = "macos") {
crate::utils::mac::get_hardware_uuid()?
} else {
String::new()
},
);
Ok(())
}
#[derive(Error, Debug, Display)]
pub enum EnvError {
EnvLookup {
var: String,
source: color_eyre::eyre::Error,
},
}