diff --git a/pipenv/core.py b/pipenv/core.py index b3afee26..4c8667e7 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -975,7 +975,7 @@ def do_create_virtualenv(python=None, site_packages=False): click.echo(crayons.normal(u'Making site-packages available…', bold=True), err=True) os.environ['VIRTUAL_ENV'] = project.virtualenv_location - delegator.run('pipenv run pew toggleglobalsitepackages') + delegator.run('pipenv run pewtwo toggleglobalsitepackages') del os.environ['VIRTUAL_ENV'] # Say where the virtualenv is. diff --git a/pipenv/patched/pew/__init__.py b/pipenv/patched/pew/__init__.py new file mode 100644 index 00000000..90fe7cfd --- /dev/null +++ b/pipenv/patched/pew/__init__.py @@ -0,0 +1,4 @@ +from __future__ import absolute_import + +from . import pew +__all__ = ['pew'] diff --git a/pipenv/patched/pew/__main__.py b/pipenv/patched/pew/__main__.py new file mode 100644 index 00000000..e35cf68e --- /dev/null +++ b/pipenv/patched/pew/__main__.py @@ -0,0 +1,3 @@ +from pew.pew import pew + +pew() diff --git a/pipenv/patched/pew/_print_utils.py b/pipenv/patched/pew/_print_utils.py new file mode 100644 index 00000000..91a1d2b7 --- /dev/null +++ b/pipenv/patched/pew/_print_utils.py @@ -0,0 +1,55 @@ +from __future__ import division, print_function + +import os +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 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() + columns_number = 1 + for columns_number in range(1, len(venvs) + 1): + rows = get_rows(venvs, columns_number) + if max(map(row_len, rows)) > 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 new file mode 100644 index 00000000..d1d8e0ac --- /dev/null +++ b/pipenv/patched/pew/_utils.py @@ -0,0 +1,89 @@ +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 +from pathlib 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/_win_utils.py b/pipenv/patched/pew/_win_utils.py new file mode 100644 index 00000000..4d55b020 --- /dev/null +++ b/pipenv/patched/pew/_win_utils.py @@ -0,0 +1,107 @@ +# -*- 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 +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 + + +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_grandparent_process(pid=None): + """Get grandparent process name of the supplied pid or os.getpid(). + + :param int pid: The pid to track. + :return: Name of the grandparent process. + """ + if not pid: + pid = os.getpid() + processes = get_all_processes() + ppid = processes[pid]['parent_pid'] + parent = processes[ppid] + grandparent = processes[parent['parent_pid']] + return grandparent['executable'] diff --git a/pipenv/patched/pew/pew.py b/pipenv/patched/pew/pew.py new file mode 100644 index 00000000..470dfd3d --- /dev/null +++ b/pipenv/patched/pew/pew.py @@ -0,0 +1,768 @@ +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 +from pathlib import Path + +try: + from shutil import get_terminal_size +except ImportError: + from 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_grandparent_process + +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') +workon_home = 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 = 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 = 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(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) + inve(env, *shellcmd, cwd=cwd) + + +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'] + cmderrc_path = r'%CMDER_ROOT%\vendor\init.bat' + 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_grandparent_process(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', '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 ' + 'prevent_path_errors; prevent_path_errors()"') + try: + inve(env, shell, '-c', shell_check) + except CalledProcessError: + return + 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 = (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 = 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 = 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(): + return sorted(set(env.parts[-3] for env in + workon_home.glob(os.path.join('*', env_bin_dir, 'python*')))) + + +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 (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 = 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) + 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 (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 (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 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 = 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 (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 (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') + 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 = 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(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(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 new file mode 100644 index 00000000..4f0bfd8f --- /dev/null +++ b/pipenv/patched/pew/shell_config/_pew @@ -0,0 +1 @@ +complete.zsh \ No newline at end of file diff --git a/pipenv/patched/pew/shell_config/complete.bash b/pipenv/patched/pew/shell_config/complete.bash new file mode 100644 index 00000000..21e7ba74 --- /dev/null +++ b/pipenv/patched/pew/shell_config/complete.bash @@ -0,0 +1,46 @@ +# 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 new file mode 100644 index 00000000..af9f6d22 --- /dev/null +++ b/pipenv/patched/pew/shell_config/complete.fish @@ -0,0 +1,116 @@ +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 new file mode 100644 index 00000000..623fbff8 --- /dev/null +++ b/pipenv/patched/pew/shell_config/complete.zsh @@ -0,0 +1,107 @@ +#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 new file mode 100644 index 00000000..522f8572 --- /dev/null +++ b/pipenv/patched/pew/shell_config/complete_deploy @@ -0,0 +1,4 @@ +#! /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 new file mode 100644 index 00000000..fbdefd26 --- /dev/null +++ b/pipenv/patched/pew/shell_config/init.bash @@ -0,0 +1,3 @@ +source "$( dirname "${BASH_SOURCE[0]}" )"/complete.bash + +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 new file mode 100644 index 00000000..aebc56e9 --- /dev/null +++ b/pipenv/patched/pew/shell_config/init.fish @@ -0,0 +1,9 @@ +. (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 new file mode 100644 index 00000000..93a63325 --- /dev/null +++ b/pipenv/patched/pew/shell_config/init.zsh @@ -0,0 +1,12 @@ +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 new file mode 100644 index 00000000..4fec88ef --- /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/pipenv/pew/__main__.py b/pipenv/pew/__main__.py index 16bc1552..cb33dc9c 100644 --- a/pipenv/pew/__main__.py +++ b/pipenv/pew/__main__.py @@ -1,5 +1,4 @@ -import pew +from pipenv.patched import pew if __name__ == '__main__': pew.pew.pew() - diff --git a/pipenv/project.py b/pipenv/project.py index 5634b762..64577fd8 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -176,7 +176,7 @@ class Project(object): # The user wants the virtualenv in the project. if not PIPENV_VENV_IN_PROJECT: - c = delegator.run('pew dir "{0}"'.format(self.virtualenv_name)) + c = delegator.run('pewtwo dir "{0}"'.format(self.virtualenv_name)) loc = c.out.strip() # Default mode. else: diff --git a/setup.py b/setup.py index be4e74d3..40f7e908 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,8 @@ if sys.argv[-1] == "publish": required = [ 'virtualenv', - 'pew>=0.1.26' + 'virtualenv-clone>=0.2.5', + 'pathlib;python_version<"3.4"' ] if sys.version_info < (2, 7): @@ -113,7 +114,10 @@ setup( url='https://github.com/pypa/pipenv', packages=find_packages(exclude=['tests']), entry_points={ - 'console_scripts': ['pipenv=pipenv:cli'], + 'console_scripts': [ + 'pipenv=pipenv:cli', + 'pewtwo=pipenv.patched.pew.pew:pew' + ], }, install_requires=required, extras_require={ diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index 381af55d..74ac33a0 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -692,10 +692,10 @@ requests = {version = "*"} 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('pew ls') + pew_list = delegator.run('pewtwo ls') assert '.venv' in pew_list.out # Check for the venv directory - c = delegator.run('pew dir .venv') + 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 @@ -705,7 +705,7 @@ requests = {version = "*"} # If we can do this we can theoretically make a subshell # This test doesn't work on *nix if os.name == 'nt': - args = ['pew', 'in', '.venv', 'pip', 'freeze'] + args = ['pewtwo', 'in', '.venv', 'pip', 'freeze'] process = subprocess.Popen( args, shell=True,