Skip to content

Commit

Permalink
Merge pull request conda#8644 from scopatz/xsh-fix
Browse files Browse the repository at this point in the history
Xonsh Activation fixes
  • Loading branch information
msarahan authored May 10, 2019
2 parents a36c1fe + 865f5ca commit 8232664
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 59 deletions.
4 changes: 2 additions & 2 deletions conda/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ def __init__(self, arguments=None):
self.sep = '/'
self.path_conversion = native_path_to_unix
self.script_extension = '.xsh'
self.tempfile_extension = '.xsh'
self.tempfile_extension = None
self.command_join = '\n'

self.unset_var_tmpl = 'del $%s'
Expand All @@ -851,7 +851,7 @@ def __init__(self, arguments=None):
super(XonshActivator, self).__init__(arguments)

def _hook_preamble(self):
return 'CONDA_EXE = "%s"' % context.conda_exe
return '$CONDA_EXE = "%s"' % context.conda_exe


class CmdExeActivator(_Activator):
Expand Down
113 changes: 112 additions & 1 deletion conda/core/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def make_install_plan(conda_prefix):
plan.append({
'function': install_conda_xsh.__name__,
'kwargs': {
'target_path': join(site_packages_dir, 'xonsh', 'conda.xsh'),
'target_path': join(site_packages_dir, 'xontrib', 'conda.xsh'),
'conda_prefix': conda_prefix,
},
})
Expand Down Expand Up @@ -493,6 +493,32 @@ def make_initialize_plan(conda_prefix, shells, for_user, for_system, anaconda_pr
},
})

if 'xonsh' in shells:
if for_user:
config_xonsh_path = expand(join('~', '.xonshrc'))
plan.append({
'function': init_xonsh_user.__name__,
'kwargs': {
'target_path': config_xonsh_path,
'conda_prefix': conda_prefix,
'reverse': reverse,
},
})

if for_system:
if on_win:
config_xonsh_path = expand(join('%ALLUSERSPROFILE%', 'xonsh', 'xonshrc'))
else:
config_xonsh_path = '/etc/xonshrc'
plan.append({
'function': init_xonsh_user.__name__,
'kwargs': {
'target_path': config_xonsh_path,
'conda_prefix': conda_prefix,
'reverse': reverse,
},
})

if 'tcsh' in shells and for_user:
tcshrc_path = expand(join('~', '.tcshrc'))
plan.append({
Expand Down Expand Up @@ -1066,6 +1092,91 @@ def init_fish_user(target_path, conda_prefix, reverse):
return Result.NO_CHANGE


def _config_xonsh_content(conda_prefix):
if on_win:
from ..activate import native_path_to_unix
conda_exe = native_path_to_unix(join(conda_prefix, 'Scripts', 'conda.exe'))
else:
conda_exe = join(conda_prefix, 'bin', 'conda')
conda_initialize_content = dals("""
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
import sys as _sys
from types import ModuleType as _ModuleType
_mod = _ModuleType("xontrib.conda",
"Autogenerated from $({conda_exe} shell.xonsh hook)")
__xonsh__.execer.exec($("{conda_exe}" "shell.xonsh" "hook"),
glbs=_mod.__dict__,
filename="$({conda_exe} shell.xonsh hook)")
_sys.modules["xontrib.conda"] = _mod
del _sys, _mod, _ModuleType
# <<< conda initialize <<<
""").format(conda_exe=conda_exe)
return conda_initialize_content


def init_xonsh_user(target_path, conda_prefix, reverse):
# target_path: ~/.xonshrc
user_rc_path = target_path

try:
with open(user_rc_path) as fh:
rc_content = fh.read()
except FileNotFoundError:
rc_content = ''
except:
raise

rc_original_content = rc_content

conda_init_comment = "# commented out by conda initialize"
conda_initialize_content = _config_xonsh_content(conda_prefix)
if reverse:
# uncomment any lines that were commented by prior conda init run
rc_content = re.sub(
r"#\s(.*?)\s*{}".format(conda_init_comment),
r"\1",
rc_content,
flags=re.MULTILINE,
)

# remove any conda init sections added
rc_content = re.sub(
r"^\s*" + CONDA_INITIALIZE_RE_BLOCK,
"",
rc_content,
flags=re.DOTALL | re.MULTILINE
)
else:
replace_str = "__CONDA_REPLACE_ME_123__"
rc_content = re.sub(
CONDA_INITIALIZE_RE_BLOCK,
replace_str,
rc_content,
flags=re.MULTILINE,
)
# TODO: maybe remove all but last of replace_str, if there's more than one occurrence
rc_content = rc_content.replace(replace_str, conda_initialize_content)

if "# >>> conda initialize >>>" not in rc_content:
rc_content += '\n{0}\n'.format(conda_initialize_content)

if rc_content != rc_original_content:
if context.verbosity:
print('\n')
print(target_path)
print(make_diff(rc_original_content, rc_content))
if not context.dry_run:
# Make the directory if needed.
if not exists(dirname(user_rc_path)):
mkdir_p(dirname(user_rc_path))
with open(user_rc_path, 'w') as fh:
fh.write(rc_content)
return Result.MODIFIED
else:
return Result.NO_CHANGE


def _bashrc_content(conda_prefix, shell):
if on_win:
from ..activate import native_path_to_unix
Expand Down
146 changes: 124 additions & 22 deletions conda/shell/conda.xsh
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
from argparse import ArgumentParser
import os
import sys
# Much of this forked from https://github.com/gforsyth/xonda
# Copyright (c) 2016, Gil Forsyth, All rights reserved.
# Original code licensed under BSD-3-Clause.
from xonsh.lazyasd import lazyobject

if '_CONDA_EXE' not in locals():
_CONDA_EXE = "python -m conda" # development mode
if 'CONDA_EXE' not in ${...}:
![python -m conda init --dev out> conda-dev-init.sh]
source-bash conda-dev-init.sh
import os
os.remove("conda-dev-init.sh")

_REACTIVATE_COMMANDS = ('install', 'update', 'upgrade', 'remove', 'uninstall')


@lazyobject
def Env():
from collections import namedtuple
return namedtuple('Env', ['name', 'path', 'bin_dir', 'envs_dir'])


def _parse_args(args=None):
from argparse import ArgumentParser
p = ArgumentParser(add_help=False)
p.add_argument('command')
ns, _ = p.parse_known_args(args)
Expand All @@ -34,33 +45,29 @@ def _raise_pipeline_error(pipeline):


def _conda_activate_handler(env_name_or_prefix):
pipeline = !(@(_CONDA_EXE) shell.xonsh activate @(env_name_or_prefix))
stdout = _raise_pipeline_error(pipeline)
source @(stdout)
os.unlink(stdout)
__xonsh__.execer.exec($($CONDA_EXE shell.xonsh activate @(env_name_or_prefix)),
glbs=__xonsh__.ctx,
filename="$(conda shell.xonsh activate " + env_name_or_prefix + ")")


def _conda_deactivate_handler():
pipeline = !(@(_CONDA_EXE) shell.xonsh deactivate)
stdout = _raise_pipeline_error(pipeline)
source @(stdout)
os.unlink(stdout)
__xonsh__.execer.exec($($CONDA_EXE shell.xonsh deactivate),
glbs=__xonsh__.ctx,
filename="$(conda shell.xonsh deactivate)")


def _conda_passthrough_handler(args):
pipeline = ![@(_CONDA_EXE) @(' '.join(args))]
pipeline = ![$CONDA_EXE @(args)]
_raise_pipeline_error(pipeline)


def _conda_reactivate_handler(args, name_or_prefix_given):
pipeline = ![@(_CONDA_EXE) @(' '.join(args))]
pipeline = ![$CONDA_EXE @(args)]
_raise_pipeline_error(pipeline)

if not name_or_prefix_given:
pipeline = !(@(_CONDA_EXE) shell.xonsh reactivate)
stdout = _raise_pipeline_error(pipeline)
source @(stdout)
os.unlink(stdout)
__xonsh__.execer.exec($($CONDA_EXE shell.xonsh reactivate),
glbs=__xonsh__.ctx,
filename="$(conda shell.xonsh reactivate)")


def _conda_main(args=None):
Expand All @@ -78,7 +85,102 @@ def _conda_main(args=None):

if 'CONDA_SHLVL' not in ${...}:
$CONDA_SHLVL = '0'
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname($CONDA_EXE)), "condabin"))
import os as _os
import sys as _sys
_sys.path.insert(0, _os.path.join(_os.path.dirname(_os.path.dirname($CONDA_EXE)), "condabin"))
del _os, _sys

aliases['conda'] = _conda_main


def _list_dirs(path):
"""
Generator that lists the directories in a given path.
"""
import os
for entry in os.scandir(path):
if not entry.name.startswith('.') and entry.is_dir():
yield entry.name


def _get_envs():
"""
Grab a list of all conda env dirs from conda.
"""
import os
import warnings
import importlib
with warnings.catch_warnings():
warnings.simplefilter("ignore")
try:
# breaking changes introduced in Anaconda 4.4.7
# try to import newer library structure first
context = importlib.import_module('conda.base.context')
config = context.context
except ModuleNotFoundError:
config = importlib.import_module('conda.config')

# create the list of envrionments
env_list = []
for envs_dir in config.envs_dirs:
# skip non-existing environments directories
if not os.path.exists(envs_dir):
continue
# for each environment in the environments directory
for env_name in _list_dirs(envs_dir):
# check for duplicates names
if env_name in [env.name for env in env_list]:
raise ValueError('Multiple environments with the same name '
"in the system is not supported by conda's xonsh tools.")
# add the environment to the list
env_list.append(Env(name=env_name,
path=os.path.join(envs_dir, env_name),
bin_dir=os.path.join(envs_dir, env_name, 'bin'),
envs_dir=envs_dir,
))
return env_list


def _conda_completer(prefix, line, start, end, ctx):
"""
Completion for conda
"""
args = line.split(' ')
possible = set()
if len(args) == 0 or args[0] not in ['xonda', 'conda']:
return None
curix = args.index(prefix)
if curix == 1:
possible = {'activate', 'deactivate', 'install', 'remove', 'info',
'help', 'list', 'search', 'update', 'upgrade', 'uninstall',
'config', 'init', 'clean', 'package', 'bundle', 'env',
'select', 'create'}

elif curix == 2:
if args[1] in ['activate', 'select']:
possible = set([env.name for env in _get_envs()])
elif args[1] == 'create':
possible = {'-p', '-n'}
elif args[1] == 'env':
possible = {'attach', 'create', 'export', 'list', 'remove',
'upload', 'update'}

elif curix == 3:
if args[2] == 'export':
possible = {'-n', '--name'}
elif args[2] == 'create':
possible = {'-h', '--help', '-f', '--file', '-n', '--name', '-p',
'--prefix', '-q', '--quiet', '--force', '--json',
'--debug', '-v', '--verbose'}

elif curix == 4:
if args[2] == 'export' and args[3] in ['-n','--name']:
possible = set([env.name for env in _get_envs()])

return {i for i in possible if i.startswith(prefix)}


# add _xonda_completer to list of completers
__xonsh__.completers['conda'] = _conda_completer
# bump to top of list
__xonsh__.completers.move_to_end('conda', last=False)
Loading

0 comments on commit 8232664

Please sign in to comment.