From a775a85f8cfb7e6187198355052ce48fd9224e39 Mon Sep 17 00:00:00 2001 From: Rob McBroom Date: Fri, 1 Jun 2018 09:06:04 -0400 Subject: [PATCH 1/8] prefix activation command with a space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For ZSH users with HIST_IGNORE_SPACE, this will keep activation noise out of the shell’s history. --- pipenv/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv/core.py b/pipenv/core.py index 504ce47e0d..3dac646bdf 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1181,7 +1181,7 @@ def activate_virtualenv(source=True): """Returns the string to activate a virtualenv.""" # Suffix and source command for other shells. suffix = '' - command = '.' if source else '' + command = ' .' if source else '' # Support for fish shell. if PIPENV_SHELL and 'fish' in PIPENV_SHELL: suffix = '.fish' From 8a274808032a6ac3de4325d46ecb727c41401a4b Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 03:34:31 +0800 Subject: [PATCH 2/8] Purge pew and write all the things! This will need A LOT of manual testing, on: * macOS * Linux * Bash * Fish * Zsh * more? * Fancy mode * Compat (default) mode Windows is surprisingly the simplest here, and I am quite sure it will work splendidly. On other platforms, I don't know. Let's see. --- docs/advanced.rst | 7 +- docs/requirements.txt | 1 - pipenv/_compat.py | 8 +- pipenv/core.py | 167 ++-- pipenv/environments.py | 6 +- pipenv/patched/patched.txt | 1 - pipenv/patched/pew/LICENSE | 17 - pipenv/patched/pew/__init__.py | 4 - pipenv/patched/pew/__main__.py | 4 - pipenv/patched/pew/_print_utils.py | 58 -- pipenv/patched/pew/_utils.py | 92 -- pipenv/patched/pew/pew.py | 786 ------------------ pipenv/patched/pew/shell_config/_pew | 107 --- pipenv/patched/pew/shell_config/complete.bash | 46 - pipenv/patched/pew/shell_config/complete.fish | 116 --- pipenv/patched/pew/shell_config/complete.zsh | 107 --- .../patched/pew/shell_config/complete_deploy | 4 - pipenv/patched/pew/shell_config/init.bash | 3 - pipenv/patched/pew/shell_config/init.fish | 9 - pipenv/patched/pew/shell_config/init.zsh | 12 - pipenv/patched/pew/template_django | 6 - pipenv/patched/piptools/utils.py | 2 +- pipenv/pew/__init__.py | 0 pipenv/pew/__main__.py | 13 - pipenv/pipenv.1 | 1 - pipenv/project.py | 19 +- pipenv/shelltools/__init__.py | 199 +++++ .../pew/_win_utils.py => shelltools/nt.py} | 4 +- pipenv/shelltools/posix.py | 59 ++ pipenv/utils.py | 2 +- setup.py | 1 - tasks/vendoring/__init__.py | 3 +- .../pew-cmder-root-space-escape-fix.patch | 14 - .../pew-windows-env-name-in-prompt.patch | 13 - tasks/vendoring/patches/patched/pew.patch | 287 ------- .../patches/patched/pew_psutil.patch | 151 ---- tests/integration/test_dot_venv.py | 45 - 37 files changed, 335 insertions(+), 2039 deletions(-) delete mode 100644 pipenv/patched/pew/LICENSE delete mode 100644 pipenv/patched/pew/__init__.py delete mode 100644 pipenv/patched/pew/__main__.py delete mode 100644 pipenv/patched/pew/_print_utils.py delete mode 100644 pipenv/patched/pew/_utils.py delete mode 100644 pipenv/patched/pew/pew.py delete mode 100644 pipenv/patched/pew/shell_config/_pew delete mode 100644 pipenv/patched/pew/shell_config/complete.bash delete mode 100644 pipenv/patched/pew/shell_config/complete.fish delete mode 100644 pipenv/patched/pew/shell_config/complete.zsh delete mode 100755 pipenv/patched/pew/shell_config/complete_deploy delete mode 100644 pipenv/patched/pew/shell_config/init.bash delete mode 100644 pipenv/patched/pew/shell_config/init.fish delete mode 100644 pipenv/patched/pew/shell_config/init.zsh delete mode 100644 pipenv/patched/pew/template_django delete mode 100644 pipenv/pew/__init__.py delete mode 100644 pipenv/pew/__main__.py create mode 100644 pipenv/shelltools/__init__.py rename pipenv/{patched/pew/_win_utils.py => shelltools/nt.py} (97%) create mode 100644 pipenv/shelltools/posix.py delete mode 100644 tasks/vendoring/patches/patched/pew-cmder-root-space-escape-fix.patch delete mode 100644 tasks/vendoring/patches/patched/pew-windows-env-name-in-prompt.patch delete mode 100644 tasks/vendoring/patches/patched/pew.patch delete mode 100644 tasks/vendoring/patches/patched/pew_psutil.patch diff --git a/docs/advanced.rst b/docs/advanced.rst index de16e2e2db..fbd7116fe4 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -365,7 +365,7 @@ will detect it. - ``PIPENV_SHELL_FANCY`` — Always use fancy mode when invoking ``pipenv shell``. - ``PIPENV_VENV_IN_PROJECT`` — If set, use ``.venv`` in your project directory - instead of the global virtualenv manager ``pew``. + instead of the global ``WORKON_HOME`` directory. - ``PIPENV_COLORBLIND`` — Disable terminal colors, for some reason. @@ -407,8 +407,9 @@ For example:: ☤ Custom Virtual Environment Location ------------------------------------- -Pipenv's underlying ``pew`` dependency will automatically honor the ``WORKON_HOME`` environment -variable, if you have it set — so you can tell pipenv to store your virtual environments wherever you want, e.g.:: +Pipenv automatically honors the ``WORKON_HOME`` environment variable, if you +have it set — so you can tell pipenv to store your virtual environments +wherever you want, e.g.:: export WORKON_HOME=~/.venvs diff --git a/docs/requirements.txt b/docs/requirements.txt index a14ace82b5..f29ee87352 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -10,7 +10,6 @@ imagesize==0.7.1 Jinja2==2.9.6 MarkupSafe==1.0 pbr==3.1.1 -pew==0.1.26 pip-tools==1.9.0 -e . Pygments==2.2.0 diff --git a/pipenv/_compat.py b/pipenv/_compat.py index 68d8069d5e..372c2e587e 100644 --- a/pipenv/_compat.py +++ b/pipenv/_compat.py @@ -50,12 +50,16 @@ def __init__(self, *args, **kwargs): def detach(self): return False - if six.PY2: - class ResourceWarning(Warning): pass +# 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 + def pip_import(module_path, subimport=None, old_path=None): internal = 'pip._internal.{0}'.format(module_path) diff --git a/pipenv/core.py b/pipenv/core.py index 00e5e0ccbf..bdc0d5e871 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -15,7 +15,6 @@ import crayons import dotenv import delegator -from .vendor import pexpect from first import first import pipfile from blindspin import spinner @@ -78,11 +77,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? @@ -471,7 +465,7 @@ def activate_pyenv(): if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES): version_map = { # TODO: Keep this up to date! - # These versions appear incompatible with pew: + # These versions appear incompatible with virtualenv? # '2.5': '2.5.6', '2.6': '2.6.9', '2.7': '2.7.15', @@ -892,28 +886,7 @@ def do_create_virtualenv(python=None, site_packages=False): click.echo(u'Pipfile: {0}'.format( crayons.red(project.pipfile_location, bold=True), ), err=True) - # The user wants the virtualenv in the project. - if project.is_venv_in_project(): - cmd = [ - sys.executable, '-m', 'virtualenv', - project.virtualenv_location, - '--prompt=({0})'.format(project.name), - ] - # Pass site-packages flag to virtualenv, if desired... - if site_packages: - cmd.append('--system-site-packages') - else: - # Default: use pew. - cmd = [ - sys.executable, - '-m', - 'pipenv.pew', - 'new', - project.virtualenv_name, - '-d', - '-a', - project.project_directory, - ] + # Default to using sys.executable, if Python wasn't provided. if not python: python = sys.executable @@ -926,7 +899,17 @@ def do_create_virtualenv(python=None, site_packages=False): ), err=True, ) - cmd = cmd + ['-p', python] + + cmd = [ + sys.executable, '-m', 'virtualenv', + project.virtualenv_location, + '--prompt', '({0})'.format(project.name), + '--python', python, + ] + # Pass site-packages flag to virtualenv, if desired... + if site_packages: + cmd.append('--system-site-packages') + # Actually create the virtualenv. with spinner(): try: @@ -944,15 +927,13 @@ def do_create_virtualenv(python=None, site_packages=False): ) sys.exit(1) click.echo(crayons.blue(c.out), err=True) - # Enable site-packages, if desired... - if not project.is_venv_in_project() and site_packages: - click.echo( - crayons.normal(u'Making site-packages available…', bold=True), - err=True, - ) - os.environ['VIRTUAL_ENV'] = project.virtualenv_location - delegator.run('pipenv run pewtwo toggleglobalsitepackages') - del os.environ['VIRTUAL_ENV'] + + # Associate the project to the virtual environment. + # We write with binary so the write never fails, and is guarenteed to be + # in the platform's native filesystem encoding. + with Path(project.virtualenv_location, '.project').open('wb') as f: + f.write(str(Path(project.project_directory).resolve()).encode()) + # Say where the virtualenv is. do_where(virtualenv=True, bare=False) @@ -2151,93 +2132,41 @@ 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) - click.echo( - crayons.normal( - 'Spawning environment shell ({0}). Use {1} to leave.'.format( - crayons.red(shell), crayons.normal("'exit'", bold=True) - ), - bold=True, - ), - 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() + fancy = True + + from .shelltools import choose_shell, CannotGuessShell 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 = choose_shell(PIPENV_SHELL) + except CannotGuessShell: + 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) + + click.echo(crayons.normal( + 'Spawning environment shell ({0}). Use {1} to leave.'.format( + crayons.red(shell.cmd), crayons.normal("'exit'", bold=True) + ), + bold=True, + ), err=True) + + fork_args = ( + project.virtualenv_location, + project.project_directory, + shell_args, + ) + if fancy: + shell.fork(*fork_args) + else: + shell.fork_compat(*fork_args, activate_virtualenv()) def inline_activate_virtualenv(): diff --git a/pipenv/environments.py b/pipenv/environments.py index 3837363df2..f9b00c4dfe 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -7,11 +7,15 @@ # Prevent invalid shebangs with Homebrew-installed Python: # https://bugs.python.org/issue22490 os.environ.pop('__PYVENV_LAUNCHER__', None) +# Where to put virtual environments. +WORKON_HOME = os.path.expanduser(os.environ.get( + 'WORKON_HOME', '~/.virtualenvs', +)) # Shell compatibility mode, for mis-configured shells. PIPENV_SHELL_FANCY = bool(os.environ.get('PIPENV_SHELL_FANCY')) # Support for both Python 2 and Python 3 at the same time. PIPENV_PYTHON = os.environ.get('PIPENV_PYTHON') -# Create the virtualenv in the project, instead of with pew. +# Create the virtualenv in the project, instead of inside WORKON_HOME. PIPENV_VENV_IN_PROJECT = bool( os.environ.get('PIPENV_VENV_IN_PROJECT') ) diff --git a/pipenv/patched/patched.txt b/pipenv/patched/patched.txt index 075f608b48..f8bdb9e102 100644 --- a/pipenv/patched/patched.txt +++ b/pipenv/patched/patched.txt @@ -1,7 +1,6 @@ safety git+https://github.com/jumpscale7/python-consistent-toml.git#egg=contoml crayons==0.1.2 -git+https://github.com/berdario/pew.git@1.1.5#egg=pew pipfile==0.0.2 git+https://github.com/jazzband/pip-tools.git@9cb41d828fcb0967a32cc140c1dcaca94e5f4daa#egg=piptools prettytoml==0.3 diff --git a/pipenv/patched/pew/LICENSE b/pipenv/patched/pew/LICENSE deleted file mode 100644 index 89de354795..0000000000 --- a/pipenv/patched/pew/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/pipenv/patched/pew/__init__.py b/pipenv/patched/pew/__init__.py deleted file mode 100644 index 90fe7cfdaf..0000000000 --- a/pipenv/patched/pew/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import absolute_import - -from . import pew -__all__ = ['pew'] diff --git a/pipenv/patched/pew/__main__.py b/pipenv/patched/pew/__main__.py deleted file mode 100644 index 2267b8da63..0000000000 --- a/pipenv/patched/pew/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import pew - -if __name__ == '__main__': - pew.pew.pew() diff --git a/pipenv/patched/pew/_print_utils.py b/pipenv/patched/pew/_print_utils.py deleted file mode 100644 index 611ccfc79c..0000000000 --- a/pipenv/patched/pew/_print_utils.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import division, print_function - -import os -from functools import partial -from math import ceil -try: - from itertools import zip_longest -except ImportError: - from itertools import izip_longest as zip_longest -try: - from shutil import get_terminal_size -except ImportError: - from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size - -SEP = ' ' -L = len(SEP) - - -def get_rows(venvs, columns_number): - lines_number = int(ceil(len(venvs) / columns_number)) - for i in range(lines_number): - yield venvs[i::lines_number] - - -def row_len(names): - return sum(map(len, names)) + L * len(names) - L - - -def get_best_columns_number(venvs): - max_width, _ = get_terminal_size() - longest = partial(max, key=len) - columns_number = 1 - for columns_number in range(1, len(venvs) + 1): - rows = get_rows(venvs, columns_number) - longest_row = list(map(longest, zip_longest(*rows, fillvalue=''))) - if row_len(longest_row) > max_width: - return (columns_number - 1) or 1 - else: - return columns_number - - -def align_column(column): - m = max(map(len, column)) - return [name.ljust(m) for name in column] - - -def columnize(venvs): - columns_n = get_best_columns_number(venvs) - columns = map(align_column, zip_longest(*get_rows(venvs, columns_n), fillvalue='')) - return map(SEP.join, zip(*columns)) - - -def print_virtualenvs(*venvs): - venvs = sorted(venvs) - if os.isatty(1): - print(*columnize(venvs), sep='\n') - else: - print(*venvs, sep=' ') diff --git a/pipenv/patched/pew/_utils.py b/pipenv/patched/pew/_utils.py deleted file mode 100644 index a45c227fdf..0000000000 --- a/pipenv/patched/pew/_utils.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import sys -import locale -from codecs import getwriter -from contextlib import contextmanager -from subprocess import check_call, Popen, PIPE -from collections import namedtuple -from functools import partial, wraps -try: - from pathlib import Path -except ImportError: - from pipenv.vendor.pathlib2 import Path -from tempfile import NamedTemporaryFile as _ntf -try: - from shutil import which -except ImportError: - from shutilwhich import which - -py2 = sys.version_info[0] == 2 -windows = sys.platform == 'win32' - -if py2 or windows: - locale.setlocale(locale.LC_CTYPE, '') - -encoding = locale.getlocale()[1] or 'ascii' - -if py2: - @wraps(_ntf) - def NamedTemporaryFile(mode): - return getwriter(encoding)(_ntf(mode)) - - def to_unicode(x): - return x.decode(encoding) -else: - NamedTemporaryFile = _ntf - to_unicode = str - -def check_path(): - parent = os.path.dirname - return parent(parent(which('python'))) == os.environ['VIRTUAL_ENV'] - - -def resolve_path(f): - def call(cmd, **kwargs): - ex = cmd[0] - ex = which(ex) or ex - return f([ex] + list(cmd[1:]), **kwargs) # list-conversion is required in case `cmd` is a tuple - return call - -if windows: - check_call = resolve_path(check_call) - Popen = resolve_path(Popen) - -Result = namedtuple('Result', 'returncode out err') - - -# TODO: it's better to fail early, and thus I'd need to check the exit code, but it'll -# need a refactoring of a couple of tests -def invoke(*args, **kwargs): - inp = kwargs.pop('inp', '').encode(encoding) - popen = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs) - out, err = [o.strip().decode(encoding) for o in popen.communicate(inp)] - return Result(popen.returncode, out, err) - - -invoke_pew = partial(invoke, 'pew') - -env_bin_dir = 'bin' if sys.platform != 'win32' else 'Scripts' - - -def expandpath(path): - return Path(os.path.expanduser(os.path.expandvars(path))) - - -def own(path): - if sys.platform == 'win32': - # Even if run by an administrator, the permissions will be set - # correctly on Windows, no need to check - return True - while not path.exists(): - path = path.parent - return path.stat().st_uid == os.getuid() - - -@contextmanager -def temp_environ(): - environ = dict(os.environ) - try: - yield - finally: - os.environ.clear() - os.environ.update(environ) diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py deleted file mode 100644 index 038ad886bb..0000000000 --- a/pipenv/patched/pew/pew.py +++ /dev/null @@ -1,786 +0,0 @@ -from __future__ import print_function, absolute_import, unicode_literals - -import os -import sys -import argparse -import shutil -import random -import textwrap -from functools import partial -from subprocess import CalledProcessError -try: - from pathlib import Path -except ImportError: - from pipenv.vendor.pathlib2 import Path - -try: - from shutil import get_terminal_size -except ImportError: - from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size - -windows = sys.platform == 'win32' - -from clonevirtualenv import clone_virtualenv -if not windows: - try: - # Try importing these packages if avaiable - from pythonz.commands.install import InstallCommand - from pythonz.commands.uninstall import UninstallCommand - from pythonz.installer.pythoninstaller import PythonInstaller, AlreadyInstalledError - from pythonz.commands.list import ListCommand as ListPythons - from pythonz.define import PATH_PYTHONS - from pythonz.commands.locate import LocateCommand as LocatePython - except: - # create mock commands - InstallCommand = ListPythons = LocatePython = UninstallCommand = \ - lambda : sys.exit('You need to install the pythonz extra. pip install pew[pythonz]') -else: - # Pythonz does not support windows - InstallCommand = ListPythons = LocatePython = UninstallCommand = \ - lambda : sys.exit('Command not supported on this platform') - - from ._win_utils import get_shell - -from pew._utils import (check_call, invoke, expandpath, own, env_bin_dir, - check_path, temp_environ, NamedTemporaryFile, to_unicode) -from pew._print_utils import print_virtualenvs - -if sys.version_info[0] == 2: - input = raw_input - -err = partial(print, file=sys.stderr) - -if windows: - default_home = '~/.virtualenvs' -else: - default_home = os.path.join( - os.environ.get('XDG_DATA_HOME', '~/.local/share'), 'virtualenvs') - -def get_workon_home(): - return expandpath(os.environ.get('WORKON_HOME', default_home)) - - -def makedirs_and_symlink_if_needed(workon_home): - if not workon_home.exists() and own(workon_home): - workon_home.mkdir(parents=True) - link = expandpath('~/.virtualenvs') - if os.name == 'posix' and 'WORKON_HOME' not in os.environ and \ - 'XDG_DATA_HOME' not in os.environ and not link.exists(): - link.symlink_to(str(workon_home)) - return True - else: - return False - -pew_site = Path(__file__).parent - -def supported_shell(): - shell = Path(os.environ.get('SHELL', '')).stem - if shell in ('bash', 'zsh', 'fish'): - return shell - - -def shell_config_cmd(argv): - "Prints the path for the current $SHELL helper file" - shell = supported_shell() - if shell: - print(pew_site / 'shell_config' / ('init.' + shell)) - else: - err('Completions and prompts are unavailable for %s' % - repr(os.environ.get('SHELL', ''))) - - -def deploy_completions(): - completions = {'complete.bash': Path('/etc/bash_completion.d/pew'), - 'complete.zsh': Path('/usr/local/share/zsh/site-functions/_pew'), - 'complete.fish': Path('/etc/fish/completions/pew.fish')} - for comp, dest in completions.items(): - if not dest.parent.exists(): - dest.parent.mkdir(parents=True) - shutil.copy(str(pew_site / 'shell_config' / comp), str(dest)) - - -def get_project_dir(env): - project_file = get_workon_home() / env / '.project' - if project_file.exists(): - with project_file.open() as f: - project_dir = f.readline().strip() - if os.path.exists(project_dir): - return project_dir - else: - err('Corrupted or outdated:', project_file, '\nDirectory', - project_dir, "doesn't exist.") - - -def unsetenv(key): - if key in os.environ: - del os.environ[key] - - -def compute_path(env): - envdir = get_workon_home() / env - return os.pathsep.join([ - str(envdir / env_bin_dir), - os.environ['PATH'], - ]) - - -def inve(env, command, *args, **kwargs): - """Run a command in the given virtual environment. - - Pass additional keyword arguments to ``subprocess.check_call()``.""" - # we don't strictly need to restore the environment, since pew runs in - # its own process, but it feels like the right thing to do - with temp_environ(): - os.environ['VIRTUAL_ENV'] = str(get_workon_home() / env) - os.environ['PATH'] = compute_path(env) - - unsetenv('PYTHONHOME') - unsetenv('__PYVENV_LAUNCHER__') - - try: - return check_call([command] + list(args), shell=windows, **kwargs) - # need to have shell=True on windows, otherwise the PYTHONPATH - # won't inherit the PATH - except OSError as e: - if e.errno == 2: - err('Unable to find', command) - else: - raise - - -def fork_shell(env, shellcmd, cwd): - or_ctrld = '' if windows else "or 'Ctrl+D' " - err("Launching subshell in virtual environment. Type 'exit' ", or_ctrld, - "to return.", sep='') - if 'VIRTUAL_ENV' in os.environ: - err("Be aware that this environment will be nested on top " - "of '%s'" % Path(os.environ['VIRTUAL_ENV']).name) - try: - inve(env, *shellcmd, cwd=cwd) - except CalledProcessError: - # These shells report errors when the last command executed in the - # subshell in an error. This causes the subprocess to fail, which is - # not what we want. Stay silent for them, there's nothing we can do. - shell_name, _ = os.path.splitext(os.path.basename(shellcmd[0])) - suppress_error = shell_name.lower() in ('cmd', 'powershell', 'pwsh') - if not suppress_error: - raise - - -def fork_bash(env, cwd): - # bash is a special little snowflake, and prevent_path_errors cannot work there - # https://github.com/berdario/pew/issues/58#issuecomment-102182346 - bashrcpath = expandpath('~/.bashrc') - if bashrcpath.exists(): - with NamedTemporaryFile('w+') as rcfile: - with bashrcpath.open() as bashrc: - rcfile.write(bashrc.read()) - rcfile.write('\nexport PATH="' + to_unicode(compute_path(env)) + '"') - rcfile.flush() - fork_shell(env, ['bash', '--rcfile', rcfile.name], cwd) - else: - fork_shell(env, ['bash'], cwd) - - -def fork_cmder(env, cwd): - shell_cmd = ['cmd'] - escaped_cmder_root = os.environ['CMDER_ROOT'].replace(' ', '^ ') - cmderrc_path = r'{0}\vendor\init.bat'.format(escaped_cmder_root) - if expandpath(cmderrc_path).exists(): - shell_cmd += ['/k', cmderrc_path] - if cwd: - os.environ['CMDER_START'] = cwd - fork_shell(env, shell_cmd, cwd) - -def _detect_shell(): - shell = os.environ.get('SHELL', None) - if not shell: - if 'CMDER_ROOT' in os.environ: - shell = 'Cmder' - elif windows: - shell = get_shell(os.getpid()) - else: - shell = 'sh' - return shell - -def shell(env, cwd=None): - env = str(env) - shell = _detect_shell() - shell_name = Path(shell).stem - if shell_name not in ('Cmder', 'bash', 'elvish', 'powershell', 'pwsh', 'klingon', 'cmd'): - # On Windows the PATH is usually set with System Utility - # so we won't worry about trying to check mistakes there - shell_check = (sys.executable + ' -c "from pipenv.patched.pew.pew import ' - 'prevent_path_errors; prevent_path_errors()"') - try: - inve(env, shell, '-c', shell_check) - except CalledProcessError: - return - if shell_name in ('Cmder', 'cmd'): - os.environ['PROMPT'] = '({0}) {1}'.format(env, os.environ['PROMPT']) - if shell_name == 'bash': - fork_bash(env, cwd) - elif shell_name == 'Cmder': - fork_cmder(env, cwd) - else: - fork_shell(env, [shell], cwd) - - -def mkvirtualenv(envname, python=None, packages=[], project=None, - requirements=None, rest=[]): - - if python: - rest = ["--python=%s" % python] + rest - - path = (get_workon_home() / envname).absolute() - - try: - check_call([sys.executable, "-m", "virtualenv", str(path)] + rest) - except (CalledProcessError, KeyboardInterrupt): - rmvirtualenvs([envname]) - raise - else: - if project: - setvirtualenvproject(envname, project.absolute()) - if requirements: - inve(envname, 'pip', 'install', '-r', str(expandpath(requirements))) - if packages: - inve(envname, 'pip', 'install', *packages) - - -def mkvirtualenv_argparser(): - parser = argparse.ArgumentParser() - parser.add_argument('-p', '--python') - parser.add_argument('-i', action='append', dest='packages', help='Install \ -a package after the environment is created. This option may be repeated.') - parser.add_argument('-r', dest='requirements', help='Provide a pip \ -requirements file to install a base set of packages into the new environment.') - parser.add_argument('-d', '--dont-activate', action='store_false', - default=True, dest='activate', help="After \ - creation, continue with the existing shell (don't \ - activate the new environment).") - return parser - - -def new_cmd(argv): - """Create a new environment, in $WORKON_HOME.""" - parser = mkvirtualenv_argparser() - parser.add_argument('-a', dest='project', help='Provide a full path to a \ -project directory to associate with the new environment.') - - parser.add_argument('envname') - args, rest = parser.parse_known_args(argv) - project = expandpath(args.project) if args.project else None - - mkvirtualenv(args.envname, args.python, args.packages, project, - args.requirements, rest) - if args.activate: - shell(args.envname) - - -def rmvirtualenvs(envs): - error_happened = False - for env in envs: - env = get_workon_home() / env - if os.environ.get('VIRTUAL_ENV') == str(env): - err("ERROR: You cannot remove the active environment (%s)." % env) - error_happened = True - break - try: - shutil.rmtree(str(env)) - except OSError as e: - err("Error while trying to remove the {0} env: \n{1}".format - (env, e.strerror)) - error_happened = True - return error_happened - - - -def rm_cmd(argv): - """Remove one or more environment, from $WORKON_HOME.""" - if len(argv) < 1: - sys.exit("Please specify an environment") - return rmvirtualenvs(argv) - - -def packages(site_packages): - nodes = site_packages.iterdir() - return set([x.stem.split('-')[0] for x in nodes]) - set(['__pycache__']) - - -def showvirtualenv(env): - columns, _ = get_terminal_size() - pkgs = sorted(packages(sitepackages_dir(env))) - env_python = get_workon_home() / env / env_bin_dir / 'python' - l = len(env) + 2 - version = invoke(str(env_python), '-V') - version = ' - '.join((version.out + version.err).splitlines()) - print(env, ': ', version, sep='') - print(textwrap.fill(' '.join(pkgs), - width=columns-l, - initial_indent=(l * ' '), - subsequent_indent=(l * ' ')), '\n') - - -def show_cmd(argv): - try: - showvirtualenv(argv[0]) - except IndexError: - if 'VIRTUAL_ENV' in os.environ: - showvirtualenv(Path(os.environ['VIRTUAL_ENV']).name) - else: - sys.exit('pew show [env]') - - -def lsenvs(): - items = get_workon_home().glob(os.path.join('*', env_bin_dir, 'python*')) - return sorted(set(env.parts[-3] for env in items)) - - -def lsvirtualenv(verbose): - envs = lsenvs() - - if not verbose: - print_virtualenvs(*envs) - else: - for env in envs: - showvirtualenv(env) - - -def ls_cmd(argv): - """List available environments.""" - parser = argparse.ArgumentParser() - p_group = parser.add_mutually_exclusive_group() - p_group.add_argument('-b', '--brief', action='store_false') - p_group.add_argument('-l', '--long', action='store_true') - args = parser.parse_args(argv) - lsvirtualenv(args.long) - -def parse_envname(argv, no_arg_callback): - if len(argv) < 1: - no_arg_callback() - - env = argv[0] - if env.startswith('/'): - sys.exit("ERROR: Invalid environment name '{0}'.".format(env)) - if not (get_workon_home() / env).exists(): - sys.exit("ERROR: Environment '{0}' does not exist. Create it with \ -'pew new {0}'.".format(env)) - else: - return env - -def workon_cmd(argv): - """List or change working virtual environments.""" - - def list_and_exit(): - lsvirtualenv(False) - sys.exit(0) - - env = parse_envname(argv, list_and_exit) - - # Check if the virtualenv has an associated project directory and in - # this case, use it as the current working directory. - project_dir = get_project_dir(env) or os.getcwd() - shell(env, cwd=project_dir) - - -def sitepackages_dir(env=os.environ.get('VIRTUAL_ENV')): - if not env: - sys.exit('ERROR: no virtualenv active') - else: - env_python = get_workon_home() / env / env_bin_dir / 'python' - return Path(invoke(str(env_python), '-c', 'import distutils; \ -print(distutils.sysconfig.get_python_lib())').out) - - -def add_cmd(argv): - """Add the specified directories to the Python path for the currently active virtualenv. - -This will be done by placing the directory names in a path file named -"virtualenv_path_extensions.pth" inside the virtualenv's site-packages -directory; if this file does not exists, it will be created first. - -""" - parser = argparse.ArgumentParser() - parser.add_argument('-d', dest='remove', action='store_true') - parser.add_argument('dirs', nargs='+') - args = parser.parse_args(argv) - - extra_paths = sitepackages_dir() / '_virtualenv_path_extensions.pth' - new_paths = [os.path.abspath(d) + "\n" for d in args.dirs] - if not extra_paths.exists(): - with extra_paths.open('w') as extra: - extra.write('''import sys; sys.__plen = len(sys.path) -import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new) - ''') - - def rewrite(f): - with extra_paths.open('r+') as extra: - to_write = f(extra.readlines()) - extra.seek(0) - extra.truncate() - extra.writelines(to_write) - - if args.remove: - rewrite(lambda ls: [line for line in ls if line not in new_paths]) - else: - rewrite(lambda lines: lines[0:1] + new_paths + lines[1:]) - - -def sitepackages_dir_cmd(argv): - print(sitepackages_dir()) - - -def lssitepackages_cmd(argv): - """Show the content of the site-packages directory of the current virtualenv.""" - site = sitepackages_dir() - print(*sorted(site.iterdir()), sep=os.linesep) - extra_paths = site / '_virtualenv_path_extensions.pth' - if extra_paths.exists(): - print('from _virtualenv_path_extensions.pth:') - with extra_paths.open() as extra: - print(''.join(extra.readlines())) - - -def toggleglobalsitepackages_cmd(argv): - """Toggle the current virtualenv between having and not having access to the global site-packages.""" - quiet = argv == ['-q'] - site = sitepackages_dir() - ngsp_file = site.parent / 'no-global-site-packages.txt' - if ngsp_file.exists(): - ngsp_file.unlink() - if not quiet: - print('Enabled global site-packages') - else: - with ngsp_file.open('w'): - if not quiet: - print('Disabled global site-packages') - - -def cp_cmd(argv): - """Duplicate the named virtualenv to make a new one.""" - parser = argparse.ArgumentParser() - parser.add_argument('source') - parser.add_argument('target', nargs='?') - parser.add_argument('-d', '--dont-activate', action='store_false', - default=True, dest='activate', help="After \ - creation, continue with the existing shell (don't \ - activate the new environment).") - - args = parser.parse_args(argv) - target_name = copy_virtualenv_project(args.source, args.target) - if args.activate: - shell(target_name) - - -def copy_virtualenv_project(source, target): - source = expandpath(source) - workon_home = get_workon_home() - if not source.exists(): - source = workon_home / source - if not source.exists(): - sys.exit('Please provide a valid virtualenv to copy') - - target_name = target or source.name - - target = workon_home / target_name - - if target.exists(): - sys.exit('%s virtualenv already exists in %s.' % ( - target_name, workon_home - )) - - print('Copying {0} in {1}'.format(source, target_name)) - clone_virtualenv(str(source), str(target)) - return target_name - - -def rename_cmd(argv): - """Rename a virtualenv""" - parser = argparse.ArgumentParser() - parser.add_argument('source') - parser.add_argument('target') - pargs = parser.parse_args(argv) - copy_virtualenv_project(pargs.source, pargs.target) - return rmvirtualenvs([pargs.source]) - - -def setvirtualenvproject(env, project): - print('Setting project for {0} to {1}'.format(env, project)) - with (get_workon_home() / env / '.project').open('wb') as prj: - prj.write(str(project).encode()) - - -def setproject_cmd(argv): - """Given a virtualenv directory and a project directory, set the - virtualenv up to be associated with the project.""" - args = dict(enumerate(argv)) - project = os.path.abspath(args.get(1, '.')) - env = args.get(0, os.environ.get('VIRTUAL_ENV')) - if not env: - sys.exit('pew setproject [virtualenv] [project_path]') - if not (get_workon_home() / env).exists(): - sys.exit("Environment '%s' doesn't exist." % env) - if not os.path.isdir(project): - sys.exit('pew setproject: %s does not exist' % project) - setvirtualenvproject(env, project) - - -def mkproject_cmd(argv): - """Create a new project directory and its associated virtualenv.""" - if '-l' in argv or '--list' in argv: - templates = [t.name[9:] for t in get_workon_home().glob("template_*")] - print("Available project templates:", *templates, sep='\n') - return - - parser = mkvirtualenv_argparser() - parser.add_argument('envname') - parser.add_argument( - '-t', action='append', default=[], dest='templates', help='Multiple \ -templates may be selected. They are applied in the order specified on the \ -command line.') - parser.add_argument( - '-l', '--list', action='store_true', help='List available templates.') - - args, rest = parser.parse_known_args(argv) - - projects_home = Path(os.environ.get('PROJECT_HOME', '.')) - if not projects_home.exists(): - sys.exit('ERROR: Projects directory %s does not exist. \ -Create it or set PROJECT_HOME to an existing directory.' % projects_home) - - project = (projects_home / args.envname).absolute() - if project.exists(): - sys.exit('Project %s already exists.' % args.envname) - - mkvirtualenv(args.envname, args.python, args.packages, project.absolute(), - args.requirements, rest) - - project.mkdir() - - for template_name in args.templates: - template = get_workon_home() / ("template_" + template_name) - inve(args.envname, str(template), args.envname, str(project)) - if args.activate: - shell(args.envname, cwd=str(project)) - - -def mktmpenv_cmd(argv): - """Create a temporary virtualenv.""" - parser = mkvirtualenv_argparser() - env = '.' - while (get_workon_home() / env).exists(): - env = hex(random.getrandbits(64))[2:-1] - - args, rest = parser.parse_known_args(argv) - - mkvirtualenv(env, args.python, args.packages, requirements=args.requirements, - rest=rest) - print('This is a temporary environment. It will be deleted when you exit') - try: - if args.activate: - # only used for testing on windows - shell(env) - finally: - return rmvirtualenvs([env]) - - -def wipeenv_cmd(argv): - """Remove all installed packages from the current (or supplied) env.""" - env = argv[0] if argv else os.environ.get('VIRTUAL_ENV') - - if not env: - sys.exit('ERROR: no virtualenv active') - elif not (get_workon_home() / env).exists(): - sys.exit("ERROR: Environment '{0}' does not exist.".format(env)) - else: - env_pip = str(get_workon_home() / env / env_bin_dir / 'pip') - all_pkgs = set(invoke(env_pip, 'freeze').out.splitlines()) - pkgs = set(p for p in all_pkgs if len(p.split("==")) == 2) - ignored = sorted(all_pkgs - pkgs) - pkgs = set(p.split("==")[0] for p in pkgs) - to_remove = sorted(pkgs - set(['distribute', 'wsgiref'])) - if to_remove: - print("Ignoring:\n %s" % "\n ".join(ignored)) - print("Uninstalling packages:\n %s" % "\n ".join(to_remove)) - inve(env, 'pip', 'uninstall', '-y', *to_remove) - else: - print("Nothing to remove") - - -def inall_cmd(argv): - """Run a command in each virtualenv.""" - envs = lsenvs() - errors = False - for env in envs: - print("\n%s:" % env) - try: - inve(env, *argv) - except CalledProcessError as e: - errors = True - err(e) - sys.exit(errors) - - -def in_cmd(argv): - """Run a command in the given virtualenv.""" - - if len(argv) == 1: - return workon_cmd(argv) - - parse_envname(argv, lambda : sys.exit('You must provide a valid virtualenv to target')) - - inve(*argv) - - -def restore_cmd(argv): - """Try to restore a broken virtualenv by reinstalling the same python version on top of it""" - - if len(argv) < 1: - sys.exit('You must provide a valid virtualenv to target') - - env = argv[0] - path = get_workon_home() / env - path = workon_home / env - py = path / env_bin_dir / ('python.exe' if windows else 'python') - exact_py = py.resolve().name - - check_call([sys.executable, "-m", "virtualenv", str(path.absolute()), "--python=%s" % exact_py]) - - -def dir_cmd(argv): - """Print the path for the virtualenv directory""" - env = parse_envname(argv, lambda : sys.exit('You must provide a valid virtualenv to target')) - print(get_workon_home() / env) - - -def install_cmd(argv): - '''Use Pythonz to download and build the specified Python version''' - installer = InstallCommand() - options, versions = installer.parser.parse_args(argv) - if len(versions) != 1: - installer.parser.print_help() - sys.exit(1) - else: - try: - actual_installer = PythonInstaller.get_installer(versions[0], options) - actual_installer.install() - except AlreadyInstalledError as e: - print(e) - - -def uninstall_cmd(argv): - '''Use Pythonz to uninstall the specified Python version''' - UninstallCommand().run(argv) - - -def list_pythons_cmd(argv): - '''List the pythons installed by Pythonz (or all the installable ones)''' - try: - Path(PATH_PYTHONS).mkdir(parents=True) - except OSError: - pass - ListPythons().run(argv) - - -def locate_python_cmd(argv): - '''Locate the path for the python version installed by Pythonz''' - LocatePython().run(argv) - - -def version_cmd(argv): - """Prints current pew version""" - import pkg_resources - - try: - __version__ = pkg_resources.get_distribution('pew').version - except pkg_resources.DistributionNotFound: - __version__ = 'unknown' - print('Setuptools has some issues here, failed to get our own package.', file=sys.stderr) - - print(__version__) - - -def prevent_path_errors(): - if 'VIRTUAL_ENV' in os.environ and not check_path(): - sys.exit('''ERROR: The virtualenv hasn't been activated correctly. -Either the env is corrupted (try running `pew restore env`), -Or an upgrade of your Python version broke your env, -Or check the contents of your $PATH. You might be adding new directories to it -from inside your shell's configuration file. -In this case, for further details please see: https://github.com/berdario/pew#the-environment-doesnt-seem-to-be-activated''') - - -def first_run_setup(): - shell = supported_shell() - if shell: - if shell == 'fish': - source_cmd = 'source (pew shell_config)' - else: - source_cmd = 'source $(pew shell_config)' - rcpath = expandpath({'bash': '~/.bashrc' - , 'zsh': '~/.zshrc' - , 'fish': '~/.config/fish/config.fish'}[shell]) - if rcpath.exists(): - update_config_file(rcpath, source_cmd) - else: - print("It seems that you're running pew for the first time\n" - "If you want source shell competions and update your prompt, " - "Add the following line to your shell config file:\n %s" % source_cmd) - print('\nWill now continue with the command:', *sys.argv[1:]) - input('[enter]') - -def update_config_file(rcpath, source_cmd): - with rcpath.open('r+') as rcfile: - if source_cmd not in (line.strip() for line in rcfile.readlines()): - choice = 'X' - while choice not in ('y', '', 'n'): - choice = input("It seems that you're running pew for the first time\n" - "do you want to modify %s to source completions and" - " update your prompt? [y/N]\n> " % rcpath).lower() - if choice == 'y': - rcfile.write('\n# added by Pew\n%s\n' % source_cmd) - print('Done') - else: - print('\nOk, if you want to do it manually, just add\n %s\nat' - ' the end of %s' % (source_cmd, rcpath)) - - -def print_commands(cmds): - longest = max(map(len, cmds)) + 3 - columns, _ = get_terminal_size() - - print('Available commands:\n') - for cmd, fun in sorted(cmds.items()): - if fun.__doc__: - print(textwrap.fill( - fun.__doc__.splitlines()[0], - columns or 1000, - initial_indent=(' {0}: '.format(cmd)).ljust(longest), - subsequent_indent=longest * ' ')) - else: - print(' ' + cmd) - - -def pew(): - first_run = makedirs_and_symlink_if_needed(get_workon_home()) - if first_run and sys.stdin.isatty(): - first_run_setup() - - cmds = dict((cmd[:-4], fun) - for cmd, fun in globals().items() if cmd.endswith('_cmd')) - if sys.argv[1:]: - if sys.argv[1] in cmds: - command = cmds[sys.argv[1]] - try: - return command(sys.argv[2:]) - except CalledProcessError as e: - return e.returncode - except KeyboardInterrupt: - pass - else: - err("ERROR: command", sys.argv[1], "does not exist.") - print_commands(cmds) - sys.exit(1) - else: - print_commands(cmds) diff --git a/pipenv/patched/pew/shell_config/_pew b/pipenv/patched/pew/shell_config/_pew deleted file mode 100644 index 623fbff82f..0000000000 --- a/pipenv/patched/pew/shell_config/_pew +++ /dev/null @@ -1,107 +0,0 @@ -#compdef pew - -_pew_list_venvs () { - local expl - local -a venvs - - venvs=(${(f)"$(_call_program venvs pew ls | tr " " "\n" 2>/dev/null)"}) - _wanted venvs expl 'virtual envs' compadd -a venvs -} - - -local curcontext="$curcontext" state line -typeset -A opt_args - -_arguments -C \ - ':subcommand:->subcommand' \ - '*::option:->option' - -case $state in - (subcommand) - local -a subcommands - subcommands=( - 'add:Add directories to python path of active virtualenv' - 'cp:Duplicate the named virtualenv to make a new one' - 'inall:Run a command in each virtualenv:command' - 'install:Use Pythonz to download and build the specified Python version' - 'list_pythons:List the pythons installed by Pythonz (or all the installable ones)' - 'locate_python:Locate the path for the python version installed by Pythonz' - 'ls:List all existing virtual environments' - 'lssitepackages:List currently active site-packages' - 'mkproject:Create environment with an associated project directory' - 'mktmpenv:Create a temporary virtualenv' - 'new:Create a new environment' - 'rename:Rename a virtualenv' - 'restore:Try to restore a broken virtualenv by reinstalling the same python version on top of it' - 'rm:Remove one or more environments' - 'setproject:Bind an existing virtualenv to an existing project directory' - 'show:Display currently active virtualenv' - 'sitepackages_dir:Location of the currently active site-packages' - 'toggleglobalsitepackages:Toggle access to global site-packages for current virtualenv' - 'wipeenv:Remove all installed packages from current env' - 'workon:Activates an existing virtual environment' - ) - _describe -t commands 'pew subcommands' subcommands - ;; - - (option) - local -a new_env_options - new_env_options=( - '-h[Show help]' - '-p[Python executable]:python:_command_names' - '*-i[Install a package after the environment is created]:package name' - '-a[Project directory to associate]:project directory:_path_files -/' - '-r[Pip requirements file]:requirements file:_files' - ) - - case "$line[1]" in - (mktmpenv) - _arguments \ - $new_env_options - ;; - (new) - _arguments \ - $new_env_options \ - '1:new env name' - ;; - (mkproject) - _arguments \ - $new_env_options \ - '*-t[Apply templates]' \ - '-l[List available templates]' \ - '1:new env name' - ;; - - (ls) - _arguments \ - '(-l --long)--long[Verbose ls]' \ - '(-b --brief)--brief[One line ls]' - ;; - (inall) - _arguments \ - '*:command' - ;; - - (show|workon|rm|wipeenv|restore) - _arguments \ - '1:virtual env:_pew_list_venvs' - ;; - (cp) - _arguments \ - '1:virtual env:_pew_list_venvs' \ - '2:new env name' - ;; - - (add) - _arguments \ - '-h[Show help]' \ - '-d[Removes previously added directories]' \ - '*: :_directories -/' - ;; - (setproject) - _arguments \ - '1:virtual env:_pew_list_venvs' \ - '*:project directory:_directories -/' - ;; - esac -esac diff --git a/pipenv/patched/pew/shell_config/complete.bash b/pipenv/patched/pew/shell_config/complete.bash deleted file mode 100644 index 21e7ba7401..0000000000 --- a/pipenv/patched/pew/shell_config/complete.bash +++ /dev/null @@ -1,46 +0,0 @@ -# Homebrew on Macs have version 1.3 of bash-completion which doesn't include -# _init_completion. This is a very minimal version of that function. -__my_init_completion() -{ - COMPREPLY=() - _get_comp_words_by_ref cur prev words cword -} - -_pew() -{ - local cur prev words cword args commands - if declare -F _init_completions >/dev/null 2>&1; then - _init_completion || return - else - __my_init_completion || return - fi - args="--help --python -i -a -r" - commands="ls add mkproject rm lssitepackages cp workon new mktmpenv setproject show wipeenv sitepackages_dir inall toggleglobalsitepackages rename restore install list_pythons locate_python" - - case $prev in - ls|show|rm|workon|cp|setproject|rename|wipeenv) - COMPREPLY=( $(compgen -W "$(pew ls)" -- ${cur}) ) - return 0 - ;; - inall) - _command_offset 2 - return 0 - ;; - mktmpenv|new) - COMPREPLY=( $(compgen -W "${args}" -- ${cur}) ) - return 0 - ;; - mkproject) - COMPREPLY=( $(compgen -W "${args} -t --list" -- ${cur}) ) - return 0 - ;; - add) - COMPREPLY=( $(compgen -W "--help -d" -- ${cur}) ) - return 0 - ;; - esac - - COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) - -} && -complete -o default -F _pew pew diff --git a/pipenv/patched/pew/shell_config/complete.fish b/pipenv/patched/pew/shell_config/complete.fish deleted file mode 100644 index af9f6d220f..0000000000 --- a/pipenv/patched/pew/shell_config/complete.fish +++ /dev/null @@ -1,116 +0,0 @@ -set pew pew - - -function __pew_needs_command - set cmd (commandline -opc) - if [ (count $cmd) -eq 1 -a $cmd[1] = "$pew" ] - return 0 - end - return 1 -end - - - -function __pew_list_envs - eval "$pew ls" | tr " " "\n" -end - -function __pew_using_command - set cmd (commandline -opc) - if test (count $cmd) -gt 1 - if test $argv[1] = $cmd[2] - return 0 - end - end - return 1 -end - - -#### new -complete -f -c $pew -n '__pew_needs_command' -a new -d 'Create a new environment' -complete -f -c $pew -n '__pew_using_command new' -s h -d 'Show help' -complete -f -c $pew -n '__pew_using_command new' -s p -l python -d 'Python executable' -complete -f -c $pew -n '__pew_using_command new' -s i -d 'Install a package after the environment is created' -complete -f -c $pew -n '__pew_using_command new' -s a -a '(__fish_complete_directories (commandline -ct))' -d 'Project directory to associate' -complete -f -c $pew -n '__pew_using_command new' -s r -d 'Pip requirements file' - - -#### workon -complete -f -c $pew -n '__pew_needs_command' -a workon -d 'Activates an existing virtual environment' -complete -f -c $pew -n '__pew_using_command workon' -a '(__pew_list_envs)' -d 'Virtual env' - -#### mktmpenv -complete -f -c $pew -n '__pew_needs_command' -a mktmpenv -d 'Create a temporary virtualenv' -complete -f -c $pew -n '__pew_using_command mktmpenv' -s h -d 'Show help' -complete -f -c $pew -n '__pew_using_command mktmpenv' -s p -l python -d 'Python executable' -complete -f -c $pew -n '__pew_using_command mktmpenv' -s i -d 'Install a package after the environment is created' -complete -f -c $pew -n '__pew_using_command mktmpenv' -s a -d 'Project directory to associate' -complete -f -c $pew -n '__pew_using_command mktmpenv' -s r -d 'Pip requirements file' - -#### ls -complete -f -c $pew -n '__pew_needs_command' -a ls -d 'List all existing virtual environments' -complete -f -c $pew -n '__pew_using_command ls' -s l -l long -d 'Verbose ls' -complete -f -c $pew -n '__pew_using_command ls' -s b -l brief -d 'One line ls' - -#### show -complete -f -c $pew -n '__pew_needs_command' -a show -d 'Show' -complete -f -c $pew -n '__pew_using_command show' -a '(__pew_list_envs)' -d 'Virtual env' - -#### rm -complete -f -c $pew -n '__pew_needs_command' -a rm -d 'Remove one or more environments' -complete -f -c $pew -n '__pew_using_command rm' -a '(__pew_list_envs)' -d 'Virtual env' - -#### cp -complete -f -c $pew -n '__pew_needs_command' -a cp -d 'Duplicate an existing virtualenv environment' - -#### sitepackages_dir -complete -f -c $pew -n '__pew_needs_command' -a sitepackages_dir -d 'Location of the currently active site-packages' - -#### lssitepackages -complete -f -c $pew -n '__pew_needs_command' -a lssitepackages -d 'List currently active site-packages' - - -#### add -complete -f -c $pew -n '__pew_needs_command' -a add -d 'Adds the specified directories' -complete -f -c $pew -n '__pew_using_command add' -s h -d 'Show help' -complete -f -c $pew -n '__pew_using_command add' -s d -d 'Removes previously added directiories' - -#### toggleglobalsitepackages -complete -f -c $pew -n '__pew_needs_command' -a toggleglobalsitepackages -d 'Active virtualenv can access global site-packages' - -#### mkproject -complete -f -c $pew -n '__pew_needs_command' -a mkproject -d 'Create a new environment with a project directory' -complete -f -c $pew -n '__pew_using_command mkproject' -s h -d 'Show help' -complete -f -c $pew -n '__pew_using_command mkproject' -s p -l python -d 'Python executable' -complete -f -c $pew -n '__pew_using_command mkproject' -s i -d 'Install a package after the environment is created' -complete -f -c $pew -n '__pew_using_command mkproject' -s a -d 'Project directory to associate' -complete -f -c $pew -n '__pew_using_command mkproject' -s r -d 'Pip requirements file' -complete -f -c $pew -n '__pew_using_command mkproject' -s t -d 'Apply templates' -complete -f -c $pew -n '__pew_using_command mkproject' -s l -l list -d 'List available templates' - - -#### setproject -complete -f -c $pew -n '__pew_needs_command' -a setproject -d 'Bind an existing virtualenv to an existing project' - -#### version -complete -f -c $pew -n '__pew_needs_command' -a version -d 'Prints current version' - -#### wipeenv -complete -f -c $pew -n '__pew_needs_command' -a wipeenv -d 'Remove all installed packages from a virtualenv' -complete -f -c $pew -n '__pew_using_command workon' -a '(__pew_list_envs)' -d 'Virtual env' - -#### restore -complete -f -c $pew -n '__pew_needs_command' -a restore -d 'Try to restore a broken virtualenv' -complete -f -c $pew -n '__pew_using_command workon' -a '(__pew_list_envs)' -d 'Virtual env' - -#### rename -complete -f -c $pew -n '__pew_needs_command' -a rename -d 'Rename a virtualenv' - -#### install -complete -f -c $pew -n '__pew_needs_command' -a install -d 'Use Pythonz to download and build a Python vm' - -#### list_pythons -complete -f -c $pew -n '__pew_needs_command' -a list_pythons -d 'List the pythons installed by Pythonz' - -#### locate_python -complete -f -c $pew -n '__pew_needs_command' -a locate_python -d 'Locate the path for the python version installed by Pythonz' diff --git a/pipenv/patched/pew/shell_config/complete.zsh b/pipenv/patched/pew/shell_config/complete.zsh deleted file mode 100644 index 623fbff82f..0000000000 --- a/pipenv/patched/pew/shell_config/complete.zsh +++ /dev/null @@ -1,107 +0,0 @@ -#compdef pew - -_pew_list_venvs () { - local expl - local -a venvs - - venvs=(${(f)"$(_call_program venvs pew ls | tr " " "\n" 2>/dev/null)"}) - _wanted venvs expl 'virtual envs' compadd -a venvs -} - - -local curcontext="$curcontext" state line -typeset -A opt_args - -_arguments -C \ - ':subcommand:->subcommand' \ - '*::option:->option' - -case $state in - (subcommand) - local -a subcommands - subcommands=( - 'add:Add directories to python path of active virtualenv' - 'cp:Duplicate the named virtualenv to make a new one' - 'inall:Run a command in each virtualenv:command' - 'install:Use Pythonz to download and build the specified Python version' - 'list_pythons:List the pythons installed by Pythonz (or all the installable ones)' - 'locate_python:Locate the path for the python version installed by Pythonz' - 'ls:List all existing virtual environments' - 'lssitepackages:List currently active site-packages' - 'mkproject:Create environment with an associated project directory' - 'mktmpenv:Create a temporary virtualenv' - 'new:Create a new environment' - 'rename:Rename a virtualenv' - 'restore:Try to restore a broken virtualenv by reinstalling the same python version on top of it' - 'rm:Remove one or more environments' - 'setproject:Bind an existing virtualenv to an existing project directory' - 'show:Display currently active virtualenv' - 'sitepackages_dir:Location of the currently active site-packages' - 'toggleglobalsitepackages:Toggle access to global site-packages for current virtualenv' - 'wipeenv:Remove all installed packages from current env' - 'workon:Activates an existing virtual environment' - ) - _describe -t commands 'pew subcommands' subcommands - ;; - - (option) - local -a new_env_options - new_env_options=( - '-h[Show help]' - '-p[Python executable]:python:_command_names' - '*-i[Install a package after the environment is created]:package name' - '-a[Project directory to associate]:project directory:_path_files -/' - '-r[Pip requirements file]:requirements file:_files' - ) - - case "$line[1]" in - (mktmpenv) - _arguments \ - $new_env_options - ;; - (new) - _arguments \ - $new_env_options \ - '1:new env name' - ;; - (mkproject) - _arguments \ - $new_env_options \ - '*-t[Apply templates]' \ - '-l[List available templates]' \ - '1:new env name' - ;; - - (ls) - _arguments \ - '(-l --long)--long[Verbose ls]' \ - '(-b --brief)--brief[One line ls]' - ;; - (inall) - _arguments \ - '*:command' - ;; - - (show|workon|rm|wipeenv|restore) - _arguments \ - '1:virtual env:_pew_list_venvs' - ;; - (cp) - _arguments \ - '1:virtual env:_pew_list_venvs' \ - '2:new env name' - ;; - - (add) - _arguments \ - '-h[Show help]' \ - '-d[Removes previously added directories]' \ - '*: :_directories -/' - ;; - (setproject) - _arguments \ - '1:virtual env:_pew_list_venvs' \ - '*:project directory:_directories -/' - ;; - esac -esac diff --git a/pipenv/patched/pew/shell_config/complete_deploy b/pipenv/patched/pew/shell_config/complete_deploy deleted file mode 100755 index 522f85723b..0000000000 --- a/pipenv/patched/pew/shell_config/complete_deploy +++ /dev/null @@ -1,4 +0,0 @@ -#! /usr/bin/env python -from pew.pew import deploy_completions -if __name__ == '__main__': - deploy_completions() diff --git a/pipenv/patched/pew/shell_config/init.bash b/pipenv/patched/pew/shell_config/init.bash deleted file mode 100644 index 07e8045ace..0000000000 --- a/pipenv/patched/pew/shell_config/init.bash +++ /dev/null @@ -1,3 +0,0 @@ -source "$( dirname "${BASH_SOURCE[0]}" )"/complete.bash - -[[ -z "${VIRTUAL_ENV}" ]] || PS1="\[\033[01;34m\]\$(basename '$VIRTUAL_ENV')\[\e[0m\] $PS1" diff --git a/pipenv/patched/pew/shell_config/init.fish b/pipenv/patched/pew/shell_config/init.fish deleted file mode 100644 index aebc56e977..0000000000 --- a/pipenv/patched/pew/shell_config/init.fish +++ /dev/null @@ -1,9 +0,0 @@ -. (dirname (status --current-filename))/complete.fish - -function pew_prompt - if [ -n "$VIRTUAL_ENV" ] - echo -n (set_color --bold -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)" " - end -end - -# Remember to use (pew_prompt) inside fish_prompt if you want your prompt to display the active environment diff --git a/pipenv/patched/pew/shell_config/init.zsh b/pipenv/patched/pew/shell_config/init.zsh deleted file mode 100644 index 93a63325c0..0000000000 --- a/pipenv/patched/pew/shell_config/init.zsh +++ /dev/null @@ -1,12 +0,0 @@ -fpath=( ${0:a:h} "${fpath[@]}" ) -compinit - -function virtualenv_prompt_info() { - if [ -n "$VIRTUAL_ENV" ]; then - local name=$(basename $VIRTUAL_ENV) - echo "($name) " - fi -} -PS1="$(virtualenv_prompt_info)$PS1" - -# be sure to disable promptinit if the prompt is not updated diff --git a/pipenv/patched/pew/template_django b/pipenv/patched/pew/template_django deleted file mode 100644 index 4fec88efc0..0000000000 --- a/pipenv/patched/pew/template_django +++ /dev/null @@ -1,6 +0,0 @@ -#! /bin/sh -# put this inside $WORKON_HOME -project=$1 -project_dir=$2 -pip install django -django-admin.py startproject "$project" \ No newline at end of file diff --git a/pipenv/patched/piptools/utils.py b/pipenv/patched/piptools/utils.py index 1d732bf90e..c8f11e3159 100644 --- a/pipenv/patched/piptools/utils.py +++ b/pipenv/patched/piptools/utils.py @@ -274,7 +274,7 @@ def fs_str(string): _fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -# Borrowed from pew to avoid importing pew which imports psutil +# Borrowed from pew. # See https://github.com/berdario/pew/blob/master/pew/_utils.py#L82 @contextmanager def temp_environ(): diff --git a/pipenv/pew/__init__.py b/pipenv/pew/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pipenv/pew/__main__.py b/pipenv/pew/__main__.py deleted file mode 100644 index fc6ceddb1d..0000000000 --- a/pipenv/pew/__main__.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -import sys - -PIPENV_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PIPENV_VENDOR = os.sep.join([PIPENV_ROOT, 'vendor']) -PIPENV_PATCHED = os.sep.join([PIPENV_ROOT, 'patched']) - -import pew - -if __name__ == '__main__': - sys.path.insert(0, PIPENV_VENDOR) - sys.path.insert(0, PIPENV_PATCHED) - pew.pew.pew() diff --git a/pipenv/pipenv.1 b/pipenv/pipenv.1 index 10fc911f65..327bc38735 100644 --- a/pipenv/pipenv.1 +++ b/pipenv/pipenv.1 @@ -326,7 +326,6 @@ Then, simply run: .sp .nf .ft C -$ pipsi install pew $ pipsi install pipenv .ft P .fi diff --git a/pipenv/project.py b/pipenv/project.py index bf86b10c97..4c998e48bd 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -38,6 +38,7 @@ is_star, ) from .environments import ( + WORKON_HOME, PIPENV_MAX_DEPTH, PIPENV_PIPFILE, PIPENV_VENV_IN_PROJECT, @@ -237,11 +238,10 @@ def virtualenv_exists(self): @classmethod def _get_virtualenv_location(cls, name): - from .patched.pew.pew import get_workon_home - venv = get_workon_home() / name - if not venv.exists(): + venv = os.path.join(WORKON_HOME, name) + if not os.path.exists(venv): return '' - return '{0}'.format(venv) + return venv @classmethod def _sanitize(cls, name): @@ -281,15 +281,16 @@ def get_name(name, location): return clean_name, encoded_hash # Check for different capitalization of the same project. - from .patched.pew.pew import lsenvs - for env in lsenvs(): + for path in Path(WORKON_HOME).iterdir(): + if not path.is_dir(): + continue try: - env_name, hash_ = env.rsplit('-', 1) + pname, hash_ = path.name.rsplit('-', 1) except ValueError: continue - if len(hash_) != 8 or env_name.lower() != name.lower(): + if len(hash_) != 8 or pname.lower() != name.lower(): continue - return get_name(env_name, self.pipfile_location.replace(name, env_name)) + return get_name(pname, self.pipfile_location.replace(name, pname)) # Use the default if no matching env exists. return clean_name, encoded_hash diff --git a/pipenv/shelltools/__init__.py b/pipenv/shelltools/__init__.py new file mode 100644 index 0000000000..0c7cc7875c --- /dev/null +++ b/pipenv/shelltools/__init__.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +"""Supporting functions to work with the user's surrounding shell. +""" + +import collections +import contextlib +import importlib +import os +import signal +import subprocess +import sys + +from pipenv._compat import NamedTemporaryFile, Path, get_terminal_size +from pipenv.utils import temp_environ +from pipenv.vendor import pexpect + +import click + + +def _handover(cmd, args): + args = [cmd] + args + if os.name != 'nt': + os.execvp(cmd, args) + else: + proc = subprocess.run(args, shell=True, universal_newlines=True) + sys.exit(proc.returncode) + + +POSSIBLE_ENV_PYTHON = [ + Path('bin', 'python'), + Path('Scripts', 'python.exe'), +] + + +def _iter_python(venv): + for path in POSSIBLE_ENV_PYTHON: + full_path = venv.joinpath(path) + if full_path.is_file(): + yield full_path + + +class Shell: + + def __init__(self, cmd): + self.cmd = cmd + self.args = [] + + @contextlib.contextmanager + def inject_path(self, venv): + yield + + def fork(self, venv, cwd, args): + click.echo("Launching subshell in virtual environment…", err=True) + os.environ['VIRTUAL_ENV'] = str(venv) + with self.inject_path(venv): + os.chdir(cwd) + _handover(self.cmd, self.args + list(args)) + + def fork_compat(self, venv, cwd, args, lines): + try: + spawn = pexpect.spawn + except AttributeError: + click.echo( + u'Compatibility mode not supported. ' + u'Trying to continue as well-configured shell…', + err=True, + ) + self.fork(venv, cwd, args) + return + + # Grab current terminal dimensions to replace the hardcoded default + # dimensions of pexpect + dims = get_terminal_size() + with temp_environ(): + c = spawn(self.cmd, ['-i'], dimensions=(dims.lines, dims.columns)) + c.sendline(lines) + if args: + c.sendline(' '.join(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): + dims = get_terminal_size() + c.setwinsize(dims.lines, dims.columns) + + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + + # Interact with the new shell. + c.interact(escape_character=None) + c.close() + sys.exit(c.exitstatus) + + +class Bash(Shell): + # The usual PATH injection technique does not work with Bash. + # https://github.com/berdario/pew/issues/58#issuecomment-102182346 + @contextlib.contextmanager + def inject_path(self, venv): + bashrc_path = Path.home().joinpath('.bashrc') + with NamedTemporaryFile('w+') as rcfile: + if bashrc_path.is_file(): + base_rc_src = 'source "{0}"\n'.format(bashrc_path.as_posix()) + rcfile.write(base_rc_src) + + export_path = 'export PATH="{0}:$PATH"\n'.format(':'.join( + python.parent.as_posix() + for python in _iter_python(venv) + )) + rcfile.write(export_path) + rcfile.flush() + self.args.extend(['--rcfile', rcfile.name]) + yield + + +class CmderEmulatedShell(Shell): + def fork(self, venv, cwd, args): + if cwd: + os.environ['CMDER_START'] = cwd + super(CmderEmulatedShell, self).fork(venv, cwd, args) + + +class CmderCommandPrompt(CmderEmulatedShell): + def fork(self, venv, cwd, args): + rc = os.path.expandvars('%CMDER_ROOT%\\vendor\\init.bat') + if os.path.exists(rc): + self.args.extend(['/k', rc]) + super(CmderCommandPrompt, self).fork(venv, cwd, args) + + +class CmderPowershell(Shell): + def fork(self, venv, cwd, args): + rc = os.path.expandvars('%CMDER_ROOT%\\vendor\\profile.ps1') + if os.path.exists(rc): + self.args.extend([ + '-ExecutionPolicy', 'Bypass', '-NoLogo', '-NoProfile', + '-NoExit', '-Command', + "Invoke-Expression '. ''{0}'''".format(rc), + ]) + super(CmderPowershell, self).fork(venv, cwd, args) + + +# Two dimensional dict. First is the shell type, second is the emulator type. +# Example: SHELL_LOOKUP['powershell']['cmder'] => CmderPowershell. +SHELL_LOOKUP = collections.defaultdict( + lambda: collections.defaultdict(lambda: Shell), + { + 'bash': collections.defaultdict(lambda: Bash), + 'cmd': collections.defaultdict(lambda: Shell, { + 'cmder': CmderCommandPrompt, + }), + 'powershell': collections.defaultdict(lambda: Shell, { + 'cmder': CmderPowershell, + }), + 'pwsh': collections.defaultdict(lambda: Shell, { + 'cmder': CmderPowershell, + }), + }, +) + + +class CannotGuessShell(EnvironmentError): + pass + + +def _get_current_shell(pid=None, max_depth=6): + name = os.name + try: + impl = importlib.import_module('.' + name, 'pipenv.shelltools') + except ImportError: + raise RuntimeError( + 'Shell detection not implemented for {0!r}'.format(name), + ) + try: + get_shell = impl.get_shell + except AttributeError: + raise RuntimeError('get_shell not implemented for {0!r}'.format(name)) + shell = get_shell(pid, max_depth=max_depth) + if shell: + return shell + raise CannotGuessShell() + + +def _get_current_emulator(): + if os.environ.get('CMDER_ROOT'): + return 'cmder' + return '' + + +def choose_shell(shell_cmd=None): + if shell_cmd is None: + shell_cmd = _get_current_shell() + try: + emulator = os.environ['PIPENV_EMULATOR'] + except KeyError: + emulator = _get_current_emulator() + shell_cls = SHELL_LOOKUP[Path(shell_cmd).stem.lower()][emulator] + return shell_cls(shell_cmd) diff --git a/pipenv/patched/pew/_win_utils.py b/pipenv/shelltools/nt.py similarity index 97% rename from pipenv/patched/pew/_win_utils.py rename to pipenv/shelltools/nt.py index 8ab7a0b93c..47c30b0c13 100644 --- a/pipenv/patched/pew/_win_utils.py +++ b/pipenv/shelltools/nt.py @@ -1,4 +1,3 @@ -# -*- coding=utf-8 -*- # psutil is painfully slow in win32. So to avoid adding big # dependencies like pywin32 a ctypes based solution is preferred @@ -15,7 +14,7 @@ ERROR_NO_MORE_FILES = 18 INVALID_HANDLE_VALUE = c_void_p(-1).value -SHELL_NAMES = ['cmd', 'powershell', 'pwsh', 'cmder'] +SHELL_NAMES = ['cmd', 'powershell', 'pwsh'] class PROCESSENTRY32(Structure): @@ -71,7 +70,6 @@ def Process32Next(hSnapshot, pe=None): def get_all_processes(): """Return a dictionary of properties about all processes. - >>> get_all_processes() { 1509: { diff --git a/pipenv/shelltools/posix.py b/pipenv/shelltools/posix.py new file mode 100644 index 0000000000..7d57a5a936 --- /dev/null +++ b/pipenv/shelltools/posix.py @@ -0,0 +1,59 @@ +import collections +import os +import shlex +import subprocess +import sys + + +Process = collections.namedtuple('Process', 'args pid ppid') + +SHELL_NAMES = { + 'sh', 'bash', 'dash', # Bourne. + 'csh', 'tcsh', # C. + 'ksh', 'zsh', 'fish', # Common alternatives. + 'elvish', 'pwsh', 'xonsh', # More exotic. +} + + +def _get_process_mapping(): + """Try to look up the process tree via the output of `ps`. + """ + output = subprocess.check_output([ + 'ps', '-ww', '-o', 'pid=', '-o', 'ppid=', '-o', 'args=', + ]) + if not isinstance(output, str): + output = output.decode(sys.stdout.encoding) + processes = {} + for line in output.split('\n'): + try: + pid, ppid, args = line.strip().split(maxsplit=2) + except ValueError: + continue + processes[pid] = Process( + args=tuple(shlex.split(args)), pid=pid, ppid=ppid, + ) + return processes + + +def get_shell(pid=None, max_depth=6): + """Get the shell that the supplied pid or os.getpid() is running in. + """ + pid = str(pid or os.getpid()) + mapping = _get_process_mapping() + login_shell = os.environ.get('SHELL', '') + for _ in range(max_depth): + try: + proc = mapping[pid] + except KeyError: + break + name = os.path.basename(proc.args[0]).lower() + if name in SHELL_NAMES: + return proc.args[0] + elif proc.args[0].startswith('-'): + # This is the login shell. Use the SHELL environ if possible + # because it provides better information. + if login_shell: + return login_shell + return proc.args[0][1:] + pid = proc.ppid # Go up one level. + return None diff --git a/pipenv/utils.py b/pipenv/utils.py index 913cc88df1..fcecca5a86 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -902,7 +902,7 @@ def find_requirements(max_depth=3): raise RuntimeError('No requirements.txt found!') -# Borrowed from pew to avoid importing pew which imports psutil +# Borrowed from pew. # See https://github.com/berdario/pew/blob/master/pew/_utils.py#L82 @contextmanager def temp_environ(): diff --git a/setup.py b/setup.py index 7ba9c3d6fb..8b36627d4f 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,6 @@ def run(self): entry_points={ 'console_scripts': [ 'pipenv=pipenv:cli', - 'pewtwo=pipenv.patched.pew.pew:pew', 'pipenv-resolver=pipenv.resolver:main', ] }, diff --git a/tasks/vendoring/__init__.py b/tasks/vendoring/__init__.py index 4b64b818b6..9f02ab47db 100644 --- a/tasks/vendoring/__init__.py +++ b/tasks/vendoring/__init__.py @@ -41,7 +41,6 @@ 'semver': 'https://raw.githubusercontent.com/k-bx/python-semver/master/LICENSE.txt', 'crayons': 'https://raw.githubusercontent.com/kennethreitz/crayons/master/LICENSE', 'pip-tools': 'https://raw.githubusercontent.com/jazzband/pip-tools/master/LICENSE', - 'pew': 'https://raw.githubusercontent.com/berdario/pew/master/LICENSE', 'pytoml': 'https://github.com/avakar/pytoml/raw/master/LICENSE', 'webencodings': 'https://github.com/SimonSapin/python-webencodings/raw/' 'master/LICENSE', @@ -467,7 +466,7 @@ def extract_license_member(vendor_dir, tar, member, name): def generate_patch(ctx, package_path, patch_description, base='HEAD'): pkg = Path(package_path) if len(pkg.parts) != 2 or pkg.parts[0] not in ('vendor', 'patched'): - raise ValueError('example usage: generate-patch patched/pew some-description') + raise ValueError('example usage: generate-patch patched/piptools some-description') if patch_description: patch_fn = '{0}-{1}.patch'.format(pkg.parts[1], patch_description) else: diff --git a/tasks/vendoring/patches/patched/pew-cmder-root-space-escape-fix.patch b/tasks/vendoring/patches/patched/pew-cmder-root-space-escape-fix.patch deleted file mode 100644 index eb0e3d8296..0000000000 --- a/tasks/vendoring/patches/patched/pew-cmder-root-space-escape-fix.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py -index 2d3889a0..91f313c1 100644 ---- a/pipenv/patched/pew/pew.py -+++ b/pipenv/patched/pew/pew.py -@@ -184,7 +184,8 @@ def fork_bash(env, cwd): - - def fork_cmder(env, cwd): - shell_cmd = ['cmd'] -- cmderrc_path = r'%CMDER_ROOT%\vendor\init.bat' -+ escaped_cmder_root = os.environ['CMDER_ROOT'].replace(' ', '^ ') -+ cmderrc_path = r'{0}\vendor\init.bat'.format(escaped_cmder_root) - if expandpath(cmderrc_path).exists(): - shell_cmd += ['/k', cmderrc_path] - if cwd: diff --git a/tasks/vendoring/patches/patched/pew-windows-env-name-in-prompt.patch b/tasks/vendoring/patches/patched/pew-windows-env-name-in-prompt.patch deleted file mode 100644 index 0d06e4d0ec..0000000000 --- a/tasks/vendoring/patches/patched/pew-windows-env-name-in-prompt.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py -index 2d3889a0..07dbdf4f 100644 ---- a/pipenv/patched/pew/pew.py -+++ b/pipenv/patched/pew/pew.py -@@ -215,6 +215,8 @@ def shell(env, cwd=None): - inve(env, shell, '-c', shell_check) - except CalledProcessError: - return -+ if shell_name in ('Cmder', 'cmd'): -+ os.environ['PROMPT'] = '({0}) {1}'.format(env, os.environ['PROMPT']) - if shell_name == 'bash': - fork_bash(env, cwd) - elif shell_name == 'Cmder': diff --git a/tasks/vendoring/patches/patched/pew.patch b/tasks/vendoring/patches/patched/pew.patch deleted file mode 100644 index e6e5f737dc..0000000000 --- a/tasks/vendoring/patches/patched/pew.patch +++ /dev/null @@ -1,287 +0,0 @@ -diff --git a/pipenv/patched/pew/_print_utils.py b/pipenv/patched/pew/_print_utils.py -index 91a1d2b..05584e0 100644 ---- a/pipenv/patched/pew/_print_utils.py -+++ b/pipenv/patched/pew/_print_utils.py -@@ -9,7 +9,7 @@ except ImportError: - try: - from shutil import get_terminal_size - except ImportError: -- from backports.shutil_get_terminal_size import get_terminal_size -+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size - - SEP = ' ' - L = len(SEP) -diff --git a/pipenv/patched/pew/_utils.py b/pipenv/patched/pew/_utils.py -index d1d8e0a..c45c37e 100644 ---- a/pipenv/patched/pew/_utils.py -+++ b/pipenv/patched/pew/_utils.py -@@ -6,7 +6,10 @@ from contextlib import contextmanager - from subprocess import check_call, Popen, PIPE - from collections import namedtuple - from functools import partial, wraps --from pathlib import Path -+try: -+ from pathlib import Path -+except ImportError: -+ from pipenv.vendor.pathlib2 import Path - from tempfile import NamedTemporaryFile as _ntf - try: - from shutil import which -diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py -index bcaabad..b8fc3e7 100644 ---- a/pipenv/patched/pew/pew.py -+++ b/pipenv/patched/pew/pew.py -@@ -8,12 +8,15 @@ import random - import textwrap - from functools import partial - from subprocess import CalledProcessError --from pathlib import Path -+try: -+ from pathlib import Path -+except ImportError: -+ from pipenv.vendor.pathlib2 import Path - - try: - from shutil import get_terminal_size - except ImportError: -- from backports.shutil_get_terminal_size import get_terminal_size -+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size - - windows = sys.platform == 'win32' - -@@ -36,7 +39,7 @@ else: - InstallCommand = ListPythons = LocatePython = UninstallCommand = \ - lambda : sys.exit('Command not supported on this platform') - -- import psutil -+ from ._win_utils import get_shell - - from pew._utils import (check_call, invoke, expandpath, own, env_bin_dir, - check_path, temp_environ, NamedTemporaryFile, to_unicode) -@@ -52,8 +55,9 @@ if windows: - else: - default_home = os.path.join( - os.environ.get('XDG_DATA_HOME', '~/.local/share'), 'virtualenvs') --workon_home = expandpath( -- os.environ.get('WORKON_HOME', default_home)) -+ -+def get_workon_home(): -+ return expandpath(os.environ.get('WORKON_HOME', default_home)) - - - def makedirs_and_symlink_if_needed(workon_home): -@@ -96,7 +100,7 @@ def deploy_completions(): - - - def get_project_dir(env): -- project_file = workon_home / env / '.project' -+ project_file = get_workon_home() / env / '.project' - if project_file.exists(): - with project_file.open() as f: - project_dir = f.readline().strip() -@@ -113,7 +117,7 @@ def unsetenv(key): - - - def compute_path(env): -- envdir = workon_home / env -+ envdir = get_workon_home() / env - return os.pathsep.join([ - str(envdir / env_bin_dir), - os.environ['PATH'], -@@ -127,7 +131,7 @@ def inve(env, command, *args, **kwargs): - # we don't strictly need to restore the environment, since pew runs in - # its own process, but it feels like the right thing to do - with temp_environ(): -- os.environ['VIRTUAL_ENV'] = str(workon_home / env) -+ os.environ['VIRTUAL_ENV'] = str(get_workon_home() / env) - os.environ['PATH'] = compute_path(env) - - unsetenv('PYTHONHOME') -@@ -151,7 +155,16 @@ def fork_shell(env, shellcmd, cwd): - if 'VIRTUAL_ENV' in os.environ: - err("Be aware that this environment will be nested on top " - "of '%s'" % Path(os.environ['VIRTUAL_ENV']).name) -- inve(env, *shellcmd, cwd=cwd) -+ try: -+ inve(env, *shellcmd, cwd=cwd) -+ except CalledProcessError: -+ # These shells report errors when the last command executed in the -+ # subshell in an error. This causes the subprocess to fail, which is -+ # not what we want. Stay silent for them, there's nothing we can do. -+ shell_name, _ = os.path.splitext(os.path.basename(shellcmd[0])) -+ suppress_error = shell_name.lower() in ('cmd', 'powershell', 'pwsh') -+ if not suppress_error: -+ raise - - - def fork_bash(env, cwd): -@@ -184,7 +197,7 @@ def _detect_shell(): - if 'CMDER_ROOT' in os.environ: - shell = 'Cmder' - elif windows: -- shell = psutil.Process(os.getpid()).parent().parent().name() -+ shell = get_shell(os.getpid()) - else: - shell = 'sh' - return shell -@@ -193,10 +206,10 @@ def shell(env, cwd=None): - env = str(env) - shell = _detect_shell() - shell_name = Path(shell).stem -- if shell_name not in ('Cmder', 'bash', 'elvish', 'powershell', 'klingon', 'cmd'): -+ if shell_name not in ('Cmder', 'bash', 'elvish', 'powershell', 'pwsh', 'klingon', 'cmd'): - # On Windows the PATH is usually set with System Utility - # so we won't worry about trying to check mistakes there -- shell_check = (sys.executable + ' -c "from pew.pew import ' -+ shell_check = (sys.executable + ' -c "from pipenv.patched.pew.pew import ' - 'prevent_path_errors; prevent_path_errors()"') - try: - inve(env, shell, '-c', shell_check) -@@ -216,7 +229,7 @@ def mkvirtualenv(envname, python=None, packages=[], project=None, - if python: - rest = ["--python=%s" % python] + rest - -- path = (workon_home / envname).absolute() -+ path = (get_workon_home() / envname).absolute() - - try: - check_call([sys.executable, "-m", "virtualenv", str(path)] + rest) -@@ -265,7 +278,7 @@ project directory to associate with the new environment.') - def rmvirtualenvs(envs): - error_happened = False - for env in envs: -- env = workon_home / env -+ env = get_workon_home() / env - if os.environ.get('VIRTUAL_ENV') == str(env): - err("ERROR: You cannot remove the active environment (%s)." % env) - error_happened = True -@@ -295,7 +308,7 @@ def packages(site_packages): - def showvirtualenv(env): - columns, _ = get_terminal_size() - pkgs = sorted(packages(sitepackages_dir(env))) -- env_python = workon_home / env / env_bin_dir / 'python' -+ env_python = get_workon_home() / env / env_bin_dir / 'python' - l = len(env) + 2 - version = invoke(str(env_python), '-V') - version = ' - '.join((version.out + version.err).splitlines()) -@@ -317,8 +330,8 @@ def show_cmd(argv): - - - def lsenvs(): -- return sorted(set(env.parts[-3] for env in -- workon_home.glob(os.path.join('*', env_bin_dir, 'python*')))) -+ items = get_workon_home().glob(os.path.join('*', env_bin_dir, 'python*')) -+ return sorted(set(env.parts[-3] for env in items)) - - - def lsvirtualenv(verbose): -@@ -347,7 +360,7 @@ def parse_envname(argv, no_arg_callback): - env = argv[0] - if env.startswith('/'): - sys.exit("ERROR: Invalid environment name '{0}'.".format(env)) -- if not (workon_home / env).exists(): -+ if not (get_workon_home() / env).exists(): - sys.exit("ERROR: Environment '{0}' does not exist. Create it with \ - 'pew new {0}'.".format(env)) - else: -@@ -372,7 +385,7 @@ def sitepackages_dir(env=os.environ.get('VIRTUAL_ENV')): - if not env: - sys.exit('ERROR: no virtualenv active') - else: -- env_python = workon_home / env / env_bin_dir / 'python' -+ env_python = get_workon_home() / env / env_bin_dir / 'python' - return Path(invoke(str(env_python), '-c', 'import distutils; \ - print(distutils.sysconfig.get_python_lib())').out) - -@@ -459,6 +472,7 @@ def cp_cmd(argv): - - def copy_virtualenv_project(source, target): - source = expandpath(source) -+ workon_home = get_workon_home() - if not source.exists(): - source = workon_home / source - if not source.exists(): -@@ -490,7 +504,7 @@ def rename_cmd(argv): - - def setvirtualenvproject(env, project): - print('Setting project for {0} to {1}'.format(env, project)) -- with (workon_home / env / '.project').open('wb') as prj: -+ with (get_workon_home() / env / '.project').open('wb') as prj: - prj.write(str(project).encode()) - - -@@ -502,7 +516,7 @@ def setproject_cmd(argv): - env = args.get(0, os.environ.get('VIRTUAL_ENV')) - if not env: - sys.exit('pew setproject [virtualenv] [project_path]') -- if not (workon_home / env).exists(): -+ if not (get_workon_home() / env).exists(): - sys.exit("Environment '%s' doesn't exist." % env) - if not os.path.isdir(project): - sys.exit('pew setproject: %s does not exist' % project) -@@ -512,7 +526,7 @@ def setproject_cmd(argv): - def mkproject_cmd(argv): - """Create a new project directory and its associated virtualenv.""" - if '-l' in argv or '--list' in argv: -- templates = [t.name[9:] for t in workon_home.glob("template_*")] -+ templates = [t.name[9:] for t in get_workon_home().glob("template_*")] - print("Available project templates:", *templates, sep='\n') - return - -@@ -542,7 +556,7 @@ Create it or set PROJECT_HOME to an existing directory.' % projects_home) - project.mkdir() - - for template_name in args.templates: -- template = workon_home / ("template_" + template_name) -+ template = get_workon_home() / ("template_" + template_name) - inve(args.envname, str(template), args.envname, str(project)) - if args.activate: - shell(args.envname, cwd=str(project)) -@@ -552,7 +566,7 @@ def mktmpenv_cmd(argv): - """Create a temporary virtualenv.""" - parser = mkvirtualenv_argparser() - env = '.' -- while (workon_home / env).exists(): -+ while (get_workon_home() / env).exists(): - env = hex(random.getrandbits(64))[2:-1] - - args, rest = parser.parse_known_args(argv) -@@ -574,10 +588,10 @@ def wipeenv_cmd(argv): - - if not env: - sys.exit('ERROR: no virtualenv active') -- elif not (workon_home / env).exists(): -+ elif not (get_workon_home() / env).exists(): - sys.exit("ERROR: Environment '{0}' does not exist.".format(env)) - else: -- env_pip = str(workon_home / env / env_bin_dir / 'pip') -+ env_pip = str(get_workon_home() / env / env_bin_dir / 'pip') - all_pkgs = set(invoke(env_pip, 'freeze').out.splitlines()) - pkgs = set(p for p in all_pkgs if len(p.split("==")) == 2) - ignored = sorted(all_pkgs - pkgs) -@@ -623,6 +637,7 @@ def restore_cmd(argv): - sys.exit('You must provide a valid virtualenv to target') - - env = argv[0] -+ path = get_workon_home() / env - path = workon_home / env - py = path / env_bin_dir / ('python.exe' if windows else 'python') - exact_py = py.resolve().name -@@ -633,7 +648,7 @@ def restore_cmd(argv): - def dir_cmd(argv): - """Print the path for the virtualenv directory""" - env = parse_envname(argv, lambda : sys.exit('You must provide a valid virtualenv to target')) -- print(workon_home / env) -+ print(get_workon_home() / env) - - - def install_cmd(argv): -@@ -745,7 +760,7 @@ def print_commands(cmds): - - - def pew(): -- first_run = makedirs_and_symlink_if_needed(workon_home) -+ first_run = makedirs_and_symlink_if_needed(get_workon_home()) - if first_run and sys.stdin.isatty(): - first_run_setup() - diff --git a/tasks/vendoring/patches/patched/pew_psutil.patch b/tasks/vendoring/patches/patched/pew_psutil.patch deleted file mode 100644 index 1378f56a47..0000000000 --- a/tasks/vendoring/patches/patched/pew_psutil.patch +++ /dev/null @@ -1,151 +0,0 @@ -diff --git a/pipenv/patched/pew/__main__.py b/pipenv/patched/pew/__main__.py -index e35cf68..2267b8d 100644 ---- a/pipenv/patched/pew/__main__.py -+++ b/pipenv/patched/pew/__main__.py -@@ -1,3 +1,4 @@ --from pew.pew import pew -+from . import pew - --pew() -+if __name__ == '__main__': -+ pew.pew.pew() -diff --git a/pipenv/patched/pew/_win_utils.py b/pipenv/patched/pew/_win_utils.py -new file mode 100644 -index 0000000..8ab7a0b ---- /dev/null -+++ b/pipenv/patched/pew/_win_utils.py -@@ -0,0 +1,121 @@ -+# -*- coding=utf-8 -*- -+# psutil is painfully slow in win32. So to avoid adding big -+# dependencies like pywin32 a ctypes based solution is preferred -+ -+# Code based on the winappdbg project http://winappdbg.sourceforge.net/ -+# (BSD License) - adapted from Celery -+# https://github.com/celery/celery/blob/2.5-archived/celery/concurrency/processes/_win.py -+import os -+import six -+from ctypes import ( -+ byref, sizeof, windll, Structure, WinError, POINTER, -+ c_size_t, c_char, c_void_p -+) -+from ctypes.wintypes import DWORD, LONG -+ -+ERROR_NO_MORE_FILES = 18 -+INVALID_HANDLE_VALUE = c_void_p(-1).value -+SHELL_NAMES = ['cmd', 'powershell', 'pwsh', 'cmder'] -+ -+ -+class PROCESSENTRY32(Structure): -+ _fields_ = [ -+ ('dwSize', DWORD), -+ ('cntUsage', DWORD), -+ ('th32ProcessID', DWORD), -+ ('th32DefaultHeapID', c_size_t), -+ ('th32ModuleID', DWORD), -+ ('cntThreads', DWORD), -+ ('th32ParentProcessID', DWORD), -+ ('pcPriClassBase', LONG), -+ ('dwFlags', DWORD), -+ ('szExeFile', c_char * 260), -+ ] -+ -+ -+LPPROCESSENTRY32 = POINTER(PROCESSENTRY32) -+ -+ -+def CreateToolhelp32Snapshot(dwFlags=2, th32ProcessID=0): -+ hSnapshot = windll.kernel32.CreateToolhelp32Snapshot( -+ dwFlags, -+ th32ProcessID -+ ) -+ if hSnapshot == INVALID_HANDLE_VALUE: -+ raise WinError() -+ return hSnapshot -+ -+ -+def Process32First(hSnapshot): -+ pe = PROCESSENTRY32() -+ pe.dwSize = sizeof(PROCESSENTRY32) -+ success = windll.kernel32.Process32First(hSnapshot, byref(pe)) -+ if not success: -+ if windll.kernel32.GetLastError() == ERROR_NO_MORE_FILES: -+ return -+ raise WinError() -+ return pe -+ -+ -+def Process32Next(hSnapshot, pe=None): -+ if pe is None: -+ pe = PROCESSENTRY32() -+ pe.dwSize = sizeof(PROCESSENTRY32) -+ success = windll.kernel32.Process32Next(hSnapshot, byref(pe)) -+ if not success: -+ if windll.kernel32.GetLastError() == ERROR_NO_MORE_FILES: -+ return -+ raise WinError() -+ return pe -+ -+ -+def get_all_processes(): -+ """Return a dictionary of properties about all processes. -+ -+ >>> get_all_processes() -+ { -+ 1509: { -+ 'parent_pid': 1201, -+ 'executable': 'C:\\Program\\\\ Files\\Python36\\python.exe' -+ } -+ } -+ """ -+ h_process = CreateToolhelp32Snapshot() -+ pids = {} -+ pe = Process32First(h_process) -+ while pe: -+ pids[pe.th32ProcessID] = { -+ 'executable': str(pe.szExeFile.decode('utf-8')) -+ } -+ if pe.th32ParentProcessID: -+ pids[pe.th32ProcessID]['parent_pid'] = pe.th32ParentProcessID -+ pe = Process32Next(h_process, pe) -+ -+ return pids -+ -+ -+def _get_executable(process_dict): -+ if hasattr(process_dict, 'keys'): -+ executable = process_dict.get('executable') -+ if isinstance(executable, six.string_types): -+ return executable.lower().rsplit('.', 1)[0] -+ return '' -+ -+ -+def get_shell(pid=None, max_depth=6): -+ """Get the shell that the supplied pid or os.getpid() is running in. -+ """ -+ if not pid: -+ pid = os.getpid() -+ processes = get_all_processes() -+ -+ def check_parent(pid, lvl=0): -+ ppid = processes[pid].get('parent_pid') -+ if ppid and _get_executable(processes.get(ppid)) in SHELL_NAMES: -+ return processes[ppid]['executable'] -+ if lvl >= max_depth: -+ return -+ return check_parent(ppid, lvl=lvl+1) -+ if _get_executable(processes.get(pid)) in SHELL_NAMES: -+ return processes[pid]['executable'] -+ return check_parent(pid) -diff --git a/pipenv/patched/pew/template_django b/pipenv/patched/pew/template_django -new file mode 100644 -index 0000000..4fec88e ---- /dev/null -+++ b/pipenv/patched/pew/template_django -@@ -0,0 +1,6 @@ -+#! /bin/sh -+# put this inside $WORKON_HOME -+project=$1 -+project_dir=$2 -+pip install django -+django-admin.py startproject "$project" -\ No newline at end of file diff --git a/tests/integration/test_dot_venv.py b/tests/integration/test_dot_venv.py index 0b4cb48590..e7b157f3c2 100644 --- a/tests/integration/test_dot_venv.py +++ b/tests/integration/test_dot_venv.py @@ -39,48 +39,3 @@ def test_reuse_previous_venv(PipenvInstance, pypi): c = p.pipenv('install requests') assert c.return_code == 0 assert normalize_drive(p.path) in p.pipenv('--venv').out - - -@pytest.mark.dotvenv -@pytest.mark.install -@pytest.mark.complex -@pytest.mark.shell -@pytest.mark.windows -@pytest.mark.pew -@pytest.mark.skip('Not mocking this.') -def test_shell_nested_venv_in_project(PipenvInstance, pypi): - import subprocess - with temp_environ(): - os.environ['PIPENV_VENV_IN_PROJECT'] = '1' - os.environ['PIPENV_IGNORE_VIRTUALENVS'] = '1' - with PipenvInstance(chdir=True, pypi=pypi) as p: - # Signal to pew to look in the project directory for the environment - os.environ['WORKON_HOME'] = p.path - c = p.pipenv('install requests') - assert c.return_code == 0 - assert 'requests' in p.pipfile['packages'] - assert 'requests' in p.lockfile['default'] - # Check that .venv now shows in pew's managed list - pew_list = delegator.run('pewtwo ls') - assert '.venv' in pew_list.out - # Check for the venv directory - c = delegator.run('pewtwo dir .venv') - # Compare pew's virtualenv path to what we expect - venv_path = get_windows_path(Project().project_directory, '.venv') - # os.path.normpath will normalize slashes - assert venv_path == normalize_drive(os.path.normpath(c.out.strip())) - # Have pew run 'pip freeze' in the virtualenv - # This is functionally the same as spawning a subshell - # If we can do this we can theoretically make a subshell - # This test doesn't work on *nix - if os.name == 'nt': - process = subprocess.Popen( - 'pewtwo in .venv pip freeze', - shell=True, - universal_newlines=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - out, _ = process.communicate() - assert any(req.startswith('requests') for req in out.splitlines()) is True From c5a3f6cd7f4a5417c61a9edb05c8054a858fb354 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 03:48:04 +0800 Subject: [PATCH 3/8] Python 2 compatibility --- pipenv/core.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index bdc0d5e871..988be32c5c 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -2158,15 +2158,19 @@ def do_shell(three=None, python=False, fancy=False, shell_args=None): bold=True, ), err=True) - fork_args = ( - project.virtualenv_location, - project.project_directory, - shell_args, - ) if fancy: - shell.fork(*fork_args) + shell.fork( + project.virtualenv_location, + project.project_directory, + shell_args, + ) else: - shell.fork_compat(*fork_args, activate_virtualenv()) + shell.fork_compat( + project.virtualenv_location, + project.project_directory, + shell_args, + activate_virtualenv(), + ) def inline_activate_virtualenv(): From 499b19661280bbc232f654ca9fae37dd2d89af91 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 03:51:35 +0800 Subject: [PATCH 4/8] Check for dir existence before iterating --- pipenv/project.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pipenv/project.py b/pipenv/project.py index 4c998e48bd..ed61dd2738 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -281,7 +281,10 @@ def get_name(name, location): return clean_name, encoded_hash # Check for different capitalization of the same project. - for path in Path(WORKON_HOME).iterdir(): + workon_home = Path(WORKON_HOME) + if not workon_home.is_dir(): # Nothing to check. We're good. + return clean_name, encoded_hash + for path in workon_home.iterdir(): if not path.is_dir(): continue try: From 255388e04586802e9670391f6623f4586dbbb311 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 10:41:51 +0800 Subject: [PATCH 5/8] PS1 support and move environs out of the subpackage --- pipenv/core.py | 5 +++-- pipenv/environments.py | 1 + pipenv/{shelltools => shells}/__init__.py | 24 ++++++++++++++--------- pipenv/{shelltools => shells}/nt.py | 0 pipenv/{shelltools => shells}/posix.py | 0 5 files changed, 19 insertions(+), 11 deletions(-) rename pipenv/{shelltools => shells}/__init__.py (90%) rename pipenv/{shelltools => shells}/nt.py (100%) rename pipenv/{shelltools => shells}/posix.py (100%) diff --git a/pipenv/core.py b/pipenv/core.py index 988be32c5c..d9d86d6550 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -72,6 +72,7 @@ PIPENV_USE_SYSTEM, PIPENV_DOTENV_LOCATION, PIPENV_SHELL, + PIPENV_EMULATOR, PIPENV_PYTHON, PIPENV_VIRTUALENV, PIPENV_CACHE_DIR, @@ -2141,9 +2142,9 @@ def do_shell(three=None, python=False, fancy=False, shell_args=None): if PIPENV_SHELL_FANCY: fancy = True - from .shelltools import choose_shell, CannotGuessShell + from .shells import choose_shell, CannotGuessShell try: - shell = choose_shell(PIPENV_SHELL) + shell = choose_shell(PIPENV_SHELL, PIPENV_EMULATOR) except CannotGuessShell: click.echo(crayons.red( 'Please ensure that the {0} environment variable is set before ' diff --git a/pipenv/environments.py b/pipenv/environments.py index f9b00c4dfe..837aaf75b8 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -79,4 +79,5 @@ ) SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno())) 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')) diff --git a/pipenv/shelltools/__init__.py b/pipenv/shells/__init__.py similarity index 90% rename from pipenv/shelltools/__init__.py rename to pipenv/shells/__init__.py index 0c7cc7875c..59773764f2 100644 --- a/pipenv/shelltools/__init__.py +++ b/pipenv/shells/__init__.py @@ -53,6 +53,14 @@ def inject_path(self, venv): def fork(self, venv, cwd, args): click.echo("Launching subshell in virtual environment…", err=True) os.environ['VIRTUAL_ENV'] = str(venv) + if os.name == 'nt': + os.environ['PROMPT'] = '({0}) {1}'.format( + str(venv), os.environ['PROMPT'], + ) + else: + os.environ['PS1'] = '({0}) {1}'.format( + str(venv), os.environ['PS1'], + ) with self.inject_path(venv): os.chdir(cwd) _handover(self.cmd, self.args + list(args)) @@ -63,7 +71,7 @@ def fork_compat(self, venv, cwd, args, lines): except AttributeError: click.echo( u'Compatibility mode not supported. ' - u'Trying to continue as well-configured shell…', + u'Trying to continue as well-configured shell…', err=True, ) self.fork(venv, cwd, args) @@ -164,7 +172,7 @@ class CannotGuessShell(EnvironmentError): pass -def _get_current_shell(pid=None, max_depth=6): +def _detect_current_shell(pid=None, max_depth=6): name = os.name try: impl = importlib.import_module('.' + name, 'pipenv.shelltools') @@ -182,18 +190,16 @@ def _get_current_shell(pid=None, max_depth=6): raise CannotGuessShell() -def _get_current_emulator(): +def _detect_current_emulator(): if os.environ.get('CMDER_ROOT'): return 'cmder' return '' -def choose_shell(shell_cmd=None): +def choose_shell(shell_cmd=None, emulator=None): if shell_cmd is None: - shell_cmd = _get_current_shell() - try: - emulator = os.environ['PIPENV_EMULATOR'] - except KeyError: - emulator = _get_current_emulator() + shell_cmd = _detect_current_shell() + if emulator is None: + emulator = _detect_current_emulator() shell_cls = SHELL_LOOKUP[Path(shell_cmd).stem.lower()][emulator] return shell_cls(shell_cmd) diff --git a/pipenv/shelltools/nt.py b/pipenv/shells/nt.py similarity index 100% rename from pipenv/shelltools/nt.py rename to pipenv/shells/nt.py diff --git a/pipenv/shelltools/posix.py b/pipenv/shells/posix.py similarity index 100% rename from pipenv/shelltools/posix.py rename to pipenv/shells/posix.py From 2a4450e56fe02e87d2d213bdd9fe59592748abee Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 10:58:45 +0800 Subject: [PATCH 6/8] ALways clean up WORKON_HOME Becauss pathlib does not like trailing whitespace --- pipenv/environments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv/environments.py b/pipenv/environments.py index 837aaf75b8..081e0f0940 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -10,7 +10,7 @@ # Where to put virtual environments. WORKON_HOME = os.path.expanduser(os.environ.get( 'WORKON_HOME', '~/.virtualenvs', -)) +)).strip() # Shell compatibility mode, for mis-configured shells. PIPENV_SHELL_FANCY = bool(os.environ.get('PIPENV_SHELL_FANCY')) # Support for both Python 2 and Python 3 at the same time. From b0fa6ca663c21ad8c4341ea77db438b9473eae53 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 11:21:01 +0800 Subject: [PATCH 7/8] ALways return the virtualenv location --- pipenv/project.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pipenv/project.py b/pipenv/project.py index ed61dd2738..98cabff5d0 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -238,10 +238,7 @@ def virtualenv_exists(self): @classmethod def _get_virtualenv_location(cls, name): - venv = os.path.join(WORKON_HOME, name) - if not os.path.exists(venv): - return '' - return venv + return os.path.join(WORKON_HOME, name) @classmethod def _sanitize(cls, name): From e37326a2a9e5feda48ea776640de31adb2795639 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 8 Jun 2018 11:26:56 +0800 Subject: [PATCH 8/8] I want to remove this This gives me cryptic errors if virtualenv_location is empty (because the None from .get() is passed into os.path.join and hell breaks loose). --- pipenv/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipenv/core.py b/pipenv/core.py index d9d86d6550..6aaf51e53d 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -117,7 +117,7 @@ def spinner(): def which(command, location=None, allow_global=False): if not allow_global and location is None: - location = project.virtualenv_location or os.environ.get('VIRTUAL_ENV') + location = project.virtualenv_location if not allow_global: if os.name == 'nt': p = find_windows_executable(