Skip to content

Commit

Permalink
fix bash not_found handler
Browse files Browse the repository at this point in the history
Fixes #1557

Also refactored all the shell activate scripts
  • Loading branch information
jdx committed Jan 30, 2024
1 parent 6a16dc0 commit b06aef6
Show file tree
Hide file tree
Showing 25 changed files with 149 additions and 461 deletions.
47 changes: 37 additions & 10 deletions src/cli/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,28 +64,55 @@ impl Activate {
} else {
env::MISE_BIN.clone()
};
match self.shims {
true => self.activate_shims(shell.as_ref(), &mise_bin),
false => self.activate(shell.as_ref(), &mise_bin),
}

Ok(())
}

fn activate_shims(&self, shell: &dyn Shell, mise_bin: &Path) {
let exe_dir = mise_bin.parent().unwrap();
miseprint!("{}", self.prepend_path(shell, exe_dir));
miseprint!("{}", self.prepend_path(shell, &dirs::SHIMS));
}

fn activate(&self, shell: &dyn Shell, mise_bin: &Path) {
let mut flags = vec![];
if self.quiet {
flags.push(" --quiet");
}
if self.status {
flags.push(" --status");
}
let output = match self.shims {
true => self.activate_shims(shell.as_ref(), &mise_bin),
false => shell.activate(&mise_bin, flags.join("")),
};
miseprint!("{output}");

Ok(())
miseprint!("{}", self.prepend_path(shell, mise_bin));
miseprint!("{}", shell.activate(mise_bin, flags.join("")));
}

fn activate_shims(&self, shell: &dyn Shell, exe: &Path) -> String {
let exe_dir = exe.parent().unwrap().to_path_buf();
shell.prepend_path(&[exe_dir, dirs::SHIMS.clone()])
fn prepend_path(&self, shell: &dyn Shell, p: &Path) -> String {
if is_dir_not_in_nix(p) && !is_dir_in_path(p) && !p.is_relative() {
shell.prepend_env("PATH", p.to_string_lossy().as_ref())
} else {
String::new()
}
}
}

fn is_dir_in_path(dir: &Path) -> bool {
let dir = dir.canonicalize().unwrap_or(dir.to_path_buf());
env::PATH
.clone()
.into_iter()
.any(|p| p.canonicalize().unwrap_or(p) == dir)
}

fn is_dir_not_in_nix(dir: &Path) -> bool {
!dir.canonicalize()
.unwrap_or(dir.to_path_buf())
.starts_with("/nix/")
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>eval "$(mise activate bash)"</bold>
Expand Down
52 changes: 15 additions & 37 deletions src/shell/bash.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
use std::path::{Path, PathBuf};
use std::path::Path;

use crate::config::Settings;
use crate::shell::{is_dir_in_path, is_dir_not_in_nix, Shell};
use crate::shell::Shell;

#[derive(Default)]
pub struct Bash {}

impl Shell for Bash {
fn activate(&self, exe: &Path, flags: String) -> String {
let dir = exe.parent().unwrap();
let settings = Settings::get();
let exe = exe.to_string_lossy();
let mut out = String::new();
if is_dir_not_in_nix(dir) && !is_dir_in_path(dir) && !dir.is_relative() {
out.push_str(&format!("export PATH=\"{}:$PATH\"\n", dir.display()));
}
out.push_str(&formatdoc! {r#"
let mut out = formatdoc! {r#"
export MISE_SHELL=bash
export __MISE_ORIG_PATH="$PATH"
Expand Down Expand Up @@ -47,12 +43,12 @@ impl Shell for Bash {
if [[ ";${{PROMPT_COMMAND:-}};" != *";_mise_hook;"* ]]; then
PROMPT_COMMAND="_mise_hook${{PROMPT_COMMAND:+;$PROMPT_COMMAND}}"
fi
"#});
if Settings::get().not_found_auto_install {
"#};
if settings.not_found_auto_install {
out.push_str(&formatdoc! {r#"
if [ -z "${{_mise_cmd_not_found:-}}" ]; then
_mise_cmd_not_found=1
[ -n "$(declare -f command_not_found_handler)" ] && eval "${{$(declare -f command_not_found_handler)/command_not_found_handler/_command_not_found_handler}}"
[ -n "$(declare -f command_not_found_handle)" ] && eval "${{$(declare -f command_not_found_handle)/command_not_found_handle/_command_not_found_handle}}"
command_not_found_handle() {{
if {exe} hook-not-found -s bash -- "$1"; then
Expand Down Expand Up @@ -82,34 +78,24 @@ impl Shell for Bash {
"#}
}

fn prepend_path(&self, paths: &[PathBuf]) -> String {
if paths.is_empty() {
return String::new();
}
let mut path = String::new();
for p in paths {
if is_dir_not_in_nix(p) && !is_dir_in_path(p) && !p.is_relative() {
path = format!("{}:{path}", p.display());
}
}
format!("export PATH=\"{}$PATH\"\n", path)
}

fn set_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into());
let v = shell_escape::unix::escape(v.into());
let v = v.replace("\\n", "\n");
format!("export {k}={v}\n")
}

fn prepend_env(&self, k: &str, v: &str) -> String {
format!("export {k}=\"{v}:${k}\"\n")
}

fn unset_env(&self, k: &str) -> String {
format!("unset {k}\n", k = shell_escape::unix::escape(k.into()))
}
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use crate::test::replace_path;

Expand All @@ -123,22 +109,14 @@ mod tests {
}

#[test]
fn test_activate_nix() {
let bash = Bash::default();
let exe = Path::new("/nix/store/mise");
assert_snapshot!(bash.activate(exe, " --status".into()));
fn test_set_env() {
assert_snapshot!(Bash::default().set_env("FOO", "1"));
}

#[test]
fn test_prepend_path() {
fn test_prepend_env() {
let bash = Bash::default();
let paths = vec![PathBuf::from("/some/dir"), PathBuf::from("/some/other/dir")];
assert_snapshot!(replace_path(&bash.prepend_path(&paths)));
}

#[test]
fn test_set_env() {
assert_snapshot!(Bash::default().set_env("FOO", "1"));
assert_snapshot!(replace_path(&bash.prepend_env("PATH", "/some/dir:/2/dir")));
}

#[test]
Expand Down
46 changes: 13 additions & 33 deletions src/shell/fish.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
use std::path::{Path, PathBuf};
use std::path::Path;

use crate::config::Settings;
use crate::shell::{is_dir_in_path, is_dir_not_in_nix, Shell};
use crate::shell::Shell;

#[derive(Default)]
pub struct Fish {}

impl Shell for Fish {
fn activate(&self, exe: &Path, flags: String) -> String {
let dir = exe.parent().unwrap();
let exe = exe.to_string_lossy();
let description = "'Update mise environment when changing directories'";
let mut out = String::new();

if is_dir_not_in_nix(dir) && !is_dir_in_path(dir) && !dir.is_relative() {
out.push_str(&format!("fish_add_path -g {dir}\n", dir = dir.display()));
}

// much of this is from direnv
// https://github.com/direnv/direnv/blob/cb5222442cb9804b1574954999f6073cc636eff0/internal/cmd/shell_fish.go#L14-L36
out.push_str(&formatdoc! {r#"
Expand Down Expand Up @@ -101,26 +96,19 @@ impl Shell for Fish {
"#}
}

fn prepend_path(&self, paths: &[PathBuf]) -> String {
if paths.is_empty() {
return String::new();
}
let mut path = String::new();
for p in paths {
if is_dir_not_in_nix(p) && !is_dir_in_path(p) && !p.is_relative() {
path = format!("{} {path}", p.display());
}
}
format!("set -gx PATH {path}$PATH\n")
}

fn set_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into());
let v = shell_escape::unix::escape(v.into());
let v = v.replace("\\n", "\n");
format!("set -gx {k} {v}\n")
}

fn prepend_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into());
let v = shell_escape::unix::escape(v.into());
format!("set -gx {k} {v} ${k}\n")
}

fn unset_env(&self, k: &str) -> String {
format!("set -e {k}\n", k = shell_escape::unix::escape(k.into()))
}
Expand All @@ -139,22 +127,14 @@ mod tests {
}

#[test]
fn test_activate_nix() {
let fish = Fish::default();
let exe = Path::new("/nix/store/mise");
assert_snapshot!(fish.activate(exe, " --status".into()));
}

#[test]
fn test_prepend_path() {
let fish = Fish::default();
let paths = vec![PathBuf::from("/some/dir"), PathBuf::from("/some/other/dir")];
assert_snapshot!(fish.prepend_path(&paths));
fn test_set_env() {
assert_snapshot!(Fish::default().set_env("FOO", "1"));
}

#[test]
fn test_set_env() {
assert_snapshot!(Fish::default().set_env("FOO", "1"));
fn test_prepend_env() {
let sh = Fish::default();
assert_snapshot!(replace_path(&sh.prepend_env("PATH", "/some/dir:/2/dir")));
}

#[test]
Expand Down
26 changes: 2 additions & 24 deletions src/shell/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
use std::path::Path;

use crate::env;

Expand Down Expand Up @@ -54,17 +54,9 @@ impl Display for ShellType {

pub trait Shell {
fn activate(&self, exe: &Path, flags: String) -> String;
fn prepend_path(&self, paths: &[PathBuf]) -> String {
let mut path = env::var("PATH").unwrap_or_default();
for p in paths {
if is_dir_not_in_nix(p) && !is_dir_in_path(p) && !p.is_relative() {
path = format!("{}:{path}", p.display());
}
}
self.set_env("PATH", &path)
}
fn deactivate(&self) -> String;
fn set_env(&self, k: &str, v: &str) -> String;
fn prepend_env(&self, k: &str, v: &str) -> String;
fn unset_env(&self, k: &str) -> String;
}

Expand All @@ -78,17 +70,3 @@ pub fn get_shell(shell: Option<ShellType>) -> Option<Box<dyn Shell>> {
_ => None,
}
}

pub fn is_dir_in_path(dir: &Path) -> bool {
let dir = dir.canonicalize().unwrap_or(dir.to_path_buf());
env::PATH
.clone()
.into_iter()
.any(|p| p.canonicalize().unwrap_or(p) == dir)
}

pub fn is_dir_not_in_nix(dir: &Path) -> bool {
!dir.canonicalize()
.unwrap_or(dir.to_path_buf())
.starts_with("/nix/")
}
34 changes: 13 additions & 21 deletions src/shell/nushell.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fmt::Display, path::Path};

use crate::shell::{is_dir_in_path, is_dir_not_in_nix, Shell};
use crate::shell::Shell;

#[derive(Default)]
pub struct Nushell {}
Expand All @@ -22,18 +22,9 @@ impl<'a> Display for EnvOp<'a> {

impl Shell for Nushell {
fn activate(&self, exe: &Path, flags: String) -> String {
let dir = exe.parent().unwrap();
let exe = exe.display();
let mut out = String::new();

if is_dir_not_in_nix(dir) && !is_dir_in_path(dir) && !dir.is_relative() {
out.push_str(&format!(
"$env.PATH = ($env.PATH | prepend '{}')\n", // TODO: set PATH as Path on windows
dir.display()
));
}

out.push_str(&formatdoc! {r#"
formatdoc! {r#"
export-env {{
$env.MISE_SHELL = "nu"
Expand Down Expand Up @@ -82,16 +73,14 @@ impl Shell for Nushell {
}}
}}
}}
def --env mise_hook [] {{
^"{exe}" hook-env{flags} -s nu
| parse vars
| update-env
}}
"#});

out
"#}
}

fn deactivate(&self) -> String {
Expand All @@ -107,6 +96,10 @@ impl Shell for Nushell {
EnvOp::Set { key: &k, val: &v }.to_string()
}

fn prepend_env(&self, k: &str, v: &str) -> String {
format!("$env.{k} = ($env.{k} | prepend '{v}')\n")
}

fn unset_env(&self, k: &str) -> String {
let k = shell_escape::unix::escape(k.into());
EnvOp::Hide { key: k.as_ref() }.to_string()
Expand All @@ -126,15 +119,14 @@ mod tests {
}

#[test]
fn test_hook_init_nix() {
let nushell = Nushell::default();
let exe = Path::new("/nix/store/mise");
assert_snapshot!(nushell.activate(exe, " --status".into()));
fn test_set_env() {
assert_snapshot!(Nushell::default().set_env("FOO", "1"));
}

#[test]
fn test_set_env() {
assert_snapshot!(Nushell::default().set_env("FOO", "1"));
fn test_prepend_env() {
let sh = Nushell::default();
assert_snapshot!(replace_path(&sh.prepend_env("PATH", "/some/dir:/2/dir")));
}

#[test]
Expand Down
3 changes: 1 addition & 2 deletions src/shell/snapshots/mise__shell__bash__tests__activate.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
source: src/shell/bash.rs
expression: "bash.activate(exe, \" --status\".into())"
---
export PATH="/some/dir:$PATH"
export MISE_SHELL=bash
export __MISE_ORIG_PATH="$PATH"

Expand Down Expand Up @@ -37,7 +36,7 @@ if [[ ";${PROMPT_COMMAND:-};" != *";_mise_hook;"* ]]; then
fi
if [ -z "${_mise_cmd_not_found:-}" ]; then
_mise_cmd_not_found=1
[ -n "$(declare -f command_not_found_handler)" ] && eval "${$(declare -f command_not_found_handler)/command_not_found_handler/_command_not_found_handler}"
[ -n "$(declare -f command_not_found_handle)" ] && eval "${$(declare -f command_not_found_handle)/command_not_found_handle/_command_not_found_handle}"

command_not_found_handle() {
if /some/dir/mise hook-not-found -s bash -- "$1"; then
Expand Down
Loading

0 comments on commit b06aef6

Please sign in to comment.