mirror of https://github.com/jdx/mise
feat: lazy env eval (#3598)
Fixes https://github.com/jdx/mise/issues/1912
This commit is contained in:
parent
234975c43a
commit
ca33515f3e
|
@ -31,6 +31,18 @@ NODE_ENV development mise.toml
|
|||
$ mise unset NODE_ENV
|
||||
```
|
||||
|
||||
## Lazy eval
|
||||
|
||||
Environment variables typically are resolved before tools—that way you can configure tool installation
|
||||
with environment variables. However, sometimes you want to access environment variables produced by
|
||||
tools. To do that, turn the value into a map with `tools = true`:
|
||||
|
||||
```toml
|
||||
[env]
|
||||
MY_VAR = { value = "tools path: {{env.PATH}}", tools = true }
|
||||
_.path = { value = ["{{env.GEM_HOME}}/bin"], tools = true } # directives may also set tools = true
|
||||
```
|
||||
|
||||
## `env._` directives
|
||||
|
||||
`env._.*` define special behavior for setting environment variables. (e.g.: reading env vars
|
||||
|
@ -57,6 +69,15 @@ not to mise since there is not much mise can do about the way that crate works.
|
|||
Or set [`MISE_ENV_FILE=.env`](/configuration#mise-env-file) to automatically load dotenv files in any
|
||||
directory.
|
||||
|
||||
You can also use json or yaml files:
|
||||
|
||||
```toml
|
||||
[env]
|
||||
_.file = '.env.json'
|
||||
```
|
||||
|
||||
See [secrets](/environments/secrets) for ways to read encrypted files with `env._.file`.
|
||||
|
||||
### `env._.path`
|
||||
|
||||
`PATH` is treated specially, it needs to be defined as a string/array in `mise.path`:
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cat <<EOF >mise.toml
|
||||
[[env]]
|
||||
A_PATH = "foo: {{ env.PATH }}"
|
||||
B_PATH = { value = "foo: {{ env.PATH }}", tools = true }
|
||||
[[env]]
|
||||
_.path = {value = "tiny-{{env.JDXCODE_TINY}}-tiny", tools = true}
|
||||
|
||||
[tools]
|
||||
tiny = "1.0.0"
|
||||
EOF
|
||||
|
||||
mise i
|
||||
assert_not_contains "mise env | grep A_PATH" "tiny"
|
||||
assert_contains "mise env | grep B_PATH" "tiny"
|
||||
|
||||
assert_contains "mise dr path" "tiny-1.0.0-tiny"
|
|
@ -359,6 +359,7 @@ cmd "doctor" help="Check mise installation for possible problems" {
|
|||
[WARN] plugin node is not installed
|
||||
"
|
||||
cmd "path" help="Print the current PATH entries mise is providing" {
|
||||
alias "paths" hide=true
|
||||
after_long_help r"Examples:
|
||||
|
||||
Get the current PATH entries mise is providing
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::env;
|
|||
|
||||
/// Print the current PATH entries mise is providing
|
||||
#[derive(Debug, clap::Args)]
|
||||
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
|
||||
#[clap(alias="paths", verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
|
||||
pub struct Path {
|
||||
/// Print all entries including those not provided by mise
|
||||
#[clap(long, short, verbatim_doc_comment)]
|
||||
|
|
|
@ -68,7 +68,7 @@ impl Set {
|
|||
if env_vars.len() == 1 && env_vars[0].value.is_none() {
|
||||
let key = &env_vars[0].key;
|
||||
match config.env_entries()?.into_iter().find_map(|ev| match ev {
|
||||
EnvDirective::Val(k, v) if &k == key => Some(v),
|
||||
EnvDirective::Val(k, v, _) if &k == key => Some(v),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(value) => miseprintln!("{value}"),
|
||||
|
@ -122,7 +122,7 @@ impl Set {
|
|||
.env_entries()?
|
||||
.into_iter()
|
||||
.filter_map(|ed| match ed {
|
||||
EnvDirective::Val(key, value) => Some(Row {
|
||||
EnvDirective::Val(key, value, _) => Some(Row {
|
||||
key,
|
||||
value,
|
||||
source: display_path(file),
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use eyre::{eyre, WrapErr};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
|
@ -9,14 +5,18 @@ use once_cell::sync::OnceCell;
|
|||
use serde::de::Visitor;
|
||||
use serde::{de, Deserializer};
|
||||
use serde_derive::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use tera::Context as TeraContext;
|
||||
use toml_edit::{table, value, Array, DocumentMut, InlineTable, Item, Key, Value};
|
||||
use versions::Versioning;
|
||||
|
||||
use crate::cli::args::{BackendArg, ToolVersionType};
|
||||
use crate::config::config_file::toml::{deserialize_arr, deserialize_path_entry_arr};
|
||||
use crate::config::config_file::toml::deserialize_arr;
|
||||
use crate::config::config_file::{config_trust_root, trust, trust_check, ConfigFile, TaskConfig};
|
||||
use crate::config::env_directive::{EnvDirective, PathEntry};
|
||||
use crate::config::env_directive::{EnvDirective, EnvDirectiveOptions};
|
||||
use crate::config::settings::SettingsPartial;
|
||||
use crate::config::{Alias, AliasMap};
|
||||
use crate::file::{create_dir_all, display_path};
|
||||
|
@ -40,11 +40,11 @@ pub struct MiseToml {
|
|||
#[serde(skip)]
|
||||
path: PathBuf,
|
||||
#[serde(default, alias = "dotenv", deserialize_with = "deserialize_arr")]
|
||||
env_file: Vec<PathBuf>,
|
||||
env_file: Vec<String>,
|
||||
#[serde(default)]
|
||||
env: EnvList,
|
||||
#[serde(default, deserialize_with = "deserialize_arr")]
|
||||
env_path: Vec<PathEntry>,
|
||||
env_path: Vec<String>,
|
||||
#[serde(default)]
|
||||
alias: AliasMap,
|
||||
#[serde(skip)]
|
||||
|
@ -79,6 +79,12 @@ pub struct MiseTomlTool {
|
|||
pub options: Option<ToolVersionOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MiseTomlEnvDirective {
|
||||
pub value: String,
|
||||
pub options: EnvDirectiveOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Tasks(pub BTreeMap<String, Task>);
|
||||
|
||||
|
@ -278,12 +284,12 @@ impl ConfigFile for MiseToml {
|
|||
let path_entries = self
|
||||
.env_path
|
||||
.iter()
|
||||
.map(|p| EnvDirective::Path(p.clone()))
|
||||
.map(|p| EnvDirective::Path(p.clone(), Default::default()))
|
||||
.collect_vec();
|
||||
let env_files = self
|
||||
.env_file
|
||||
.iter()
|
||||
.map(|p| EnvDirective::File(p.clone()))
|
||||
.map(|p| EnvDirective::File(p.clone(), Default::default()))
|
||||
.collect_vec();
|
||||
let all = path_entries
|
||||
.into_iter()
|
||||
|
@ -707,7 +713,7 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
match key.as_str() {
|
||||
"_" | "mise" => {
|
||||
struct EnvDirectivePythonVenv {
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
create: bool,
|
||||
python: Option<String>,
|
||||
uv_create_args: Option<Vec<String>>,
|
||||
|
@ -723,12 +729,12 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct EnvDirectives {
|
||||
#[serde(default, deserialize_with = "deserialize_path_entry_arr")]
|
||||
path: Vec<PathEntry>,
|
||||
#[serde(default, deserialize_with = "deserialize_arr")]
|
||||
file: Vec<PathBuf>,
|
||||
path: Vec<MiseTomlEnvDirective>,
|
||||
#[serde(default, deserialize_with = "deserialize_arr")]
|
||||
source: Vec<PathBuf>,
|
||||
file: Vec<MiseTomlEnvDirective>,
|
||||
#[serde(default, deserialize_with = "deserialize_arr")]
|
||||
source: Vec<MiseTomlEnvDirective>,
|
||||
#[serde(default)]
|
||||
python: EnvDirectivePython,
|
||||
#[serde(flatten)]
|
||||
|
@ -826,17 +832,17 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
|
||||
let directives = map.next_value::<EnvDirectives>()?;
|
||||
// TODO: parse these in the order they're defined somehow
|
||||
for path in directives.path {
|
||||
env.push(EnvDirective::Path(path));
|
||||
for d in directives.path {
|
||||
env.push(EnvDirective::Path(d.value, d.options));
|
||||
}
|
||||
for file in directives.file {
|
||||
env.push(EnvDirective::File(file));
|
||||
for d in directives.file {
|
||||
env.push(EnvDirective::File(d.value, d.options));
|
||||
}
|
||||
for source in directives.source {
|
||||
env.push(EnvDirective::Source(source));
|
||||
for d in directives.source {
|
||||
env.push(EnvDirective::Source(d.value, d.options));
|
||||
}
|
||||
for (key, value) in directives.other {
|
||||
env.push(EnvDirective::Module(key, value));
|
||||
env.push(EnvDirective::Module(key, value, Default::default()));
|
||||
}
|
||||
if let Some(venv) = directives.python.venv {
|
||||
env.push(EnvDirective::PythonVenv {
|
||||
|
@ -845,6 +851,7 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
python: venv.python,
|
||||
uv_create_args: venv.uv_create_args,
|
||||
python_create_args: venv.python_create_args,
|
||||
options: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -853,18 +860,33 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
Int(i64),
|
||||
Str(String),
|
||||
Bool(bool),
|
||||
Map { value: Box<Val>, tools: bool },
|
||||
}
|
||||
impl Display for Val {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Val::Int(i) => write!(f, "{}", i),
|
||||
Val::Str(s) => write!(f, "{}", s),
|
||||
Val::Bool(b) => write!(f, "{}", b),
|
||||
Val::Map { value, tools } => {
|
||||
write!(f, "{}", value)?;
|
||||
if *tools {
|
||||
write!(f, " tools")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for Val {
|
||||
fn deserialize<D>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<Self, D::Error>
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ValVisitor;
|
||||
|
||||
impl Visitor<'_> for ValVisitor {
|
||||
impl<'de> Visitor<'de> for ValVisitor {
|
||||
type Value = Val;
|
||||
fn expecting(
|
||||
&self,
|
||||
|
@ -878,12 +900,7 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match v {
|
||||
true => Err(de::Error::custom(
|
||||
"env values cannot be true",
|
||||
)),
|
||||
false => Ok(Val::Bool(v)),
|
||||
}
|
||||
Ok(Val::Bool(v))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
|
@ -899,6 +916,46 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
{
|
||||
Ok(Val::Str(v.to_string()))
|
||||
}
|
||||
|
||||
fn visit_map<A>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::MapAccess<'de>,
|
||||
{
|
||||
let mut value: Option<Val> = None;
|
||||
let mut tools = None;
|
||||
while let Some((key, val)) =
|
||||
map.next_entry::<String, Val>()?
|
||||
{
|
||||
match key.as_str() {
|
||||
"value" => {
|
||||
value = Some(val);
|
||||
}
|
||||
"tools" => {
|
||||
tools = Some(val);
|
||||
}
|
||||
_ => {
|
||||
return Err(de::Error::unknown_field(
|
||||
&key,
|
||||
&["value", "tools"],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let value = value
|
||||
.ok_or_else(|| de::Error::missing_field("value"))?;
|
||||
let tools = if let Some(Val::Bool(tools)) = tools {
|
||||
tools
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(Val::Map {
|
||||
value: Box::new(value),
|
||||
tools,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ValVisitor)
|
||||
|
@ -908,12 +965,27 @@ impl<'de> de::Deserialize<'de> for EnvList {
|
|||
let value = map.next_value::<Val>()?;
|
||||
match value {
|
||||
Val::Int(i) => {
|
||||
env.push(EnvDirective::Val(key, i.to_string()));
|
||||
env.push(EnvDirective::Val(
|
||||
key,
|
||||
i.to_string(),
|
||||
Default::default(),
|
||||
));
|
||||
}
|
||||
Val::Str(s) => {
|
||||
env.push(EnvDirective::Val(key, s));
|
||||
env.push(EnvDirective::Val(key, s, Default::default()));
|
||||
}
|
||||
Val::Bool(true) => env.push(EnvDirective::Val(
|
||||
key,
|
||||
"true".into(),
|
||||
Default::default(),
|
||||
)),
|
||||
Val::Bool(false) => {
|
||||
env.push(EnvDirective::Rm(key, Default::default()))
|
||||
}
|
||||
Val::Map { value, tools } => {
|
||||
let opts = EnvDirectiveOptions { tools };
|
||||
env.push(EnvDirective::Val(key, value.to_string(), opts));
|
||||
}
|
||||
Val::Bool(_b) => env.push(EnvDirective::Rm(key)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1028,6 +1100,71 @@ impl<'de> de::Deserialize<'de> for MiseTomlToolList {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for MiseTomlEnvDirective {
|
||||
type Err = eyre::Report;
|
||||
|
||||
fn from_str(s: &str) -> eyre::Result<Self> {
|
||||
Ok(MiseTomlEnvDirective {
|
||||
value: s.into(),
|
||||
options: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for MiseTomlEnvDirective {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct MiseTomlEnvDirectiveVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for MiseTomlEnvDirectiveVisitor {
|
||||
type Value = MiseTomlEnvDirective;
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("env directive")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(MiseTomlEnvDirective {
|
||||
value: v.into(),
|
||||
options: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
|
||||
where
|
||||
M: de::MapAccess<'de>,
|
||||
{
|
||||
let mut options: EnvDirectiveOptions = Default::default();
|
||||
let mut value = None;
|
||||
while let Some((k, v)) = map.next_entry::<String, toml::Value>()? {
|
||||
match k.as_str() {
|
||||
"value" => {
|
||||
value = Some(v.as_str().unwrap().to_string());
|
||||
}
|
||||
"tools" => {
|
||||
options.tools = v.as_bool().unwrap();
|
||||
}
|
||||
_ => {
|
||||
return Err(de::Error::custom("invalid key"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(value) = value {
|
||||
Ok(MiseTomlEnvDirective { value, options })
|
||||
} else {
|
||||
Err(de::Error::custom("missing value"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(MiseTomlEnvDirectiveVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for MiseTomlTool {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
|
|
|
@ -9,14 +9,23 @@ MiseToml(~/cwd/.test.mise.toml): ToolRequestSet: <empty> {
|
|||
Val(
|
||||
"foo",
|
||||
"bar",
|
||||
EnvDirectiveOptions {
|
||||
tools: false,
|
||||
},
|
||||
),
|
||||
Val(
|
||||
"foo2",
|
||||
"qux\\nquux",
|
||||
EnvDirectiveOptions {
|
||||
tools: false,
|
||||
},
|
||||
),
|
||||
Val(
|
||||
"foo3",
|
||||
"qux\nquux",
|
||||
EnvDirectiveOptions {
|
||||
tools: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ MiseToml(~/fixtures/.mise.toml): ToolRequestSet: terraform@1.0.0 node@18 node@pr
|
|||
Val(
|
||||
"NODE_ENV",
|
||||
"production",
|
||||
EnvDirectiveOptions {
|
||||
tools: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
alias: {
|
||||
|
|
|
@ -7,5 +7,8 @@ snapshot_kind: text
|
|||
Val(
|
||||
"NODE_ENV",
|
||||
"production",
|
||||
EnvDirectiveOptions {
|
||||
tools: false,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
|
||||
use either::Either;
|
||||
use serde::de;
|
||||
use serde::{de, Deserialize};
|
||||
|
||||
use crate::task::{EitherIntOrBool, EitherStringOrIntOrBool};
|
||||
|
||||
|
@ -88,14 +88,14 @@ impl<'a> TomlParser<'a> {
|
|||
pub fn deserialize_arr<'de, D, T>(deserializer: D) -> eyre::Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
T: FromStr,
|
||||
T: FromStr + Deserialize<'de>,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
struct ArrVisitor<T>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<'de, T> de::Visitor<'de> for ArrVisitor<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T: FromStr + Deserialize<'de>,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
type Value = Vec<T>;
|
||||
|
@ -121,49 +121,16 @@ where
|
|||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, map: M) -> std::result::Result<Self::Value, M::Error>
|
||||
where
|
||||
M: de::MapAccess<'de>,
|
||||
{
|
||||
Ok(vec![Deserialize::deserialize(
|
||||
de::value::MapAccessDeserializer::new(map),
|
||||
)?])
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ArrVisitor(std::marker::PhantomData))
|
||||
}
|
||||
|
||||
pub fn deserialize_path_entry_arr<'de, D, T>(deserializer: D) -> eyre::Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
T: FromStr + Debug + serde::Deserialize<'de>,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
struct PathEntryArrVisitor<T>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<'de, T> de::Visitor<'de> for PathEntryArrVisitor<T>
|
||||
where
|
||||
T: FromStr + Debug + serde::Deserialize<'de>,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
type Value = Vec<T>;
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("path entry or array of path entries")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let v = v.parse().map_err(de::Error::custom)?;
|
||||
Ok(vec![v])
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, mut seq: S) -> std::result::Result<Self::Value, S::Error>
|
||||
where
|
||||
S: de::SeqAccess<'de>,
|
||||
{
|
||||
let mut v = vec![];
|
||||
while let Some(entry) = seq.next_element::<T>()? {
|
||||
trace!("visit_seq: entry: {:?}", entry);
|
||||
v.push(entry);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(PathEntryArrVisitor(std::marker::PhantomData))
|
||||
}
|
||||
|
|
|
@ -18,19 +18,21 @@ struct Env<V> {
|
|||
}
|
||||
|
||||
impl EnvResults {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn file(
|
||||
ctx: &mut tera::Context,
|
||||
tera: &mut tera::Tera,
|
||||
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
|
||||
r: &mut EnvResults,
|
||||
normalize_path: fn(&Path, PathBuf) -> PathBuf,
|
||||
source: &Path,
|
||||
config_root: &Path,
|
||||
input: PathBuf,
|
||||
input: String,
|
||||
) -> Result<()> {
|
||||
let s = r.parse_template(ctx, source, input.to_string_lossy().as_ref())?;
|
||||
let s = r.parse_template(ctx, tera, source, &input)?;
|
||||
for p in xx::file::glob(normalize_path(config_root, s.into())).unwrap_or_default() {
|
||||
r.env_files.push(p.clone());
|
||||
let parse_template = |s: String| r.parse_template(ctx, source, &s);
|
||||
let parse_template = |s: String| r.parse_template(ctx, tera, source, &s);
|
||||
let ext = p
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy().to_string())
|
||||
|
@ -51,7 +53,7 @@ impl EnvResults {
|
|||
|
||||
fn json<PT>(p: &Path, parse_template: PT) -> Result<EnvMap>
|
||||
where
|
||||
PT: Fn(String) -> Result<String>,
|
||||
PT: FnMut(String) -> Result<String>,
|
||||
{
|
||||
let errfn = || eyre!("failed to parse json file: {}", display_path(p));
|
||||
if let Ok(raw) = file::read_to_string(p) {
|
||||
|
@ -81,7 +83,7 @@ impl EnvResults {
|
|||
|
||||
fn yaml<PT>(p: &Path, parse_template: PT) -> Result<EnvMap>
|
||||
where
|
||||
PT: Fn(String) -> Result<String>,
|
||||
PT: FnMut(String) -> Result<String>,
|
||||
{
|
||||
let errfn = || eyre!("failed to parse yaml file: {}", display_path(p));
|
||||
if let Ok(raw) = file::read_to_string(p) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::env;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::env;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::cmd::cmd;
|
||||
use crate::config::config_file::{config_root, trust_check};
|
||||
use crate::dirs;
|
||||
use crate::env_diff::EnvMap;
|
||||
|
@ -11,7 +11,6 @@ use crate::file::display_path;
|
|||
use crate::tera::get_tera;
|
||||
use eyre::{eyre, Context};
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
mod file;
|
||||
mod module;
|
||||
|
@ -19,84 +18,51 @@ mod path;
|
|||
mod source;
|
||||
mod venv;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PathEntry {
|
||||
Normal(PathBuf),
|
||||
Lazy(PathBuf),
|
||||
}
|
||||
|
||||
impl From<&str> for PathEntry {
|
||||
fn from(s: &str) -> Self {
|
||||
let pb = PathBuf::from(s);
|
||||
Self::Normal(pb)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PathEntry {
|
||||
type Err = eyre::Error;
|
||||
|
||||
fn from_str(s: &str) -> eyre::Result<Self> {
|
||||
let pb = PathBuf::from_str(s)?;
|
||||
Ok(Self::Normal(pb))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for PathEntry {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Path {
|
||||
match self {
|
||||
PathEntry::Normal(pb) => pb.as_ref(),
|
||||
PathEntry::Lazy(pb) => pb.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PathEntry {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MapPathEntry {
|
||||
value: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Helper {
|
||||
Normal(PathBuf),
|
||||
Lazy(MapPathEntry),
|
||||
}
|
||||
|
||||
Ok(match Helper::deserialize(deserializer)? {
|
||||
Helper::Normal(value) => Self::Normal(value),
|
||||
Helper::Lazy(this) => Self::Lazy(this.value),
|
||||
})
|
||||
}
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EnvDirectiveOptions {
|
||||
pub(crate) tools: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EnvDirective {
|
||||
/// simple key/value pair
|
||||
Val(String, String),
|
||||
Val(String, String, EnvDirectiveOptions),
|
||||
/// remove a key
|
||||
Rm(String),
|
||||
Rm(String, EnvDirectiveOptions),
|
||||
/// dotenv file
|
||||
File(PathBuf),
|
||||
File(String, EnvDirectiveOptions),
|
||||
/// add a path to the PATH
|
||||
Path(PathEntry),
|
||||
Path(String, EnvDirectiveOptions),
|
||||
/// run a bash script and apply the resulting env diff
|
||||
Source(PathBuf),
|
||||
Source(String, EnvDirectiveOptions),
|
||||
PythonVenv {
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
create: bool,
|
||||
python: Option<String>,
|
||||
uv_create_args: Option<Vec<String>>,
|
||||
python_create_args: Option<Vec<String>>,
|
||||
options: EnvDirectiveOptions,
|
||||
},
|
||||
Module(String, toml::Value),
|
||||
Module(String, toml::Value, EnvDirectiveOptions),
|
||||
}
|
||||
|
||||
impl EnvDirective {
|
||||
pub fn options(&self) -> &EnvDirectiveOptions {
|
||||
match self {
|
||||
EnvDirective::Val(_, _, opts)
|
||||
| EnvDirective::Rm(_, opts)
|
||||
| EnvDirective::File(_, opts)
|
||||
| EnvDirective::Path(_, opts)
|
||||
| EnvDirective::Source(_, opts)
|
||||
| EnvDirective::PythonVenv { options: opts, .. }
|
||||
| EnvDirective::Module(_, _, opts) => opts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(String, String)> for EnvDirective {
|
||||
fn from((k, v): (String, String)) -> Self {
|
||||
Self::Val(k, v)
|
||||
Self::Val(k, v, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,18 +75,19 @@ impl From<(String, i64)> for EnvDirective {
|
|||
impl Display for EnvDirective {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EnvDirective::Val(k, v) => write!(f, "{k}={v}"),
|
||||
EnvDirective::Rm(k) => write!(f, "unset {k}"),
|
||||
EnvDirective::File(path) => write!(f, "dotenv {}", display_path(path)),
|
||||
EnvDirective::Path(path) => write!(f, "path_add {}", display_path(path)),
|
||||
EnvDirective::Source(path) => write!(f, "source {}", display_path(path)),
|
||||
EnvDirective::Module(name, _) => write!(f, "module {}", name),
|
||||
EnvDirective::Val(k, v, _) => write!(f, "{k}={v}"),
|
||||
EnvDirective::Rm(k, _) => write!(f, "unset {k}"),
|
||||
EnvDirective::File(path, _) => write!(f, "dotenv {}", display_path(path)),
|
||||
EnvDirective::Path(path, _) => write!(f, "path_add {}", display_path(path)),
|
||||
EnvDirective::Source(path, _) => write!(f, "source {}", display_path(path)),
|
||||
EnvDirective::Module(name, _, _) => write!(f, "module {}", name),
|
||||
EnvDirective::PythonVenv {
|
||||
path,
|
||||
create,
|
||||
python,
|
||||
uv_create_args,
|
||||
python_create_args,
|
||||
..
|
||||
} => {
|
||||
write!(f, "python venv path={}", display_path(path))?;
|
||||
if *create {
|
||||
|
@ -155,6 +122,7 @@ impl EnvResults {
|
|||
mut ctx: tera::Context,
|
||||
initial: &EnvMap,
|
||||
input: Vec<(EnvDirective, PathBuf)>,
|
||||
tools: bool,
|
||||
) -> eyre::Result<Self> {
|
||||
// trace!("resolve: input: {:#?}", &input);
|
||||
let mut env = initial
|
||||
|
@ -176,8 +144,33 @@ impl EnvResults {
|
|||
_ => p.to_path_buf(),
|
||||
}
|
||||
};
|
||||
let mut paths: Vec<(PathEntry, PathBuf)> = Vec::new();
|
||||
let mut paths: Vec<(PathBuf, PathBuf)> = Vec::new();
|
||||
for (directive, source) in input.clone() {
|
||||
if directive.options().tools != tools {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
});
|
||||
// trace!(
|
||||
// "resolve: directive: {:?}, source: {:?}",
|
||||
// &directive,
|
||||
|
@ -193,22 +186,23 @@ impl EnvResults {
|
|||
ctx.insert("env", &env_vars);
|
||||
// trace!("resolve: ctx.get('env'): {:#?}", &ctx.get("env"));
|
||||
match directive {
|
||||
EnvDirective::Val(k, v) => {
|
||||
let v = r.parse_template(&ctx, &source, &v)?;
|
||||
EnvDirective::Val(k, v, _opts) => {
|
||||
let v = r.parse_template(&ctx, &mut tera, &source, &v)?;
|
||||
r.env_remove.remove(&k);
|
||||
// trace!("resolve: inserting {:?}={:?} from {:?}", &k, &v, &source);
|
||||
env.insert(k, (v, Some(source.clone())));
|
||||
}
|
||||
EnvDirective::Rm(k) => {
|
||||
EnvDirective::Rm(k, _opts) => {
|
||||
env.shift_remove(&k);
|
||||
r.env_remove.insert(k);
|
||||
}
|
||||
EnvDirective::Path(input_str) => {
|
||||
Self::path(&mut ctx, &mut r, &mut paths, source, input_str)?;
|
||||
EnvDirective::Path(input_str, _opts) => {
|
||||
Self::path(&mut ctx, &mut tera, &mut r, &mut paths, source, input_str)?;
|
||||
}
|
||||
EnvDirective::File(input) => {
|
||||
EnvDirective::File(input, _opts) => {
|
||||
Self::file(
|
||||
&mut ctx,
|
||||
&mut tera,
|
||||
&mut env,
|
||||
&mut r,
|
||||
normalize_path,
|
||||
|
@ -217,9 +211,10 @@ impl EnvResults {
|
|||
input,
|
||||
)?;
|
||||
}
|
||||
EnvDirective::Source(input) => {
|
||||
EnvDirective::Source(input, _opts) => {
|
||||
Self::source(
|
||||
&mut ctx,
|
||||
&mut tera,
|
||||
&mut env,
|
||||
&mut r,
|
||||
normalize_path,
|
||||
|
@ -235,9 +230,11 @@ impl EnvResults {
|
|||
python,
|
||||
uv_create_args,
|
||||
python_create_args,
|
||||
options: _opts,
|
||||
} => {
|
||||
Self::venv(
|
||||
&mut ctx,
|
||||
&mut tera,
|
||||
&mut env,
|
||||
&mut r,
|
||||
normalize_path,
|
||||
|
@ -251,7 +248,7 @@ impl EnvResults {
|
|||
python_create_args,
|
||||
)?;
|
||||
}
|
||||
EnvDirective::Module(name, value) => {
|
||||
EnvDirective::Module(name, value, _opts) => {
|
||||
Self::module(&mut r, source, name, &value)?;
|
||||
}
|
||||
};
|
||||
|
@ -268,21 +265,14 @@ impl EnvResults {
|
|||
}
|
||||
// trace!("resolve: paths: {:#?}", &paths);
|
||||
// trace!("resolve: ctx.env: {:#?}", &ctx.get("env"));
|
||||
for (entry, source) in paths {
|
||||
for (p, source) in paths {
|
||||
// trace!("resolve: entry: {:?}, source: {}", &entry, display_path(source));
|
||||
let config_root = source
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.or_else(|| dirs::CWD.clone())
|
||||
.unwrap_or_default();
|
||||
let s = match entry {
|
||||
PathEntry::Normal(pb) => pb.to_string_lossy().to_string(),
|
||||
PathEntry::Lazy(pb) => {
|
||||
// trace!("resolve: s: {:?}", &s);
|
||||
r.parse_template(&ctx, &source, pb.to_string_lossy().as_ref())?
|
||||
}
|
||||
};
|
||||
env::split_paths(&s)
|
||||
env::split_paths(&p)
|
||||
.map(|s| normalize_path(&config_root, s))
|
||||
.for_each(|p| r.env_paths.push(p.clone()));
|
||||
}
|
||||
|
@ -292,6 +282,7 @@ impl EnvResults {
|
|||
fn parse_template(
|
||||
&self,
|
||||
ctx: &tera::Context,
|
||||
tera: &mut tera::Tera,
|
||||
path: &Path,
|
||||
input: &str,
|
||||
) -> eyre::Result<String> {
|
||||
|
@ -299,8 +290,7 @@ impl EnvResults {
|
|||
return Ok(input.to_string());
|
||||
}
|
||||
trust_check(path)?;
|
||||
let dir = path.parent();
|
||||
let output = get_tera(dir)
|
||||
let output = tera
|
||||
.render_str(input, ctx)
|
||||
.wrap_err_with(|| eyre!("failed to parse template: '{input}'"))?;
|
||||
Ok(output)
|
||||
|
|
|
@ -1,36 +1,25 @@
|
|||
use crate::config::env_directive::{EnvResults, PathEntry};
|
||||
use crate::config::env_directive::EnvResults;
|
||||
use crate::result;
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl EnvResults {
|
||||
pub fn path(
|
||||
ctx: &mut tera::Context,
|
||||
tera: &mut tera::Tera,
|
||||
r: &mut EnvResults,
|
||||
paths: &mut Vec<(PathEntry, PathBuf)>,
|
||||
paths: &mut Vec<(PathBuf, PathBuf)>,
|
||||
source: PathBuf,
|
||||
input_str: PathEntry,
|
||||
input: String,
|
||||
) -> result::Result<()> {
|
||||
// trace!("resolve: input_str: {:#?}", input_str);
|
||||
match input_str {
|
||||
PathEntry::Normal(input) => {
|
||||
// trace!(
|
||||
// "resolve: normal: input: {:?}, input.to_string(): {:?}",
|
||||
// &input,
|
||||
// input.to_string_lossy().as_ref()
|
||||
// );
|
||||
let s = r.parse_template(ctx, &source, input.to_string_lossy().as_ref())?;
|
||||
// trace!("resolve: s: {:?}", &s);
|
||||
paths.push((PathEntry::Normal(s.into()), source));
|
||||
}
|
||||
PathEntry::Lazy(input) => {
|
||||
// trace!(
|
||||
// "resolve: lazy: input: {:?}, input.to_string(): {:?}",
|
||||
// &input,
|
||||
// input.to_string_lossy().as_ref()
|
||||
// );
|
||||
paths.push((PathEntry::Lazy(input), source));
|
||||
}
|
||||
}
|
||||
// trace!(
|
||||
// "resolve: normal: input: {:?}, input.to_string(): {:?}",
|
||||
// &input,
|
||||
// input.to_string_lossy().as_ref()
|
||||
// );
|
||||
let s = r.parse_template(ctx, tera, &source, &input)?;
|
||||
// trace!("resolve: s: {:?}", &s);
|
||||
paths.push((s.into(), source));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -56,22 +45,26 @@ mod tests {
|
|||
&env,
|
||||
vec![
|
||||
(
|
||||
EnvDirective::Path("/path/1".into()),
|
||||
EnvDirective::Path("/path/1".into(), Default::default()),
|
||||
PathBuf::from("/config"),
|
||||
),
|
||||
(
|
||||
EnvDirective::Path("/path/2".into()),
|
||||
EnvDirective::Path("/path/2".into(), Default::default()),
|
||||
PathBuf::from("/config"),
|
||||
),
|
||||
(
|
||||
EnvDirective::Path("~/foo/{{ env.A }}".into()),
|
||||
EnvDirective::Path("~/foo/{{ env.A }}".into(), Default::default()),
|
||||
Default::default(),
|
||||
),
|
||||
(
|
||||
EnvDirective::Path("./rel/{{ env.A }}:./rel2/{{env.B}}".into()),
|
||||
EnvDirective::Path(
|
||||
"./rel/{{ env.A }}:./rel2/{{env.B}}".into(),
|
||||
Default::default(),
|
||||
),
|
||||
Default::default(),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_debug_snapshot!(
|
||||
|
|
|
@ -7,15 +7,16 @@ impl EnvResults {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn source(
|
||||
ctx: &mut tera::Context,
|
||||
tera: &mut tera::Tera,
|
||||
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
|
||||
r: &mut EnvResults,
|
||||
normalize_path: fn(&Path, PathBuf) -> PathBuf,
|
||||
source: &Path,
|
||||
config_root: &Path,
|
||||
env_vars: &EnvMap,
|
||||
input: PathBuf,
|
||||
input: String,
|
||||
) {
|
||||
if let Ok(s) = r.parse_template(ctx, source, input.to_string_lossy().as_ref()) {
|
||||
if let Ok(s) = r.parse_template(ctx, tera, source, &input) {
|
||||
for p in xx::file::glob(normalize_path(config_root, s.into())).unwrap_or_default() {
|
||||
r.env_scripts.push(p.clone());
|
||||
let env_diff = EnvDiff::from_bash_script(&p, config_root, env_vars.clone())
|
||||
|
|
|
@ -16,13 +16,14 @@ impl EnvResults {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn venv(
|
||||
ctx: &mut tera::Context,
|
||||
tera: &mut tera::Tera,
|
||||
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
|
||||
r: &mut EnvResults,
|
||||
normalize_path: fn(&Path, PathBuf) -> PathBuf,
|
||||
source: &Path,
|
||||
config_root: &Path,
|
||||
env_vars: EnvMap,
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
create: bool,
|
||||
python: Option<String>,
|
||||
uv_create_args: Option<Vec<String>>,
|
||||
|
@ -30,7 +31,7 @@ impl EnvResults {
|
|||
) -> Result<()> {
|
||||
trace!("python venv: {} create={create}", display_path(&path));
|
||||
trust_check(source)?;
|
||||
let venv = r.parse_template(ctx, source, path.to_string_lossy().as_ref())?;
|
||||
let venv = r.parse_template(ctx, tera, source, &path)?;
|
||||
let venv = normalize_path(config_root, venv.into());
|
||||
if !venv.exists() && create {
|
||||
// TODO: the toolset stuff doesn't feel like it's in the right place here
|
||||
|
@ -162,25 +163,28 @@ mod tests {
|
|||
vec![
|
||||
(
|
||||
EnvDirective::PythonVenv {
|
||||
path: PathBuf::from("/"),
|
||||
path: "/".into(),
|
||||
create: false,
|
||||
python: None,
|
||||
uv_create_args: None,
|
||||
python_create_args: None,
|
||||
options: Default::default(),
|
||||
},
|
||||
Default::default(),
|
||||
),
|
||||
(
|
||||
EnvDirective::PythonVenv {
|
||||
path: PathBuf::from("./"),
|
||||
path: "./".into(),
|
||||
create: false,
|
||||
python: None,
|
||||
uv_create_args: None,
|
||||
python_create_args: None,
|
||||
options: Default::default(),
|
||||
},
|
||||
Default::default(),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
// expect order to be reversed as it processes directives from global to dir specific
|
||||
|
|
|
@ -582,7 +582,8 @@ impl Config {
|
|||
.flatten()
|
||||
.collect();
|
||||
// trace!("load_env: entries: {:#?}", entries);
|
||||
let env_results = EnvResults::resolve(self.tera_ctx.clone(), &env::PRISTINE_ENV, entries)?;
|
||||
let env_results =
|
||||
EnvResults::resolve(self.tera_ctx.clone(), &env::PRISTINE_ENV, entries, false)?;
|
||||
time!("load_env done");
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
trace!("{env_results:#?}");
|
||||
|
@ -1080,7 +1081,7 @@ fn load_vars(ctx: tera::Context, config_files: &ConfigMap) -> Result<EnvResults>
|
|||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
let vars_results = EnvResults::resolve(ctx, &env::PRISTINE_ENV, entries)?;
|
||||
let vars_results = EnvResults::resolve(ctx, &env::PRISTINE_ENV, entries, false)?;
|
||||
time!("load_vars done");
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
trace!("{vars_results:#?}");
|
||||
|
|
|
@ -9,9 +9,9 @@ use rops::file::RopsFile;
|
|||
use std::env;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
pub fn decrypt<PT, F>(input: &str, parse_template: PT, format: &str) -> result::Result<String>
|
||||
pub fn decrypt<PT, F>(input: &str, mut parse_template: PT, format: &str) -> result::Result<String>
|
||||
where
|
||||
PT: Fn(String) -> result::Result<String>,
|
||||
PT: FnMut(String) -> result::Result<String>,
|
||||
F: rops::file::format::FileFormat,
|
||||
{
|
||||
static AGE_KEY: OnceLock<Option<String>> = OnceLock::new();
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::{panic, thread};
|
|||
|
||||
use crate::backend::Backend;
|
||||
use crate::cli::args::BackendArg;
|
||||
use crate::config::env_directive::EnvResults;
|
||||
use crate::config::settings::{SettingsStatusMissingTools, SETTINGS};
|
||||
use crate::config::Config;
|
||||
use crate::env::{PATH_KEY, TERM_WIDTH};
|
||||
|
@ -454,6 +455,14 @@ impl Toolset {
|
|||
path_env.add(p);
|
||||
}
|
||||
env.insert(PATH_KEY.to_string(), path_env.to_string());
|
||||
let mut ctx = config.tera_ctx.clone();
|
||||
ctx.insert("env", &env);
|
||||
env.extend(
|
||||
self.load_post_env(ctx, &env)?
|
||||
.env
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.0)),
|
||||
);
|
||||
Ok(env)
|
||||
}
|
||||
pub fn env_from_tools(&self, config: &Config) -> Vec<(String, String, String)> {
|
||||
|
@ -533,6 +542,15 @@ impl Toolset {
|
|||
for p in self.list_paths() {
|
||||
paths.insert(p);
|
||||
}
|
||||
let config = Config::get();
|
||||
let mut env = self.env(&config)?;
|
||||
env.insert(
|
||||
PATH_KEY.to_string(),
|
||||
env::join_paths(paths.iter())?.to_string_lossy().to_string(),
|
||||
);
|
||||
let mut ctx = config.tera_ctx.clone();
|
||||
ctx.insert("env", &env);
|
||||
paths.extend(self.load_post_env(ctx, &env)?.env_paths);
|
||||
Ok(paths.into_iter().collect())
|
||||
}
|
||||
pub fn which(&self, bin_name: &str) -> Option<(Arc<dyn Backend>, ToolVersion)> {
|
||||
|
@ -633,6 +651,30 @@ impl Toolset {
|
|||
fn is_disabled(&self, ba: &BackendArg) -> bool {
|
||||
!ba.is_os_supported() || SETTINGS.disable_tools().contains(&ba.short)
|
||||
}
|
||||
|
||||
fn load_post_env(&self, ctx: tera::Context, env: &EnvMap) -> Result<EnvResults> {
|
||||
let config = Config::get();
|
||||
let entries = config
|
||||
.config_files
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(source, cf)| {
|
||||
cf.env_entries()
|
||||
.map(|ee| ee.into_iter().map(|e| (e, source.clone())))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
// trace!("load_env: entries: {:#?}", entries);
|
||||
let env_results = EnvResults::resolve(ctx, env, entries, true)?;
|
||||
if log::log_enabled!(log::Level::Trace) {
|
||||
trace!("{env_results:#?}");
|
||||
} else {
|
||||
debug!("{env_results:?}");
|
||||
}
|
||||
Ok(env_results)
|
||||
}
|
||||
}
|
||||
|
||||
fn show_python_install_hint(versions: &[ToolRequest]) {
|
||||
|
|
Loading…
Reference in New Issue