Skip to content

Commit

Permalink
Implement our own subshell logic
Browse files Browse the repository at this point in the history
To replace Pew's "workon" command. New code built on Shellingham.

This also fixes a few additional minor bugs, e.g. Cmder not launching
Powershell when it should.
  • Loading branch information
uranusjr committed Jun 16, 2018
1 parent e326ce6 commit 065b50b
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 124 deletions.
5 changes: 5 additions & 0 deletions pipenv/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def _infer_return_type(*args):
except ImportError:
from pathlib2 import Path

# Backport required for earlier versions of Python.
if sys.version_info < (3, 3):
from .vendor.backports.shutil_get_terminal_size import get_terminal_size
else:
from shutil import get_terminal_size

try:
from weakref import finalize
Expand Down
134 changes: 25 additions & 109 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@
PIPENV_CACHE_DIR,
)

# Backport required for earlier versions of Python.
if sys.version_info < (3, 3):
from .vendor.backports.shutil_get_terminal_size import get_terminal_size
else:
from shutil import get_terminal_size
# Packages that should be ignored later.
BAD_PACKAGES = ('setuptools', 'pip', 'wheel', 'packaging', 'distribute')
# Are we using the default Python?
Expand Down Expand Up @@ -720,7 +715,7 @@ def do_install_dependencies(
verbose=False,
concurrent=True,
requirements_dir=None,
pypi_mirror = False,
pypi_mirror=False,
):
""""Executes the install functionality.
Expand Down Expand Up @@ -1006,7 +1001,7 @@ def do_lock(
pre=False,
keep_outdated=False,
write=True,
pypi_mirror = None,
pypi_mirror=None,
):
"""Executes the freeze functionality."""
from .utils import get_vcs_deps
Expand Down Expand Up @@ -1169,29 +1164,6 @@ def do_lock(
return lockfile


def activate_virtualenv(source=True):
"""Returns the string to activate a virtualenv."""
# Suffix and source command for other shells.
suffix = ''
command = ' .' if source else ''
# Support for fish shell.
if PIPENV_SHELL and 'fish' in PIPENV_SHELL:
suffix = '.fish'
command = 'source'
# Support for csh shell.
if PIPENV_SHELL and 'csh' in PIPENV_SHELL:
suffix = '.csh'
command = 'source'
# Escape any spaces located within the virtualenv path to allow
# for proper activation.
venv_location = project.virtualenv_location.replace(' ', r'\ ')
if source:
return '{2} {0}/bin/activate{1}'.format(venv_location, suffix, command)

else:
return '{0}/bin/activate'.format(venv_location)


def do_purge(bare=False, downloads=False, allow_global=False, verbose=False):
"""Executes the purge functionality."""
if downloads:
Expand Down Expand Up @@ -1381,7 +1353,7 @@ def pip_install(
selective_upgrade=False,
requirements_dir=None,
extra_indexes=None,
pypi_mirror = None,
pypi_mirror=None,
):
from notpip._internal import logger as piplogger
from notpip._vendor.pyparsing import ParseException
Expand Down Expand Up @@ -2150,93 +2122,37 @@ def do_uninstall(


def do_shell(three=None, python=False, fancy=False, shell_args=None):
from .patched.pew import pew

# Ensure that virtualenv is available.
ensure_project(three=three, python=python, validate=False)
# Set an environment variable, so we know we're in the environment.
os.environ['PIPENV_ACTIVE'] = '1'
compat = (not fancy)
# Support shell compatibility mode.
if PIPENV_SHELL_FANCY:
compat = False
# Compatibility mode:
if compat:
if PIPENV_SHELL:
shell = os.path.abspath(PIPENV_SHELL)
else:
click.echo(
crayons.red(
'Please ensure that the {0} environment variable '
'is set before activating shell.'.format(
crayons.normal('SHELL', bold=True)
)
),
err=True,
)
sys.exit(1)
fancy = True

from .shells import choose_shell
shell = choose_shell()
click.echo("Launching subshell in virtual environment…", err=True)

fork_args = (
project.virtualenv_location,
project.project_directory,
shell_args,
)

if fancy:
shell.fork(**fork_args)
return

try:
shell.fork_compat(**fork_args)
except (AttributeError, ImportError):
click.echo(
crayons.normal(
'Spawning environment shell ({0}). Use {1} to leave.'.format(
crayons.red(shell), crayons.normal("'exit'", bold=True)
),
bold=True,
),
u'Compatibility mode not supported. '
u'Trying to continue as well-configured shell…',
err=True,
)
cmd = "{0} -i'".format(shell)
args = []
# Standard (properly configured shell) mode:
else:
if project.is_venv_in_project():
# use .venv as the target virtualenv name
workon_name = '.venv'
else:
workon_name = project.virtualenv_name
cmd = sys.executable
args = ['-m', 'pipenv.pew', 'workon', workon_name]
# Grab current terminal dimensions to replace the hardcoded default
# dimensions of pexpect
terminal_dimensions = get_terminal_size()
try:
with temp_environ():
if project.is_venv_in_project():
os.environ['WORKON_HOME'] = project.project_directory
c = pexpect.spawn(
cmd,
args,
dimensions=(
terminal_dimensions.lines, terminal_dimensions.columns
),
)
# Windows!
except AttributeError:
# import subprocess
# Tell pew to use the project directory as its workon_home
with temp_environ():
if project.is_venv_in_project():
os.environ['WORKON_HOME'] = project.project_directory
pew.workon_cmd([workon_name])
sys.exit(0)
# Activate the virtualenv if in compatibility mode.
if compat:
c.sendline(activate_virtualenv())
# Send additional arguments to the subshell.
if shell_args:
c.sendline(' '.join(shell_args))

# Handler for terminal resizing events
# Must be defined here to have the shell process in its context, since we
# can't pass it as an argument
def sigwinch_passthrough(sig, data):
terminal_dimensions = get_terminal_size()
c.setwinsize(terminal_dimensions.lines, terminal_dimensions.columns)

signal.signal(signal.SIGWINCH, sigwinch_passthrough)
# Interact with the new shell.
c.interact(escape_character=None)
c.close()
sys.exit(c.exitstatus)
shell.fork(**fork_args)


def inline_activate_virtualenv():
Expand Down
1 change: 1 addition & 0 deletions pipenv/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno()))
PIPENV_SHELL_EXPLICIT = os.environ.get('PIPENV_SHELL')
PIPENV_SHELL = os.environ.get('SHELL') or os.environ.get('PYENV_SHELL')
PIPENV_EMULATOR = os.environ.get('PIPENV_EMULATOR')
PIPENV_CACHE_DIR = os.environ.get('PIPENV_CACHE_DIR', user_cache_dir('pipenv'))
# Tells pipenv to override PyPI index urls with a mirror.
PIPENV_PYPI_MIRROR = os.environ.get('PIPENV_PYPI_MIRROR')
Loading

0 comments on commit 065b50b

Please sign in to comment.