Skip to content

Commit

Permalink
feat: added cache feature to templates
Browse files Browse the repository at this point in the history
Fixes #1261
  • Loading branch information
jdx committed Dec 16, 2024
1 parent ca33515 commit ae592c2
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 40 deletions.
12 changes: 12 additions & 0 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ An example of function using `exec`:
current = "{{ exec(command='node --version') }}"
```

### Exec Options

The `exec` function supports the following options:

- `command: String`[required] The command to run.
- `cache_key: String` – The cache key to store the result.
If the cache key is provided, the result will be cached and reused
for subsequent calls.
- `cache_duration: String` – The duration to cache the result.
The duration is in seconds, minutes, hours, days, or weeks.
e.g. `cache_duration="1d"` will cache the result for 1 day.

### Filters

Tera offers many [built-in filters](https://keats.github.io/tera/docs/#built-in-filters).
Expand Down
30 changes: 30 additions & 0 deletions e2e/env/test_env_tmpl_cache
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

cat <<'EOF' >mise.toml
[env]
NOW="{{ exec(command='date') }}"
EOF

now=$(mise env --json | jq -r '.NOW')
sleep 1
assert_not_contains "mise env --json | jq -r '.NOW'" "$now"

cat <<'EOF' >mise.toml
[env]
NOW="{{ exec(command='date', cache_key='now') }}"
EOF

now=$(mise env --json | jq -r '.NOW')
sleep 1
assert "mise env --json | jq -r '.NOW'" "$now"

cat <<'EOF' >mise.toml
[env]
NOW="{{ exec(command='date', cache_duration='2s') }}"
EOF

now=$(mise env --json | jq -r '.NOW')
sleep 1
assert "mise env --json | jq -r '.NOW'" "$now"
sleep 1
assert_not_contains "mise env --json | jq -r '.NOW'" "$now"
27 changes: 4 additions & 23 deletions src/config/env_directive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use std::collections::{BTreeSet, HashMap};
use std::fmt::{Debug, Display, Formatter};
use std::path::{Path, PathBuf};

use crate::cmd::cmd;
use crate::config::config_file::{config_root, trust_check};
use crate::dirs;
use crate::env_diff::EnvMap;
use crate::file::display_path;
use crate::tera::get_tera;
use crate::tera::{get_tera, tera_exec};
use eyre::{eyre, Context};
use indexmap::IndexMap;

Expand Down Expand Up @@ -150,27 +149,9 @@ impl EnvResults {
continue;
}
let mut tera = get_tera(source.parent());
tera.register_function("exec", {
let source = source.clone();
let env = env.clone();
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
match args.get("command") {
Some(tera::Value::String(command)) => {
let env = env::PRISTINE_ENV
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.chain(env.iter().map(|(k, (v, _))| (k.to_string(), v.to_string())))
.collect::<EnvMap>();
let result = cmd("bash", ["-c", command])
.full_env(&env)
.dir(config_root(&source))
.read()?;
Ok(tera::Value::String(result))
}
_ => Err("exec command must be a string".into()),
}
}
});
tera.register_function("exec", tera_exec(source.parent().map(|d| d.to_path_buf()), env.iter()
.map(|(k, (v, _))| (k.clone(), v.clone()))
.collect()));
// trace!(
// "resolve: directive: {:?}, source: {:?}",
// &directive,
Expand Down
66 changes: 49 additions & 17 deletions src/tera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ use rand::thread_rng;
use tera::{Context, Tera, Value};
use versions::{Requirement, Versioning};

use crate::cache::CacheManagerBuilder;
use crate::cmd::cmd;
use crate::{env, hash};
use crate::env_diff::EnvMap;
use crate::{dirs, env, hash};

pub static BASE_CONTEXT: Lazy<Context> = Lazy::new(|| {
let mut context = Context::new();
Expand Down Expand Up @@ -297,26 +299,56 @@ static TERA: Lazy<Tera> = Lazy::new(|| {
pub fn get_tera(dir: Option<&Path>) -> Tera {
let mut tera = TERA.clone();
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 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()),
}
},
);
tera.register_function("exec", tera_exec(dir, env::PRISTINE_ENV.clone()));

tera
}

pub fn tera_exec(dir: Option<PathBuf>, env: EnvMap) -> impl Fn(&HashMap<String, Value>) -> tera::Result<Value> {
move |args: &HashMap<String, Value>| -> tera::Result<Value> {
let cache = match args.get("cache_key") {
Some(Value::String(cache)) => Some(cache),
None => None,
_ => return Err("exec cache_key must be a string".into()),
};
let cache_duration = match args.get("cache_duration") {
Some(Value::String(duration)) => match humantime::parse_duration(&duration.to_string()) {
Ok(duration) => Some(duration),
Err(e) => return Err(format!("exec cache_duration: {}", e).into()),
}
None => None,
_ => return Err("exec cache_duration must be an integer".into()),
};
match args.get("command") {
Some(Value::String(command)) => {
let mut cmd = cmd("bash", ["-c", command]).full_env(&env);
if let Some(dir) = &dir {
cmd = cmd.dir(dir);
}
let result = if cache.is_some() || cache_duration.is_some() {
let cachehash = hash::hash_sha256_to_str(&(dir.as_ref().map(|d| d.to_string_lossy().to_string()).unwrap_or_default() + command))[..8].to_string();
let mut cacheman = CacheManagerBuilder::new(dirs::CACHE.join("exec").join(cachehash));
if let Some(cache) = cache {
cacheman = cacheman.with_cache_key(cache.clone());
}
if let Some(cache_duration) = cache_duration {
cacheman = cacheman.with_fresh_duration(Some(cache_duration));
}
let cache = cacheman.build();
match cache.get_or_try_init(|| Ok(cmd.read()?)) {
Ok(result) => result.clone(),
Err(e) => return Err(format!("exec command: {}", e).into()),
}
} else {
cmd.read()?
};
Ok(Value::String(result))
}
_ => Err("exec command must be a string".into()),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit ae592c2

Please sign in to comment.