From e0410cafc8feedf72562174dee983fc709a5a36e Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Thu, 16 Sep 2021 13:10:42 +0100 Subject: [PATCH] Nushell activation scripts (#2170) --- .github/workflows/check.yml | 10 ++++- setup.cfg | 2 + src/virtualenv/activation/__init__.py | 2 + src/virtualenv/activation/nushell/__init__.py | 28 +++++++++++++ src/virtualenv/activation/nushell/activate.nu | 40 +++++++++++++++++++ .../activation/nushell/deactivate.nu | 11 +++++ tests/unit/activation/test_nushell.py | 26 ++++++++++++ 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/virtualenv/activation/nushell/__init__.py create mode 100644 src/virtualenv/activation/nushell/activate.nu create mode 100644 src/virtualenv/activation/nushell/deactivate.nu create mode 100644 tests/unit/activation/test_nushell.py diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 163fe8347..6ec364e3b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -40,8 +40,14 @@ jobs: run: | for i in 1 2 3; do echo "try $i" && \ - ${{ runner.os == 'Linux' && 'sudo apt-get update -y && sudo apt-get install fish csh' || true}} && \ - ${{ runner.os == 'macOS' && 'brew install fish tcsh' || true}} && \ + ${{ runner.os == 'Linux' && 'sudo apt-get update -y && sudo apt-get install fish csh -y' || true }} && \ + ${{ runner.os == 'Linux' && 'sudo apt-get install curl wget -y' || true }} && \ + ${{ runner.os == 'Linux' && 'nushell_url=$(curl -s https://api.github.com/repos/nushell/nushell/releases/latest | grep "browser_" | cut -d\" -f4 | grep .tar.gz)' || true }} && \ + ${{ runner.os == 'Linux' && 'wget -O nushell.tar.gz $nushell_url' || true }} && \ + ${{ runner.os == 'Linux' && 'tar -zxf nushell.tar.gz --one-top-level=nushell --strip-components=2' || true }} && \ + ${{ runner.os == 'Linux' && 'sudo cp nushell/nu /usr/bin' || true }} && \ + ${{ runner.os == 'Windows' && 'choco install nushell' || true }} && \ + ${{ runner.os == 'macOS' && 'brew update && brew install fish tcsh nushell' || true }} && \ exit 0 || true; done exit 1 diff --git a/setup.cfg b/setup.cfg index 6baf8e16d..f093ca460 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,6 +66,7 @@ virtualenv.activate = fish = virtualenv.activation.fish:FishActivator powershell = virtualenv.activation.powershell:PowerShellActivator python = virtualenv.activation.python:PythonActivator + nushell = virtualenv.activation.nushell:NushellActivator virtualenv.create = venv = virtualenv.create.via_global_ref.venv:Venv cpython3-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Posix @@ -108,6 +109,7 @@ virtualenv.activation.bash = *.sh virtualenv.activation.batch = *.bat virtualenv.activation.cshell = *.csh virtualenv.activation.fish = *.fish +virtualenv.activation.nushell = *.nu virtualenv.activation.powershell = *.ps1 virtualenv.seed.wheels.embed = *.whl diff --git a/src/virtualenv/activation/__init__.py b/src/virtualenv/activation/__init__.py index 962cdf794..e9296d86e 100644 --- a/src/virtualenv/activation/__init__.py +++ b/src/virtualenv/activation/__init__.py @@ -4,6 +4,7 @@ from .batch import BatchActivator from .cshell import CShellActivator from .fish import FishActivator +from .nushell import NushellActivator from .powershell import PowerShellActivator from .python import PythonActivator @@ -14,4 +15,5 @@ "PythonActivator", "BatchActivator", "FishActivator", + "NushellActivator", ] diff --git a/src/virtualenv/activation/nushell/__init__.py b/src/virtualenv/activation/nushell/__init__.py new file mode 100644 index 000000000..994c1fb6b --- /dev/null +++ b/src/virtualenv/activation/nushell/__init__.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text + +from ..via_template import ViaTemplateActivator + + +class NushellActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.nu") + yield Path("deactivate.nu") + + def replacements(self, creator, dest_folder): + # Due to nushell scoping, it isn't easy to create a function that will + # deactivate the environment. For that reason a __DEACTIVATE_PATH__ + # replacement pointing to the deactivate.nu file is created + + return { + "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, + "__VIRTUAL_ENV__": ensure_text(str(creator.dest)), + "__VIRTUAL_NAME__": creator.env_name, + "__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))), + "__PATH_SEP__": ensure_text(os.pathsep), + "__DEACTIVATE_PATH__": ensure_text(str(Path(dest_folder) / "deactivate.nu")), + } diff --git a/src/virtualenv/activation/nushell/activate.nu b/src/virtualenv/activation/nushell/activate.nu new file mode 100644 index 000000000..f0a4da44f --- /dev/null +++ b/src/virtualenv/activation/nushell/activate.nu @@ -0,0 +1,40 @@ +# Setting all environment variables for the venv +let virtual-env = "__VIRTUAL_ENV__" +let bin = "__BIN_NAME__" +let path-sep = "__PATH_SEP__" + +let old-path = ($nu.path | str collect ($path-sep)) + +let venv-path = ([$virtual-env $bin] | path join) +let new-path = ($nu.path | prepend $venv-path | str collect ($path-sep)) + +# environment variables that will be batched loaded to the virtual env +let new-env = ([ + [name, value]; + [PATH $new-path] + [_OLD_VIRTUAL_PATH $old-path] + [VIRTUAL_ENV $virtual-env] +]) + +load-env $new-env + +# Creating the new prompt for the session +let virtual_prompt = (if ("__VIRTUAL_PROMPT__" != "") { + "__VIRTUAL_PROMPT__" +} { + $virtual-env | path basename +} +) + +# If there is no default prompt, then only the env is printed in the prompt +let new_prompt = (if ( config | select prompt | empty? ) { + ($"build-string '(char lparen)' '($virtual_prompt)' '(char rparen) ' ") +} { + ($"build-string '(char lparen)' '($virtual_prompt)' '(char rparen) ' (config get prompt | str find-replace "build-string" "")") +}) +let-env PROMPT_COMMAND = $new_prompt + +# We are using alias as the function definitions because only aliases can be +# removed from the scope +alias pydoc = python -m pydoc +alias deactivate = source "__DEACTIVATE_PATH__" diff --git a/src/virtualenv/activation/nushell/deactivate.nu b/src/virtualenv/activation/nushell/deactivate.nu new file mode 100644 index 000000000..405243803 --- /dev/null +++ b/src/virtualenv/activation/nushell/deactivate.nu @@ -0,0 +1,11 @@ +# Setting the old path +let path-name = (if ((sys).host.name == "Windows") { "Path" } { "PATH" }) +let-env $path-name = $nu.env._OLD_VIRTUAL_PATH + +# Unleting the environment variables that were created when activating the env +unlet-env VIRTUAL_ENV +unlet-env _OLD_VIRTUAL_PATH +unlet-env PROMPT_COMMAND + +unalias pydoc +unalias deactivate diff --git a/tests/unit/activation/test_nushell.py b/tests/unit/activation/test_nushell.py new file mode 100644 index 000000000..6eb50ba52 --- /dev/null +++ b/tests/unit/activation/test_nushell.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import, unicode_literals + +import sys + +if sys.version_info > (3,): + from shutil import which +else: + from distutils.spawn import find_executable as which + + +from virtualenv.activation import NushellActivator +from virtualenv.info import IS_WIN + + +def test_nushell(activation_tester_class, activation_tester): + class Nushell(activation_tester_class): + def __init__(self, session): + cmd = which("nu") + if cmd is None and IS_WIN: + cmd = "c:\\program files\\nu\\bin\\nu.exe" + + super(Nushell, self).__init__(NushellActivator, session, cmd, "activate.nu", "nu") + + self.unix_line_ending = not IS_WIN + + activation_tester(Nushell)