Skip to content

Commit

Permalink
env-man
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Jan 25, 2024
1 parent 1018b56 commit 871abf9
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}'"))?;
Expand Down
2 changes: 1 addition & 1 deletion src/config/config_file/tool_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl ToolVersions {

pub fn parse_str(s: &str, path: PathBuf) -> Result<Self> {
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 {
Expand Down
155 changes: 155 additions & 0 deletions src/env_man.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 {
/// simple key/value pair
Val(String, String),
/// remove a key
Rm(String),
/// key/value rendered as a tera template
Tmpl(String, String),
/// add a path to the PATH
Path(PathBuf),
/// run a bash script and apply the resulting env diff
Script(PathBuf),
// Plugin(String, String),
}

#[derive(Debug, Default)]
pub struct EnvManOpts {
pub source: PathBuf,
pub project_root: Option<PathBuf>,
// pub cache: Option<EnvManOptsCache>,
}

// #[derive(Debug)]
// pub struct EnvManOptsCache {
// pub key: String,
// pub globs: Vec<String>,
// }

impl EnvMan {
fn new() -> Self {
Self { entries: vec![] }
}

pub fn render(&self) -> eyre::Result<IndexMap<String, String>> {
let mut env: IndexMap<String, String> = 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!(env["PATH"].starts_with("/foo/bar"));

em.entries
.push((EnvManEntry::Path("/baz/qux".into()), Default::default()));
let env = em.render().unwrap();
assert!(env["PATH"].starts_with("/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");
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod dirs;
pub mod duration;
mod env;
mod env_diff;
mod env_man;
mod errors;
mod fake_asdf;
mod file;
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/external_plugin_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ fn parse_template(config: &Config, tv: &ToolVersion, tmpl: &str) -> Result<Strin
config
.project_root
.as_ref()
.unwrap_or(&env::current_dir().unwrap()),
.or(env::current_dir().as_ref().ok())
.map(|p| p.as_path()),
)
.render_str(tmpl, &ctx)
.wrap_err_with(|| eyre!("failed to parse template: {tmpl}"))
Expand Down
2 changes: 1 addition & 1 deletion src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Task {
let config_root = config_root(path);
let mut tera_ctx = BASE_CONTEXT.clone();
tera_ctx.insert("config_root", &config_root);
let p = TomlParser::new(&info, get_tera(config_root), tera_ctx);
let p = TomlParser::new(&info, get_tera(Some(config_root)), tera_ctx);
// trace!("task info: {:#?}", info);

let name = path.file_name().unwrap().to_str().unwrap().to_string();
Expand Down
13 changes: 7 additions & 6 deletions src/tera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ pub static BASE_CONTEXT: Lazy<Context> = 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<String, Value>| -> tera::Result<Value> {
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()),
Expand Down

0 comments on commit 871abf9

Please sign in to comment.