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') }}"
|
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
|
### Filters
|
||||||
|
|
||||||
Tera offers many [built-in filters](https://keats.github.io/tera/docs/#built-in-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::fmt::{Debug, Display, Formatter};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::cmd::cmd;
|
|
||||||
use crate::config::config_file::{config_root, trust_check};
|
use crate::config::config_file::{config_root, trust_check};
|
||||||
use crate::dirs;
|
use crate::dirs;
|
||||||
use crate::env_diff::EnvMap;
|
use crate::env_diff::EnvMap;
|
||||||
use crate::file::display_path;
|
use crate::file::display_path;
|
||||||
use crate::tera::get_tera;
|
use crate::tera::{get_tera, tera_exec};
|
||||||
use eyre::{eyre, Context};
|
use eyre::{eyre, Context};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
@ -150,27 +149,15 @@ impl EnvResults {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut tera = get_tera(source.parent());
|
let mut tera = get_tera(source.parent());
|
||||||
tera.register_function("exec", {
|
tera.register_function(
|
||||||
let source = source.clone();
|
"exec",
|
||||||
let env = env.clone();
|
tera_exec(
|
||||||
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
source.parent().map(|d| d.to_path_buf()),
|
||||||
match args.get("command") {
|
env.iter()
|
||||||
Some(tera::Value::String(command)) => {
|
.map(|(k, (v, _))| (k.clone(), v.clone()))
|
||||||
let env = env::PRISTINE_ENV
|
.collect(),
|
||||||
.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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// trace!(
|
// trace!(
|
||||||
// "resolve: directive: {:?}, source: {:?}",
|
// "resolve: directive: {:?}, source: {:?}",
|
||||||
// &directive,
|
// &directive,
|
||||||
|
|
78
src/tera.rs
78
src/tera.rs
|
@ -11,8 +11,10 @@ use rand::thread_rng;
|
||||||
use tera::{Context, Tera, Value};
|
use tera::{Context, Tera, Value};
|
||||||
use versions::{Requirement, Versioning};
|
use versions::{Requirement, Versioning};
|
||||||
|
|
||||||
|
use crate::cache::CacheManagerBuilder;
|
||||||
use crate::cmd::cmd;
|
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(|| {
|
pub static BASE_CONTEXT: Lazy<Context> = Lazy::new(|| {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
|
@ -297,26 +299,68 @@ static TERA: Lazy<Tera> = Lazy::new(|| {
|
||||||
pub fn get_tera(dir: Option<&Path>) -> Tera {
|
pub fn get_tera(dir: Option<&Path>) -> Tera {
|
||||||
let mut tera = TERA.clone();
|
let mut tera = TERA.clone();
|
||||||
let dir = dir.map(PathBuf::from);
|
let dir = dir.map(PathBuf::from);
|
||||||
tera.register_function(
|
tera.register_function("exec", tera_exec(dir, env::PRISTINE_ENV.clone()));
|
||||||
"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
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in New Issue