From 3b497379f3245d0fae09050124dee02ca98918e9 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:56:05 +0000 Subject: [PATCH] feat: allow _.file in vars --- docs/tasks/task-configuration.md | 9 ++++ e2e/tasks/test_task_vars | 14 +++++- src/cli/test_tool.rs | 7 +-- src/config/config_file/mise_toml.rs | 10 ++-- src/config/config_file/mod.rs | 20 ++------ src/config/env_directive/mod.rs | 10 ++-- src/config/env_directive/path.rs | 5 +- src/config/env_directive/venv.rs | 5 +- src/config/mod.rs | 74 +++++++++++++++++++++++------ src/task/mod.rs | 7 +-- src/task/task_script_parser.rs | 12 ++--- 11 files changed, 115 insertions(+), 58 deletions(-) diff --git a/docs/tasks/task-configuration.md b/docs/tasks/task-configuration.md index 66c082df7c..de6cb56ef5 100644 --- a/docs/tasks/task-configuration.md +++ b/docs/tasks/task-configuration.md @@ -361,3 +361,12 @@ e2e_args = '--headless' [tasks.test] run = './scripts/test-e2e.sh {{vars.e2e_args}}' ``` + +Like `[env]`, vars can also be read in as a file: + +```toml +[vars] +_.file = ".env" +``` + +[Secrets](/environments/secrets) are also supported as vars. diff --git a/e2e/tasks/test_task_vars b/e2e/tasks/test_task_vars index 945a5d071d..5033ffc9a9 100644 --- a/e2e/tasks/test_task_vars +++ b/e2e/tasks/test_task_vars @@ -4,5 +4,17 @@ cat <mise.toml tasks.a.run = "echo foo: {{vars.foo}}" vars.foo = "bar" EOF - assert "mise run a" "foo: bar" + +cat <mise.toml +tasks.a.run = "echo foo: {{vars.foo}}" +vars.foo = "{{cwd}}" +EOF +assert "mise run a" "foo: $(pwd)" + +echo '{ "SECRET": "123" }' >.env.json +cat <mise.toml +tasks.a.run = "echo foo: {{vars.SECRET}}" +vars._.file = ".env.json" +EOF +assert "mise run a" "foo: 123" diff --git a/src/cli/test_tool.rs b/src/cli/test_tool.rs index 843264e9a4..83dfadd5cf 100644 --- a/src/cli/test_tool.rs +++ b/src/cli/test_tool.rs @@ -2,7 +2,7 @@ use crate::cli::args::ToolArg; use crate::config::Config; use crate::file::display_path; use crate::registry::REGISTRY; -use crate::tera::{get_tera, BASE_CONTEXT}; +use crate::tera::get_tera; use crate::toolset::{InstallOptions, ToolsetBuilder}; use crate::ui::time; use crate::{dirs, env, file}; @@ -129,7 +129,8 @@ impl TestTool { return Ok(()); }; let backend = tv.backend()?; - let env = ts.env_with_path(&Config::get())?; + let config = Config::get(); + let env = ts.env_with_path(&config)?; let mut which_parts = cmd.split_whitespace().collect::>(); let cmd = which_parts.remove(0); let mut which_cmd = backend.which(&tv, cmd)?.unwrap_or(PathBuf::from(cmd)); @@ -170,7 +171,7 @@ impl TestTool { } None => return Err(eyre!("command failed: terminated by signal")), } - let mut ctx = BASE_CONTEXT.clone(); + let mut ctx = config.tera_ctx.clone(); ctx.insert("version", &tv.version); let mut tera = get_tera(dirs::CWD.as_ref().map(|d| d.as_path())); let expected = tera.render_str(expected, &ctx)?; diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index 85578ce8ca..97b4649c36 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -64,7 +64,7 @@ pub struct MiseToml { #[serde(default)] watch_files: Vec, #[serde(default)] - vars: IndexMap, + vars: EnvList, #[serde(default)] settings: SettingsPartial, } @@ -293,6 +293,10 @@ impl ConfigFile for MiseToml { Ok(all) } + fn vars_entries(&self) -> eyre::Result> { + Ok(self.vars.0.clone()) + } + fn tasks(&self) -> Vec<&Task> { self.tasks.0.values().collect() } @@ -486,10 +490,6 @@ impl ConfigFile for MiseToml { .flatten() .collect()) } - - fn vars(&self) -> eyre::Result<&IndexMap> { - Ok(&self.vars) - } } /// Returns a [`toml_edit::Key`] from the given `key`. diff --git a/src/config/config_file/mod.rs b/src/config/config_file/mod.rs index 5f4f4074d0..0110d34d0d 100644 --- a/src/config/config_file/mod.rs +++ b/src/config/config_file/mod.rs @@ -7,7 +7,6 @@ use std::sync::{Mutex, Once}; use eyre::{eyre, Result}; use idiomatic_version::IdiomaticVersionFile; -use indexmap::IndexMap; use once_cell::sync::Lazy; use path_absolutize::Absolutize; use serde_derive::Deserialize; @@ -25,7 +24,6 @@ use crate::hash::hash_to_str; use crate::hooks::Hook; use crate::redactions::Redactions; use crate::task::Task; -use crate::tera::{get_tera, BASE_CONTEXT}; use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionList, Toolset}; use crate::ui::{prompt, style}; use crate::watch_files::WatchFile; @@ -70,10 +68,13 @@ pub trait ConfigFile: Debug + Send + Sync { fn config_root(&self) -> PathBuf { config_root(self.get_path()) } - fn plugins(&self) -> eyre::Result> { + fn plugins(&self) -> Result> { Ok(Default::default()) } - fn env_entries(&self) -> eyre::Result> { + fn env_entries(&self) -> Result> { + Ok(Default::default()) + } + fn vars_entries(&self) -> Result> { Ok(Default::default()) } fn tasks(&self) -> Vec<&Task> { @@ -93,21 +94,10 @@ pub trait ConfigFile: Debug + Send + Sync { Ok(Default::default()) } - fn tera(&self) -> (tera::Tera, tera::Context) { - let tera = get_tera(Some(&self.config_root())); - let mut ctx = BASE_CONTEXT.clone(); - ctx.insert("config_root", &self.config_root()); - (tera, ctx) - } - fn task_config(&self) -> &TaskConfig { static DEFAULT_TASK_CONFIG: Lazy = Lazy::new(TaskConfig::default); &DEFAULT_TASK_CONFIG } - fn vars(&self) -> Result<&IndexMap> { - static DEFAULT_VARS: Lazy> = Lazy::new(IndexMap::new); - Ok(&DEFAULT_VARS) - } fn redactions(&self) -> &Redactions { static DEFAULT_REDACTIONS: Lazy = Lazy::new(Redactions::default); diff --git a/src/config/env_directive/mod.rs b/src/config/env_directive/mod.rs index 1a3a087825..6926bb5c6c 100644 --- a/src/config/env_directive/mod.rs +++ b/src/config/env_directive/mod.rs @@ -8,7 +8,7 @@ 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, BASE_CONTEXT}; +use crate::tera::get_tera; use eyre::{eyre, Context}; use indexmap::IndexMap; use serde::{Deserialize, Deserializer}; @@ -141,6 +141,7 @@ impl Display for EnvDirective { } } +#[derive(Default)] pub struct EnvResults { pub env: IndexMap, pub env_remove: BTreeSet, @@ -150,8 +151,11 @@ pub struct EnvResults { } impl EnvResults { - pub fn resolve(initial: &EnvMap, input: Vec<(EnvDirective, PathBuf)>) -> eyre::Result { - let mut ctx = BASE_CONTEXT.clone(); + pub fn resolve( + mut ctx: tera::Context, + initial: &EnvMap, + input: Vec<(EnvDirective, PathBuf)>, + ) -> eyre::Result { // trace!("resolve: input: {:#?}", &input); let mut env = initial .iter() diff --git a/src/config/env_directive/path.rs b/src/config/env_directive/path.rs index 6fad9b4d07..9d5807430e 100644 --- a/src/config/env_directive/path.rs +++ b/src/config/env_directive/path.rs @@ -38,20 +38,21 @@ impl EnvResults { #[cfg(test)] #[cfg(unix)] mod tests { + use super::*; use crate::config::env_directive::EnvDirective; use crate::env_diff::EnvMap; + use crate::tera::BASE_CONTEXT; use crate::test::replace_path; use insta::assert_debug_snapshot; use test_log::test; - use super::*; - #[test] fn test_env_path() { let mut env = EnvMap::new(); env.insert("A".to_string(), "1".to_string()); env.insert("B".to_string(), "2".to_string()); let results = EnvResults::resolve( + BASE_CONTEXT.clone(), &env, vec![ ( diff --git a/src/config/env_directive/venv.rs b/src/config/env_directive/venv.rs index 3a9b5b920a..f2d54c7874 100644 --- a/src/config/env_directive/venv.rs +++ b/src/config/env_directive/venv.rs @@ -146,17 +146,18 @@ impl EnvResults { #[cfg(test)] #[cfg(unix)] mod tests { + use super::*; use crate::config::env_directive::EnvDirective; + use crate::tera::BASE_CONTEXT; use crate::test::replace_path; use insta::assert_debug_snapshot; use test_log::test; - use super::*; - #[test] fn test_venv_path() { let env = EnvMap::new(); let results = EnvResults::resolve( + BASE_CONTEXT.clone(), &env, vec![ ( diff --git a/src/config/mod.rs b/src/config/mod.rs index 7c55f7625e..7970220cf2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -38,6 +38,7 @@ use crate::hook_env::WatchFilePattern; use crate::hooks::Hook; use crate::plugins::PluginType; use crate::redactions::Redactions; +use crate::tera::{get_tera, BASE_CONTEXT}; use crate::watch_files::WatchFile; use crate::wildcard::Wildcard; pub use settings::SETTINGS; @@ -46,13 +47,13 @@ type AliasMap = IndexMap; type ConfigMap = IndexMap>; pub type EnvWithSources = IndexMap; -#[derive(Default)] pub struct Config { pub config_files: ConfigMap, pub project_root: Option, pub all_aliases: AliasMap, pub repo_urls: HashMap, pub vars: IndexMap, + pub tera_ctx: tera::Context, aliases: AliasMap, env: OnceCell, env_with_sources: OnceCell, @@ -105,13 +106,31 @@ impl Config { let config_files = load_all_config_files(&config_paths, &idiomatic_files)?; time!("load config_files"); + let mut tera_ctx = BASE_CONTEXT.clone(); + let vars_results = load_vars(tera_ctx.clone(), &config_files)?; + let vars = vars_results + .env + .iter() + .map(|(k, (v, _))| (k.clone(), v.clone())) + .collect(); + tera_ctx.insert("vars", &vars); + let mut config = Self { aliases: load_aliases(&config_files)?, project_root: get_project_root(&config_files), repo_urls: load_plugins(&config_files)?, - vars: load_vars(&config_files)?, + tera_ctx, + vars, config_files, - ..Default::default() + env: OnceCell::new(), + env_with_sources: OnceCell::new(), + redactions: OnceCell::new(), + shorthands: OnceLock::new(), + hooks: OnceCell::new(), + tasks: OnceCell::new(), + tool_request_set: OnceCell::new(), + toolset: OnceCell::new(), + all_aliases: Default::default(), }; time!("load build"); @@ -185,7 +204,7 @@ impl Config { Ok(env) }) } - pub fn env_results(&self) -> eyre::Result<&EnvResults> { + pub fn env_results(&self) -> Result<&EnvResults> { self.env.get_or_try_init(|| self.load_env()) } pub fn path_dirs(&self) -> eyre::Result<&Vec> { @@ -548,7 +567,7 @@ impl Config { Ok(()) } - fn load_env(&self) -> eyre::Result { + fn load_env(&self) -> Result { time!("load_env start"); let entries = self .config_files @@ -563,7 +582,7 @@ impl Config { .flatten() .collect(); // trace!("load_env: entries: {:#?}", entries); - let env_results = EnvResults::resolve(&env::PRISTINE_ENV, entries)?; + let env_results = EnvResults::resolve(self.tera_ctx.clone(), &env::PRISTINE_ENV, entries)?; time!("load_env done"); if log::log_enabled!(log::Level::Trace) { trace!("{env_results:#?}"); @@ -629,6 +648,13 @@ impl Config { .collect()) } + fn tera(&self, config_root: &Path) -> (tera::Tera, tera::Context) { + let tera = get_tera(Some(config_root)); + let mut ctx = self.tera_ctx.clone(); + ctx.insert("config_root", &config_root); + (tera, ctx) + } + pub fn redactions(&self) -> Result<&IndexSet> { self.redactions.get_or_try_init(|| { let mut redactions = Redactions::default(); @@ -636,7 +662,7 @@ impl Config { let r = cf.redactions(); if !r.is_empty() { let mut r = r.clone(); - let (tera, ctx) = cf.tera(); + let (tera, ctx) = self.tera(&cf.config_root()); r.render(&mut tera.clone(), &ctx)?; redactions.merge(r); } @@ -1041,15 +1067,27 @@ fn load_plugins(config_files: &ConfigMap) -> Result> { Ok(plugins) } -fn load_vars(config_files: &ConfigMap) -> Result> { - let mut vars = IndexMap::new(); - for config_file in config_files.values() { - for (k, v) in config_file.vars()?.clone() { - vars.insert(k, v); - } +fn load_vars(ctx: tera::Context, config_files: &ConfigMap) -> Result { + time!("load_vars start"); + let entries = config_files + .iter() + .rev() + .map(|(source, cf)| { + cf.vars_entries() + .map(|ee| ee.into_iter().map(|e| (e, source.clone()))) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect(); + let vars_results = EnvResults::resolve(ctx, &env::PRISTINE_ENV, entries)?; + time!("load_vars done"); + if log::log_enabled!(log::Level::Trace) { + trace!("{vars_results:#?}"); + } else { + debug!("{vars_results:?}"); } - trace!("load_vars: {}", vars.len()); - Ok(vars) + Ok(vars_results) } impl Debug for Config { @@ -1077,6 +1115,12 @@ impl Debug for Config { if !env_results.env_files.is_empty() { s.field("Path Dirs", &env_results.env_paths); } + if !env_results.env_scripts.is_empty() { + s.field("Scripts", &env_results.env_scripts); + } + if !env_results.env_files.is_empty() { + s.field("Files", &env_results.env_files); + } } if !self.aliases.is_empty() { s.field("Aliases", &self.aliases); diff --git a/src/task/mod.rs b/src/task/mod.rs index 80e5d8dcd0..886b85b79a 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -3,7 +3,7 @@ use crate::config::Config; use crate::task::task_script_parser::{ has_any_args_defined, replace_template_placeholders_with_args, TaskScriptParser, }; -use crate::tera::{get_tera, BASE_CONTEXT}; +use crate::tera::get_tera; use crate::ui::tree::TreeItem; use crate::{dirs, file}; use console::{truncate_str, Color}; @@ -368,7 +368,7 @@ impl Task { { let config_root = self.config_root.clone().unwrap_or_default(); let mut tera = get_tera(Some(&config_root)); - let mut tera_ctx = BASE_CONTEXT.clone(); + let mut tera_ctx = config.tera_ctx.clone(); tera_ctx.insert("config_root", &config_root); let dir = tera.render_str(&dir, &tera_ctx)?; let dir = file::replace_path(&dir); @@ -404,8 +404,9 @@ impl Task { } pub fn render(&mut self, config_root: &Path) -> Result<()> { + let config = Config::get(); let mut tera = get_tera(Some(config_root)); - let mut tera_ctx = BASE_CONTEXT.clone(); + let mut tera_ctx = config.tera_ctx.clone(); tera_ctx.insert("config_root", &config_root); for a in &mut self.aliases { *a = tera.render_str(a, &tera_ctx)?; diff --git a/src/task/task_script_parser.rs b/src/task/task_script_parser.rs index ccd5260360..ae12a9b6a1 100644 --- a/src/task/task_script_parser.rs +++ b/src/task/task_script_parser.rs @@ -1,9 +1,8 @@ use crate::config::{Config, SETTINGS}; use crate::shell::ShellType; use crate::task::Task; -use crate::tera::{get_tera, BASE_CONTEXT}; +use crate::tera::get_tera; use eyre::Result; -use indexmap::IndexMap; use itertools::Itertools; use std::collections::HashMap; use std::iter::once; @@ -271,14 +270,9 @@ impl TaskScriptParser { } } }); - let mut ctx = BASE_CONTEXT.clone(); + let config = Config::get(); + let mut ctx = config.tera_ctx.clone(); ctx.insert("config_root", config_root); - let mut vars = IndexMap::new(); - ctx.insert("vars", &vars); - for (k, v) in &Config::get().vars { - vars.insert(k.clone(), tera.render_str(v, &ctx).unwrap()); - ctx.insert("vars", &vars); - } let scripts = scripts .iter() .map(|s| tera.render_str(s.trim(), &ctx).unwrap())