mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
Merge pull request #3277 from astronouth7303/xontrib-autovox
Add autovox xontrib
This commit is contained in:
commit
9acc111d65
6 changed files with 205 additions and 4 deletions
|
@ -102,3 +102,19 @@ Simply add the ``'{env_name}'`` variable to your ``$PROMPT``::
|
|||
|
||||
Note that you do **not** need to load the ``vox`` xontrib for this to work.
|
||||
For more details see :ref:`customprompt`.
|
||||
|
||||
|
||||
Automatically Switching Environments
|
||||
------------------------------------
|
||||
|
||||
Automatic environment switching based on the current directory is managed with the ``autovox`` xontrib (``xontrib load autovox``). Third-party xontribs may register various policies for use with autovox. Pick and choose xontribs that implement policies that match your work style.
|
||||
|
||||
Implementing policies is easy! Just register with the ``autovox_policy`` event and return a ``Path`` if there is a matching venv. For example, this policy implements handling if there is a ``.venv`` directory in the project::
|
||||
|
||||
@events.autovox_policy
|
||||
def dotvenv_policy(path, **_):
|
||||
venv = path / '.venv'
|
||||
if venv.exists():
|
||||
return venv
|
||||
|
||||
Note that you should only return if there is an environment for this directory exactly. Scanning parent directories is managed by autovox. You should also make the policy check relatively cheap. (Local IO is ok, but probably shouldn't call out to network services.)
|
||||
|
|
23
news/autovox.rst
Normal file
23
news/autovox.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
**Added:**
|
||||
|
||||
* Added ``autovox`` xontrib
|
||||
|
||||
**Changed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
import sys
|
||||
from xontrib.voxapi import Vox
|
||||
|
||||
from tools import skip_if_on_conda, skip_if_on_msys
|
||||
from tools import skip_if_on_conda, skip_if_on_msys, skip_if_lt_py36
|
||||
from xonsh.platform import ON_WINDOWS
|
||||
|
||||
|
||||
|
@ -209,9 +209,9 @@ else:
|
|||
|
||||
@skip_if_on_msys
|
||||
@skip_if_on_conda
|
||||
def test_crud_subdir(xonsh_builtins, tmpdir):
|
||||
def test_reserved_names(xonsh_builtins, tmpdir):
|
||||
"""
|
||||
Creates a virtual environment, gets it, enumerates it, and then deletes it.
|
||||
Tests that reserved words are disallowed.
|
||||
"""
|
||||
xonsh_builtins.__xonsh__.env["VIRTUALENV_HOME"] = str(tmpdir)
|
||||
|
||||
|
@ -227,3 +227,51 @@ def test_crud_subdir(xonsh_builtins, tmpdir):
|
|||
vox.create("spameggs/Scripts")
|
||||
else:
|
||||
vox.create("spameggs/bin")
|
||||
|
||||
|
||||
@skip_if_on_msys
|
||||
@skip_if_on_conda
|
||||
@skip_if_lt_py36
|
||||
def test_autovox(xonsh_builtins, tmpdir):
|
||||
"""
|
||||
Tests that autovox works
|
||||
"""
|
||||
import importlib
|
||||
from xonsh.lib import subprocess
|
||||
import xonsh.dirstack
|
||||
|
||||
# Set up an isolated venv home
|
||||
xonsh_builtins.__xonsh__.env["VIRTUALENV_HOME"] = str(tmpdir)
|
||||
|
||||
# Makes sure that event handlers are registered
|
||||
import xontrib.autovox
|
||||
importlib.reload(xontrib.autovox)
|
||||
|
||||
# Set up enough environment for xonsh to function
|
||||
xonsh_builtins.__xonsh__.env['PWD'] = os.getcwd()
|
||||
xonsh_builtins.__xonsh__.env['DIRSTACK_SIZE'] = 10
|
||||
xonsh_builtins.__xonsh__.env['PATH'] = []
|
||||
|
||||
xonsh_builtins.__xonsh__.env['XONSH_SHOW_TRACEBACK'] = True
|
||||
|
||||
@xonsh_builtins.events.autovox_policy
|
||||
def policy(path, **_):
|
||||
print("Checking", repr(path), vox.active())
|
||||
if str(path) == str(tmpdir):
|
||||
return "myenv"
|
||||
|
||||
vox = Vox()
|
||||
|
||||
print(xonsh_builtins.__xonsh__.env['PWD'])
|
||||
xonsh.dirstack.pushd([str(tmpdir)])
|
||||
print(xonsh_builtins.__xonsh__.env['PWD'])
|
||||
assert vox.active() is None
|
||||
xonsh.dirstack.popd([])
|
||||
print(xonsh_builtins.__xonsh__.env['PWD'])
|
||||
|
||||
vox.create('myenv')
|
||||
xonsh.dirstack.pushd([str(tmpdir)])
|
||||
print(xonsh_builtins.__xonsh__.env['PWD'])
|
||||
assert vox.active() == 'myenv'
|
||||
xonsh.dirstack.popd([])
|
||||
print(xonsh_builtins.__xonsh__.env['PWD'])
|
||||
|
|
|
@ -122,12 +122,17 @@ class DummyEnv(MutableMapping):
|
|||
return {k: str(v) for k, v in self._d.items()}
|
||||
|
||||
def __getitem__(self, k):
|
||||
if k is ...:
|
||||
return self
|
||||
else:
|
||||
return self._d[k]
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
assert k is not ...
|
||||
self._d[k] = v
|
||||
|
||||
def __delitem__(self, k):
|
||||
assert k is not ...
|
||||
del self._d[k]
|
||||
|
||||
def __len__(self):
|
||||
|
@ -156,6 +161,9 @@ class DummyEnv(MutableMapping):
|
|||
else:
|
||||
self[k] = v
|
||||
|
||||
def is_manually_set(self, key):
|
||||
return False
|
||||
|
||||
|
||||
#
|
||||
# Execer tools
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
"url": "https://github.com/sagartewari01/autojump-xonsh",
|
||||
"description": ["autojump support for xonsh"]
|
||||
},
|
||||
{"name": "autovox",
|
||||
"package": "xonsh",
|
||||
"url": "http://xon.sh",
|
||||
"description": ["Manages automatic activation of virtual environments."]
|
||||
},
|
||||
{"name": "autoxsh",
|
||||
"package": "xonsh-autoxsh",
|
||||
"url": "https://github.com/Granitas/xonsh-autoxsh",
|
||||
|
|
101
xontrib/autovox.py
Normal file
101
xontrib/autovox.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
A framework for automatic vox.
|
||||
|
||||
This coordinates multiple automatic vox policies and deals with some of the
|
||||
mechanics of venv searching and chdir handling.
|
||||
|
||||
This provides no interface for end users.
|
||||
|
||||
Developers should look at events.autovox_policy
|
||||
"""
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
import xontrib.voxapi as voxapi
|
||||
import warnings
|
||||
|
||||
__all__ = ()
|
||||
|
||||
|
||||
_policies = []
|
||||
|
||||
events.doc(
|
||||
"autovox_policy",
|
||||
"""
|
||||
autovox_policy(path: pathlib.Path) -> Union[str, pathlib.Path, None]
|
||||
|
||||
Register a policy with autovox.
|
||||
|
||||
A policy is a function that takes a Path and returns the venv associated with it,
|
||||
if any.
|
||||
|
||||
NOTE: The policy should only return a venv for this path exactly, not for
|
||||
parent paths. Parent walking is handled by autovox so that all policies can
|
||||
be queried at each level.
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
class MultipleVenvsWarning(RuntimeWarning):
|
||||
pass
|
||||
|
||||
|
||||
def get_venv(vox, dirpath):
|
||||
# Search up the directory tree until a venv is found, or none
|
||||
for path in itertools.chain((dirpath,), dirpath.parents):
|
||||
venvs = [
|
||||
vox[p]
|
||||
for p in events.autovox_policy.fire(path=path)
|
||||
if p is not None and p in vox # Filter out venvs that don't exist
|
||||
]
|
||||
if len(venvs) == 0:
|
||||
continue
|
||||
else:
|
||||
if len(venvs) > 1:
|
||||
warnings.warn(
|
||||
MultipleVenvsWarning(
|
||||
f"Found {len(venvs)} venvs for {path}; using the first"
|
||||
)
|
||||
)
|
||||
return venvs[0]
|
||||
|
||||
|
||||
def check_for_new_venv(curdir):
|
||||
vox = voxapi.Vox()
|
||||
try:
|
||||
oldve = vox[...]
|
||||
except KeyError:
|
||||
oldve = None
|
||||
newve = get_venv(vox, curdir)
|
||||
|
||||
if oldve != newve:
|
||||
if newve is None:
|
||||
vox.deactivate()
|
||||
else:
|
||||
vox.activate(newve.env)
|
||||
|
||||
|
||||
# Core mechanism: Check for venv when the current directory changes
|
||||
@events.on_chdir
|
||||
def cd_handler(newdir, **_):
|
||||
check_for_new_venv(Path(newdir))
|
||||
|
||||
|
||||
# Recalculate when venvs are created or destroyed
|
||||
|
||||
|
||||
@events.vox_on_create
|
||||
def create_handler(**_):
|
||||
check_for_new_venv(Path.cwd())
|
||||
|
||||
|
||||
@events.vox_on_destroy
|
||||
def destroy_handler(**_):
|
||||
check_for_new_venv(Path.cwd())
|
||||
|
||||
|
||||
# Initial activation before first prompt
|
||||
|
||||
|
||||
@events.on_post_init
|
||||
def load_handler(**_):
|
||||
check_for_new_venv(Path.cwd())
|
Loading…
Add table
Reference in a new issue