Skip to content

Commit

Permalink
feat: added cache feature to templates (jdx#3608)
Browse files Browse the repository at this point in the history
Fixes jdx#1261
  • Loading branch information
jdx authored Dec 16, 2024
1 parent 9c0c1ce commit 48c95c6
Show file tree
Hide file tree
Showing 4 changed files with 113 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"
33 changes: 10 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,15 @@ 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
78 changes: 61 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,68 @@ 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 48c95c6

Please sign in to comment.