From 7fc3b23f6ea245e3687a18ac366835cbc170873a Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdx@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:40:04 -0600 Subject: [PATCH] env-man --- src/config/config_file/mise_toml.rs | 2 +- src/config/config_file/tool_versions.rs | 2 +- src/env_man.rs | 150 ++++++++++++++++++++++++ src/main.rs | 1 + src/plugins/external_plugin_cache.rs | 3 +- src/task.rs | 2 +- src/tera.rs | 13 +- 7 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 src/env_man.rs diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index 89bb074034..6140fa2ab8 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -590,7 +590,7 @@ impl MiseToml { return Ok(input.to_string()); } trust_check(&self.path)?; - let dir = self.path.parent().unwrap(); + let dir = self.path.parent(); let output = get_tera(dir) .render_str(input, &self.context) .wrap_err_with(|| eyre!("failed to parse template: {k}='{input}'"))?; diff --git a/src/config/config_file/tool_versions.rs b/src/config/config_file/tool_versions.rs index e2d3df0e35..86b82ad00e 100644 --- a/src/config/config_file/tool_versions.rs +++ b/src/config/config_file/tool_versions.rs @@ -55,7 +55,7 @@ impl ToolVersions { pub fn parse_str(s: &str, path: PathBuf) -> Result { let mut cf = Self::init(&path); - let dir = path.parent().unwrap(); + let dir = path.parent(); let s = if config_file::is_trusted(&path) { get_tera(dir).render_str(s, &cf.context)? } else { diff --git a/src/env_man.rs b/src/env_man.rs new file mode 100644 index 0000000000..e24005d451 --- /dev/null +++ b/src/env_man.rs @@ -0,0 +1,150 @@ +use std::path::PathBuf; + +use indexmap::IndexMap; +use itertools::Itertools; + +use crate::env; +use crate::env_diff::{EnvDiff, EnvDiffOperation}; +use crate::tera::{get_tera, BASE_CONTEXT}; + +#[derive(Debug, Default)] +pub struct EnvMan { + entries: Vec<(EnvManEntry, EnvManOpts)>, +} + +#[derive(Debug)] +pub enum EnvManEntry { + Val(String, String), + Rm(String), + Tmpl(String, String), + Path(PathBuf), + Script(PathBuf), + // Plugin(String, String), +} + +#[derive(Debug, Default)] +pub struct EnvManOpts { + pub source: PathBuf, + pub project_root: Option, + // pub cache: Option, +} + +// #[derive(Debug)] +// pub struct EnvManOptsCache { +// pub key: String, +// pub globs: Vec, +// } + +impl EnvMan { + fn new() -> Self { + Self { entries: vec![] } + } + + pub fn render(&self) -> eyre::Result> { + let mut env: IndexMap = env::PRISTINE_ENV.clone().into_iter().collect(); + let ctx = BASE_CONTEXT.clone(); + for (entry, opts) in self.entries.iter() { + match entry { + EnvManEntry::Val(k, v) => { + env.insert(k.clone(), v.clone()); + } + EnvManEntry::Rm(k) => { + env.remove(k); + } + EnvManEntry::Tmpl(k, v) => { + let mut tera = get_tera(opts.project_root.as_deref()); + let v = tera.render_str(v, &ctx)?; + env.insert(k.clone(), v); + } + EnvManEntry::Path(p) => { + let mut path = env::split_paths(p).collect_vec(); + if let Some(orig) = env.get("PATH") { + path = path.into_iter().chain(env::split_paths(orig)).collect_vec(); + } + let path = env::join_paths(path)?; + env.insert("PATH".into(), path.to_string_lossy().to_string()); + } + EnvManEntry::Script(script) => { + // TODO: set cwd + let ed = EnvDiff::from_bash_script(script, &env)?; + for p in ed.to_patches() { + match p { + EnvDiffOperation::Add(k, v) | EnvDiffOperation::Change(k, v) => { + env.insert(k, v); + } + EnvDiffOperation::Remove(k) => { + env.remove(&k); + } + } + } + } + } + } + Ok(env) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_var() { + let mut em = EnvMan::new(); + em.entries.push(( + EnvManEntry::Val("FOO".into(), "BAR".into()), + Default::default(), + )); + let env = em.render().unwrap(); + assert_eq!(env["FOO"], "BAR"); + } + + #[test] + fn test_rm() { + let mut em = EnvMan::new(); + em.entries.push(( + EnvManEntry::Val("FOO".into(), "BAR".into()), + Default::default(), + )); + em.entries + .push((EnvManEntry::Rm("FOO".into()), Default::default())); + let env = em.render().unwrap(); + assert!(!env.contains_key("FOO")); + } + + #[test] + fn test_tmpl() { + let mut em = EnvMan::new(); + em.entries.push(( + EnvManEntry::Tmpl("FOO".into(), "{{ 1 + 1 }}".into()), + Default::default(), + )); + let env = em.render().unwrap(); + assert_eq!(env["FOO"], "2"); + } + + #[test] + fn test_path() { + let mut em = EnvMan::new(); + em.entries + .push((EnvManEntry::Path("/foo/bar".into()), Default::default())); + let env = em.render().unwrap(); + assert_eq!(env["PATH"], "/foo/bar"); + + em.entries + .push((EnvManEntry::Path("/baz/qux".into()), Default::default())); + let env = em.render().unwrap(); + assert_eq!(env["PATH"], "/baz/qux:/foo/bar"); + } + + #[test] + fn test_script() { + let mut em = EnvMan::new(); + em.entries.push(( + EnvManEntry::Script("../fixtures/exec-env".into()), + Default::default(), + )); + let env = em.render().unwrap(); + assert_eq!(env["UNMODIFIED_VAR"], "unmodified"); + } +} diff --git a/src/main.rs b/src/main.rs index ba96691b65..84ca9829ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ mod dirs; pub mod duration; mod env; mod env_diff; +mod env_man; mod errors; mod fake_asdf; mod file; diff --git a/src/plugins/external_plugin_cache.rs b/src/plugins/external_plugin_cache.rs index 5c79e70cfe..d37aedcbff 100644 --- a/src/plugins/external_plugin_cache.rs +++ b/src/plugins/external_plugin_cache.rs @@ -99,7 +99,8 @@ fn parse_template(config: &Config, tv: &ToolVersion, tmpl: &str) -> Result = Lazy::new(|| { context }); -pub fn get_tera(dir: &Path) -> Tera { +pub fn get_tera(dir: Option<&Path>) -> Tera { let mut tera = Tera::default(); - let dir = dir.to_path_buf(); + let dir = dir.map(PathBuf::from); tera.register_function( "exec", move |args: &HashMap| -> tera::Result { match args.get("command") { Some(Value::String(command)) => { - let result = cmd("bash", ["-c", command]) - .dir(&dir) - .full_env(&*env::PRISTINE_ENV) - .read()?; + let mut cmd = cmd("bash", ["-c", command]).full_env(&*env::PRISTINE_ENV); + if let Some(dir) = &dir { + cmd = cmd.dir(dir); + } + let result = cmd.read()?; Ok(Value::String(result)) } _ => Err("exec command must be a string".into()),