diff --git a/libmamba/include/mamba/core/activation.hpp b/libmamba/include/mamba/core/activation.hpp index 08164fe63e..ea175b73b0 100644 --- a/libmamba/include/mamba/core/activation.hpp +++ b/libmamba/include/mamba/core/activation.hpp @@ -230,6 +230,26 @@ namespace mamba fs::u8path hook_source_path() override; }; + class NuActivator : public Activator + { + public: + + explicit NuActivator(const Context& context) + : Activator(context) + { + } + virtual ~NuActivator() = default; + + std::string script(const EnvironmentTransform& env_transform) override; + std::pair + update_prompt(const std::string& conda_prompt_modifier) override; + std::string shell_extension() override; + std::string shell() override; + + std::string hook_preamble() override; + std::string hook_postamble() override; + fs::u8path hook_source_path() override; + }; std::vector get_path_dirs(const fs::u8path& prefix); diff --git a/libmamba/src/api/shell.cpp b/libmamba/src/api/shell.cpp index acdf11798d..95a5a14db3 100644 --- a/libmamba/src/api/shell.cpp +++ b/libmamba/src/api/shell.cpp @@ -50,6 +50,10 @@ namespace mamba { return std::make_unique(context); } + if (name == "nu") + { + return std::make_unique(context); + } throw std::invalid_argument(fmt::format("Shell type not handled: {}", name)); } } diff --git a/libmamba/src/core/activation.cpp b/libmamba/src/core/activation.cpp index 302f1b8c43..d0ba112918 100644 --- a/libmamba/src/core/activation.cpp +++ b/libmamba/src/core/activation.cpp @@ -1216,4 +1216,73 @@ namespace mamba return out.str(); } + + std::string NuActivator::shell_extension() + { + return ".nu"; + } + + std::string NuActivator::shell() + { + return "nu"; + } + + std::string NuActivator::hook_preamble() + { + return ""; + } + + std::string NuActivator::hook_postamble() + { + return ""; + } + + fs::u8path NuActivator::hook_source_path() + { + return ""; + } + + std::pair + NuActivator::update_prompt(const std::string& conda_prompt_modifier) + { + // hook is implemented in shell_init.cpp as nushell behaves like a compiled language; + // one cannot dynamically evaluate strings + return { "", "" }; + } + + std::string NuActivator::script(const EnvironmentTransform& env_transform) + { + std::stringstream out; + + if (!env_transform.export_path.empty()) + { + out << "PATH = " << env_transform.export_path << ";"; + } + + for (const fs::u8path& ds : env_transform.deactivate_scripts) + { + out << "source " << ds << ";"; + } + + for (const std::string& uvar : env_transform.unset_vars) + { + out << "hide-env " << uvar << ";"; + } + + for (const auto& [skey, svar] : env_transform.set_vars) + { + out << "let " << skey << " = " << svar << ";"; + } + + for (const auto& [ekey, evar] : env_transform.export_vars) + { + out << ekey << " = " << evar << ";"; + } + + for (const fs::u8path& p : env_transform.activate_scripts) + { + out << "source " << p << ";"; + } + return out.str(); + } } // namespace mamba diff --git a/libmamba/src/core/shell_init.cpp b/libmamba/src/core/shell_init.cpp index 252a21f72a..4c6e08d0af 100644 --- a/libmamba/src/core/shell_init.cpp +++ b/libmamba/src/core/shell_init.cpp @@ -68,6 +68,10 @@ namespace mamba { return "dash"; } + if (util::contains(parent_process_name, "nu")) + { + return "nu"; + } // xonsh in unix, Python in macOS if (util::contains(parent_process_name_lower, "python")) @@ -382,6 +386,59 @@ namespace mamba return content.str(); } + std::string + nu_content(const fs::u8path& env_prefix, const std::string& /*shell*/, const fs::u8path& mamba_exe) + { + std::stringstream content; + std::string s_mamba_exe; + if (util::on_win) + { + s_mamba_exe = native_path_to_unix(mamba_exe.string()); + } + else + { + s_mamba_exe = mamba_exe.string(); + } + + content << "\n# >>> mamba initialize >>>\n"; + content << "# !! Contents within this block are managed by 'mamba init' !!\n"; + content << "$env.MAMBA_EXE = " << mamba_exe << "\n"; + content << "$env.MAMBA_ROOT_PREFIX = " << env_prefix << "\n"; + content << "$env.PATH = ($env.PATH | prepend ([$env.MAMBA_ROOT_PREFIX bin] | path join) | uniq)\n"; + content << R"###(def-env "micromamba activate" [name: string] { + #add condabin when root + if $env.MAMBA_SHLVL? == null { + $env.MAMBA_SHLVL = 0 + $env.PATH = ($env.PATH | prepend $"($env.MAMBA_ROOT_PREFIX)/condabin") + } + #ask mamba how to setup the environment and set the environment + (^($env.MAMBA_EXE) shell activate --shell nu $name + | split row ";" + | parse "{key} = {value}" + | transpose --header-row + | into record + | load-env + ) + $env.PROMPT_COMMAND = {$env.CONDA_PROMPT_MODIFIER + (create_left_prompt)} +} +def-env "micromamba deactivate" [] { + #remove active environment except micromamba root + if $env.CONDA_PROMPT_MODIFIER? != null { + (^($env.MAMBA_EXE) shell deactivate --shell nu + | split row ";" + | str trim + | parse --regex '(.*?)(?:\s*=\s*|\s+)(.*?)' + | transpose --header-row + | into record + | load-env + ) + $env.PROMPT_COMMAND = (create_left_prompt) + }})###" + << "\n"; + content << "# <<< mamba initialize <<<\n"; + return content.str(); + } + std::string csh_content(const fs::u8path& env_prefix, const std::string& /*shell*/, const fs::u8path& mamba_exe) { @@ -445,6 +502,10 @@ namespace mamba { conda_init_content = fish_content(conda_prefix, shell, mamba_exe); } + else if (shell == "nu") + { + conda_init_content = nu_content(conda_prefix, shell, mamba_exe); + } else if (shell == "csh") { conda_init_content = csh_content(conda_prefix, shell, mamba_exe); @@ -579,6 +640,11 @@ namespace mamba util::replace_all(contents, "$MAMBA_EXE", exe.generic_string()); return contents; } + else if (shell == "nu") + { + // not implemented due to inability in nu to evaluate dynamically generated code + return ""; + } return ""; } @@ -760,6 +826,18 @@ namespace mamba std::ofstream sh_file = open_ofstream(sh_source_path); sh_file << data_mamba_fish; } + else if (shell == "nu") + { + NuActivator a{ context }; + auto sh_source_path = a.hook_source_path(); + try + { + fs::create_directories(sh_source_path.parent_path()); + } + catch (...) + { + } + } else if (shell == "cmd.exe") { init_root_prefix_cmdexe(context, root_prefix); @@ -1052,6 +1130,11 @@ namespace mamba fs::u8path fishrc_path = home / ".config" / "fish" / "config.fish"; modify_rc_file(context, fishrc_path, conda_prefix, shell, mamba_exe); } + else if (shell == "nu") + { + fs::u8path nu_config_path = home / ".config" / "nushell" / "config.nu"; + modify_rc_file(context, nu_config_path, conda_prefix, shell, mamba_exe); + } else if (shell == "cmd.exe") { #ifndef _WIN32 @@ -1121,6 +1204,11 @@ namespace mamba fs::u8path fishrc_path = home / ".config" / "fish" / "config.fish"; reset_rc_file(context, fishrc_path, shell, mamba_exe); } + else if (shell == "nu") + { + fs::u8path nu_config_path = home / ".config" / "nushell" / "config.nu"; + reset_rc_file(context, nu_config_path, shell, mamba_exe); + } else if (shell == "cmd.exe") { #ifndef _WIN32 @@ -1178,6 +1266,10 @@ namespace mamba { config_path = home / ".config" / "fish" / "config.fish"; } + else if (shell == "nu") + { + config_path = ""; + } return config_path; } @@ -1186,7 +1278,7 @@ namespace mamba fs::u8path home = env::home_directory(); std::vector result; - std::vector supported_shells = { "bash", "zsh", "xonsh", "csh", "fish" }; + std::vector supported_shells = { "bash", "zsh", "xonsh", "csh", "fish", "nu" }; for (const std::string& shell : supported_shells) { fs::u8path config_path = config_path_for_shell(shell); diff --git a/micromamba/src/activate.cpp b/micromamba/src/activate.cpp index 26e0b28d53..008f76f2eb 100644 --- a/micromamba/src/activate.cpp +++ b/micromamba/src/activate.cpp @@ -72,7 +72,7 @@ set_activate_command(CLI::App* subcom) "Otherwise, this may be an issue. In the meantime you can run commands. See:\n" " $ micromamba run --help\n" "\n" - "Supported shells are {{bash, zsh, csh, xonsh, cmd.exe, powershell, fish}}.\n", + "Supported shells are {{bash, zsh, csh, xonsh, cmd.exe, powershell, fish, nu}}.\n", get_shell_hook(guessed_shell), guessed_shell ); diff --git a/micromamba/src/shell.cpp b/micromamba/src/shell.cpp index 29942547dd..b2bda88ed8 100644 --- a/micromamba/src/shell.cpp +++ b/micromamba/src/shell.cpp @@ -31,7 +31,7 @@ namespace subcmd ->add_option("-s,--shell", shell_type.get_cli_config(), shell_type.description()) ->check(CLI::IsMember(std::set( - { "bash", "posix", "powershell", "cmd.exe", "xonsh", "zsh", "fish", "tcsh", "dash" } + { "bash", "posix", "powershell", "cmd.exe", "xonsh", "zsh", "fish", "tcsh", "dash", "nu" } ))); } diff --git a/micromamba/tests/test_activation.py b/micromamba/tests/test_activation.py index b3fc368519..4790e259ed 100644 --- a/micromamba/tests/test_activation.py +++ b/micromamba/tests/test_activation.py @@ -32,6 +32,7 @@ "xonsh": ".sh", "fish": ".fish", "powershell": ".ps1", + "nu": ".nu", } @@ -62,6 +63,7 @@ def __getitem__(self, shell: str) -> str: "xonsh": "~/.xonshrc", "tcsh": "~/.tcshrc", "fish": "~/.config/fish/config.fish", + "nu": "~/.config/nushell/config.nu", }, "linux": { "zsh": "~/.zshrc", @@ -69,6 +71,7 @@ def __getitem__(self, shell: str) -> str: "xonsh": "~/.xonshrc", "tcsh": "~/.tcshrc", "fish": "~/.config/fish/config.fish", + "nu": "~/.config/nushell/config.nu", }, } @@ -100,8 +103,8 @@ def write_script(interpreter, lines, path): possible_interpreters = { - "win": {"powershell", "cmd.exe", "bash"}, - "unix": {"bash", "zsh", "fish", "xonsh", "tcsh"}, + "win": {"powershell", "cmd.exe", "bash", "nu"}, + "unix": {"bash", "zsh", "fish", "xonsh", "tcsh", "nu"}, } @@ -266,6 +269,8 @@ def shvar(v, interpreter): return f"$Env:{v}" elif interpreter == "cmd.exe": return f"%{v}%" + elif interpreter == "nu": + return f"$env.{v}" def env_to_dict(out, interpreter="bash"): @@ -429,6 +434,8 @@ def test_shell_init_deinit_root_prefix_files( files = [tmp_root_prefix / "etc" / "profile.d" / "mamba.xsh"] elif interpreter in ["csh", "tcsh"]: files = [tmp_root_prefix / "etc" / "profile.d" / "micromamba.csh"] + elif interpreter == "nu": + files = [] # moved to ~/.config/nushell.nu controlled by micromamba activation else: raise ValueError(f"Unknown shell {interpreter}") diff --git a/micromamba/tests/test_shell.py b/micromamba/tests/test_shell.py index 5c8993a937..fc17c2741f 100644 --- a/micromamba/tests/test_shell.py +++ b/micromamba/tests/test_shell.py @@ -26,7 +26,7 @@ def skip_if_shell_incompat(shell_type): @pytest.mark.parametrize( "shell_type", - ["bash", "posix", "powershell", "cmd.exe", "xonsh", "zsh", "fish", "tcsh"], + ["bash", "posix", "powershell", "cmd.exe", "xonsh", "zsh", "fish", "tcsh", "nu"], ) def test_hook(tmp_home, tmp_root_prefix, shell_type): res = helpers.shell("hook", "-s", shell_type) @@ -52,6 +52,10 @@ def test_hook(tmp_home, tmp_root_prefix, shell_type): assert res == "" elif shell_type == "tcsh": assert res.count(mamba_exe) == 3 + elif shell_type == "nu": + # insert dummy test, as the nu scripts contains + # no mention of mamba_exe; path is added in config.nu + assert res.count(mamba_exe) == 0 res = helpers.shell("hook", "-s", shell_type, "--json") expected_keys = {"success", "operation", "context", "actions"}