mirror of https://github.com/jdx/mise
parent
9c0c1ce943
commit
48c95c6527
|
@ -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).
|
||||
|
|
|
@ -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"
|
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
|
78
src/tera.rs
78
src/tera.rs
|
@ -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();
|
||||
|
@ -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::*;
|
||||
|
|
Loading…
Reference in New Issue