From 107835ec99a9a750d5466c50c275c14aa2e9cf07 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 9 Mar 2010 14:39:48 -0500 Subject: [PATCH] factor out get_installed_distributions utility function and add pip uninstall autocomplete; fixes #76 --- MANIFEST.in | 1 - pip/__init__.py | 13 +++++++++++++ pip/commands/freeze.py | 9 ++------- pip/req.py | 11 ++++------- pip/util.py | 31 +++++++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1e9fb72b0..5be54085c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,3 @@ recursive-include docs *.txt recursive-include docs *.html recursive-exclude docs/_build *.txt prune docs/_build/_sources -recursive-include scripts/completion * diff --git a/pip/__init__.py b/pip/__init__.py index 566f69d8a..38c266bc0 100755 --- a/pip/__init__.py +++ b/pip/__init__.py @@ -9,6 +9,7 @@ from pip.baseparser import parser from pip.exceptions import InstallationError from pip.basecommand import command_dict, load_command, load_all_commands from pip.vcs import vcs, get_src_requirement, import_vcs_support +from pip.util import get_installed_distributions def autocomplete(): """Command and option completion for the main option parser (and options) @@ -39,6 +40,18 @@ def autocomplete(): # subcommand options # special case: the 'help' subcommand has no options elif cwords[0] in subcommands and cwords[0] != 'help': + # special case: list locally installed dists for uninstall command + if cwords[0] == 'uninstall' and not current.startswith('-'): + installed = [] + lc = current.lower() + for dist in get_installed_distributions(local_only=True): + if dist.key.startswith(lc) and dist.key not in cwords[1:]: + installed.append(dist.key) + # if there are no dists installed, fall back to option completion + if installed: + for dist in installed: + print dist + sys.exit(1) subcommand = command_dict.get(cwords[0]) options += [(opt.get_opt_string(), opt.nargs) for opt in subcommand.parser.option_list diff --git a/pip/commands/freeze.py b/pip/commands/freeze.py index 2f811ec4e..f312325e8 100644 --- a/pip/commands/freeze.py +++ b/pip/commands/freeze.py @@ -5,7 +5,7 @@ import pip from pip.req import InstallRequirement from pip.log import logger from pip.basecommand import Command -from pip.util import dist_location, is_local +from pip.util import get_installed_distributions class FreezeCommand(Command): name = 'freeze' @@ -61,12 +61,7 @@ class FreezeCommand(Command): for link in find_links: f.write('-f %s\n' % link) installations = {} - for dist in pkg_resources.working_set: - if local_only and not is_local(dist_location(dist)): - continue - if dist.key in ('setuptools', 'pip', 'python'): - ## FIXME: also skip virtualenv? - continue + for dist in get_installed_distributions(local_only=local_only): req = pip.FrozenRequirement.from_dist(dist, dependency_links, find_tags=find_tags) installations[req.name] = req if requirement: diff --git a/pip/req.py b/pip/req.py index 1b4d65de7..907d447bf 100644 --- a/pip/req.py +++ b/pip/req.py @@ -19,7 +19,7 @@ from pip.log import logger from pip.util import display_path, rmtree, format_size from pip.util import splitext, ask, backup_dir from pip.util import url_to_filename, filename_to_url -from pip.util import is_url, is_filename, is_local +from pip.util import is_url, is_filename, is_local, dist_is_local from pip.util import renames, normalize_path, egg_link_path from pip.util import make_path_relative, is_svn_page, file_contents from pip.util import has_leading_dir, split_leading_dir @@ -421,8 +421,6 @@ execfile(__file__) easy_install_pth = os.path.join(os.path.dirname(develop_egg_link), 'easy-install.pth') paths_to_remove.add_pth(easy_install_pth, dist.location) - # fix location (so we can uninstall links to sources outside venv) - paths_to_remove.location = normalize_path(develop_egg_link) # find distutils scripts= scripts if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'): @@ -1368,20 +1366,19 @@ class UninstallPathSet(object): self._refuse = set() self.pth = {} self.dist = dist - self.location = normalize_path(dist.location) self.save_dir = None self._moved_paths = [] def _permitted(self, path): """ - Return True if the given path is one we are permitted to remove, - False otherwise. + Return True if the given path is one we are permitted to + remove/modify, False otherwise. """ return is_local(path) def _can_uninstall(self): - if not self._permitted(self.location): + if not dist_is_local(self.dist): logger.notify("Not uninstalling %s at %s, outside environment %s" % (self.dist.project_name, self.location, sys.prefix)) return False diff --git a/pip/util.py b/pip/util.py index 0f652d86f..2462a742c 100644 --- a/pip/util.py +++ b/pip/util.py @@ -6,6 +6,9 @@ import stat import urllib import urllib2 import re + +import pkg_resources + from pip.backwardcompat import WindowsError from pip.exceptions import InstallationError from pip.locations import site_packages @@ -323,6 +326,34 @@ def is_local(path): return True return normalize_path(path).startswith(normalize_path(sys.prefix)) +def dist_is_local(dist): + """ + Return True if given Distribution object is installed locally + (i.e. within current virtualenv). + + Always True if we're not in a virtualenv. + + """ + return is_local(dist_location(dist)) + +def get_installed_distributions(local_only=True, skip=('setuptools', 'pip', 'python')): + """ + Return a list of installed Distribution objects. + + If ``local_only`` is True (default), only return installations + local to the current virtualenv, if in a virtualenv. + + ``skip`` argument is an iterable of lower-case project names to + ignore; defaults to ('setuptools', 'pip', 'python'). [FIXME also + skip virtualenv?] + + """ + if local_only: + local_test = dist_is_local + else: + local_test = lambda d: True + return [d for d in pkg_resources.working_set if local_test(d) and d.key not in skip] + def egg_link_path(dist): """ Return the path where we'd expect to find a .egg-link file for