Skip to content

Commit

Permalink
feat: allow inserting PATH in env._.source (#3685)
Browse files Browse the repository at this point in the history
* fix: trap panics in task resolving

* feat: allow inserting PATH in env._.source

Fixes #3646
  • Loading branch information
jdx authored Dec 18, 2024
1 parent 1cf5b49 commit 1072132
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 24 deletions.
2 changes: 2 additions & 0 deletions e2e/env/test_env_source
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ EOF

cat >"$MISE_CONFIG_DIR/source.sh" <<EOF
export MISE_TEST_SOURCE=1234
export PATH="$HOME/newbin:$PATH"
EOF

assert "mise env -s bash | grep MISE_TEST_SOURCE" "export MISE_TEST_SOURCE=1234"
assert_contains "mise env -s bash | grep PATH" "export PATH='$HOME/newbin:"
2 changes: 1 addition & 1 deletion src/backend/asdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ impl AsdfBackend {
}
let script = sm.get_script_path(&ExecEnv);
let dir = dirs::CWD.clone().unwrap_or_default();
let ed = EnvDiff::from_bash_script(&script, &dir, &sm.env)?;
let ed = EnvDiff::from_bash_script(&script, &dir, &sm.env, Default::default())?;
let env = ed
.to_patches()
.into_iter()
Expand Down
2 changes: 1 addition & 1 deletion src/cli/generate/task_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl TaskDocs {
if let Some(output) = &self.output {
if self.multi {
if output.is_dir() {
for (i, task) in tasks.iter().enumerate() {
for (i, task) in tasks.iter().filter(|t| !t.hide).enumerate() {
let path = output.join(format!("{}.md", i));
file::write(&path, &task.render_markdown(dir)?)?;
}
Expand Down
1 change: 1 addition & 0 deletions src/config/env_directive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ impl EnvResults {
&mut ctx,
&mut tera,
&mut env,
&mut paths,
&mut r,
normalize_path,
&source,
Expand Down
25 changes: 20 additions & 5 deletions src/config/env_directive/source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::config::env_directive::EnvResults;
use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvMap};
use crate::env;
use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvDiffOptions, EnvMap};
use indexmap::IndexMap;
use std::path::{Path, PathBuf};

Expand All @@ -9,6 +10,7 @@ impl EnvResults {
ctx: &mut tera::Context,
tera: &mut tera::Tera,
env: &mut IndexMap<String, (String, Option<PathBuf>)>,
paths: &mut Vec<(PathBuf, PathBuf)>,
r: &mut EnvResults,
normalize_path: fn(&Path, PathBuf) -> PathBuf,
source: &Path,
Expand All @@ -19,13 +21,26 @@ impl EnvResults {
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())
.unwrap_or_default();
let orig_path = env_vars.get(&*env::PATH_KEY).cloned().unwrap_or_default();
let mut env_diff_opts = EnvDiffOptions::default();
env_diff_opts.ignore_keys.shift_remove(&*env::PATH_KEY); // allow modifying PATH
let env_diff =
EnvDiff::from_bash_script(&p, config_root, env_vars.clone(), env_diff_opts)
.unwrap_or_default();
for p in env_diff.to_patches() {
match p {
EnvDiffOperation::Add(k, v) | EnvDiffOperation::Change(k, v) => {
r.env_remove.remove(&k);
env.insert(k.clone(), (v.clone(), Some(source.to_path_buf())));
if k == *env::PATH_KEY {
// TODO: perhaps deal with path removals as well
if let Some(new_path) = v.strip_suffix(&orig_path) {
for p in env::split_paths(new_path) {
paths.push((p, source.to_path_buf()));
}
}
} else {
r.env_remove.remove(&k);
env.insert(k.clone(), (v.clone(), Some(source.to_path_buf())));
}
}
EnvDiffOperation::Remove(k) => {
env.shift_remove(&k);
Expand Down
65 changes: 48 additions & 17 deletions src/env_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ use std::collections::BTreeMap;
use std::ffi::OsString;
use std::fmt::Debug;
use std::io::prelude::*;
use std::iter::once;
use std::path::{Path, PathBuf};

use base64::prelude::*;
use eyre::Result;
use flate2::write::{ZlibDecoder, ZlibEncoder};
use flate2::Compression;
use indexmap::IndexMap;
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use once_cell::sync::Lazy;
use serde_derive::{Deserialize, Serialize};

use crate::env::PATH_KEY;
Expand Down Expand Up @@ -60,7 +62,12 @@ impl EnvDiff {
diff
}

pub fn from_bash_script<T, U, V>(script: &Path, dir: &Path, env: T) -> Result<Self>
pub fn from_bash_script<T, U, V>(
script: &Path,
dir: &Path,
env: T,
opts: EnvDiffOptions,
) -> Result<Self>
where
T: IntoIterator<Item = (U, V)>,
U: Into<OsString>,
Expand Down Expand Up @@ -92,7 +99,7 @@ impl EnvDiff {
match line.strip_prefix("declare -x ") {
Some(line) => {
let (k, v) = line.split_once('=').unwrap_or_default();
if valid_key(k) {
if invalid_key(k, &opts) {
continue;
}
cur_key = Some(k.to_string());
Expand Down Expand Up @@ -161,20 +168,9 @@ impl EnvDiff {
}
}

fn valid_key(k: &str) -> bool {
fn invalid_key(k: &str, opts: &EnvDiffOptions) -> bool {
k.is_empty()
|| k == "_"
|| k == "SHLVL"
|| k == *PATH_KEY
|| k == "PWD"
|| k == "OLDPWD"
|| k == "HOME"
|| k == "USER"
|| k == "SHELL"
|| k == "SHELLOPTS"
|| k == "COMP_WORDBREAKS"
|| k == "PS1"
|| k == "PROMPT_DIRTRIM"
|| opts.ignore_keys.contains(k)
// following two ignores are for exported bash functions and exported bash
// functions which are multiline, they appear in the environment as e.g.:
// BASH_FUNC_exported-bash-function%%=() { echo "this is an"
Expand All @@ -185,6 +181,25 @@ fn valid_key(k: &str) -> bool {
|| k.starts_with(' ')
}

static DEFAULT_IGNORE_KEYS: Lazy<IndexSet<String>> = Lazy::new(|| {
[
"_",
"SHLVL",
"PWD",
"OLDPWD",
"HOME",
"USER",
"SHELL",
"SHELLOPTS",
"COMP_WORDBREAKS",
"PS1",
"PROMPT_DIRTRIM",
]
.iter()
.map(|s| s.to_string())
.collect()
});

impl Debug for EnvDiff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let print_sorted = |hashmap: &IndexMap<String, String>| {
Expand Down Expand Up @@ -248,6 +263,22 @@ fn normalize_escape_sequences(input: &str) -> String {
result
}

pub struct EnvDiffOptions {
pub ignore_keys: IndexSet<String>,
}

impl Default for EnvDiffOptions {
fn default() -> Self {
Self {
ignore_keys: DEFAULT_IGNORE_KEYS
.iter()
.cloned()
.chain(once(PATH_KEY.to_string()))
.collect(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -356,7 +387,7 @@ mod tests {
.map(|(k, v)| (k.into(), v.into()))
.collect::<Vec<(String, String)>>();
let cwd = dirs::CWD.clone().unwrap();
let ed = EnvDiff::from_bash_script(path.as_path(), &cwd, orig).unwrap();
let ed = EnvDiff::from_bash_script(path.as_path(), &cwd, orig, Default::default()).unwrap();
assert_debug_snapshot!(ed);
}

Expand Down

0 comments on commit 1072132

Please sign in to comment.