Commits

Arc Riley committed be24009 Merge

Branch merge to trunk

  • Participants
  • Parent commits f19a726, 677266d

Comments (0)

Files changed (118)

 include
 bin
 nosetests.xml
+Distutils2.egg-info
 ---------
 
 - The setup runner supports more options:
-- XXX fill changes done in commands + compilers
-- Issue 10409: Fixed the Licence selector in mkcfg
+- XXX fill changes done in commands + compilers [tarek]
+- Issue #10409: Fixed the Licence selector in mkcfg [tarek]
+- Issue #9558: Fix build_ext with VS 8.0 [éric]
+- Issue #6007: Add disclaimer about MinGW compatibility in docs [éric]
+- Renamed DistributionMetadata to Metadata [ccomb]
 
 1.0a3 - 2010-10-08
 ------------------
 
-- Provided a Tox configuration for cross-python testing [holger]
+- Provided a Tox configuration for cross-Python testing [holger]
 - Fixed the installation when using easy_install and Pip by switching
   setup.py to distutils1 [holger/tarek]
 - Added missing c/h files in the MANIFEST so they are always present
-  no matter which python version was used to build it. [holger/tarek]
+  no matter which Python version was used to build it. [holger/tarek]
 - Added the new setup runner that uses only setup.cfg
 - Renamed mkpkg to mkcfg [tarek]
 - Renamed install_tools to install [alexis]

File CONTRIBUTORS.txt

 - Nicolas Cadou
 - Konrad Delong
 - Josip Djolonga
+- Andrew Francis
 - Yannick Gingras
+- Alexandre Hamelin
+- Kelsey Hightower
+- Christian Hudon
 - Jeremy Kloth
 - Amos Latteier
+- Mathieu Leduc-Hamel
 - Martin von Löwis
+- Simon Mathieu
 - Carl Meyer
 - Alexis Métaireau
 - Zubin Mithra
 - Derek McTavish Mounce
 - Michael Mulich
+- Louis Munro
 - George Peristerakis
 - Mathieu Perreault
 - Sean Reifschneider

File DEVNOTES.txt

   one of these Python versions.
 
 - Always run tests.sh before you push a change. This implies
-  that you have all Python versions installed from 2.4 to 2.7.
+  that you have all Python versions installed from 2.4 to 2.7. Be sure to have 
+  docutils installed on all python versions no avoid skipping tests as well.
 
 See the documentation at http://packages.python.org/Distutils2 for more info.
 
+If you want to contribute, please have a look to 
+http://distutils2.notmyidea.org/contributing.html
+
 **Beware that Distutils2 is in its early stage and should not be used in
 production. Its API is subject to changes**
 

File distutils2/__init__.py

 
 __version__ = "1.0a3"
 logger = getLogger('distutils2')
-
-# when set to True, converts doctests by default too
-run_2to3_on_doctests = True
-# Standard package names for fixer packages
-lib2to3_fixer_packages = ['lib2to3.fixes']

File distutils2/_backport/__init__.py

 """Things that will land in the Python 3.3 std lib but which we must drag along
-with us for now to support 2.x."""
+ us for now to support 2.x."""
 
 def any(seq):
     for elem in seq:

File distutils2/_backport/pkgutil.py

 """Utilities to support packages."""
 
-# NOTE: This module must remain compatible with Python 2.3, as it is shared
-# by setuptools for distribution with Python 2.3 and up.
+import imp
+import sys
 
+from csv import reader as csv_reader
 import os
-import sys
-import imp
-import os.path
-from csv import reader as csv_reader
+import re
+from stat import ST_SIZE
 from types import ModuleType
+import warnings
+
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
 from distutils2.errors import DistutilsError
-from distutils2.metadata import DistributionMetadata
+from distutils2.metadata import Metadata
 from distutils2.version import suggest_normalized_version, VersionPredicate
-import zipimport
 try:
     import cStringIO as StringIO
 except ImportError:
     import StringIO
-import re
-import warnings
 
 
 __all__ = [
     'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
     'get_distributions', 'get_distribution', 'get_file_users',
     'provides_distribution', 'obsoletes_distribution',
-    'enable_cache', 'disable_cache', 'clear_cache'
+    'enable_cache', 'disable_cache', 'clear_cache',
 ]
 
 
+##########################
+# PEP 302 Implementation #
+##########################
+
 def read_code(stream):
     # This helper is needed in order for the :pep:`302` emulation to
     # correctly handle compiled files
     if magic != imp.get_magic():
         return None
 
-    stream.read(4) # Skip timestamp
+    stream.read(4)  # Skip timestamp
     return marshal.load(stream)
 
 
     """Make a trivial single-dispatch generic function"""
     registry = {}
 
-    def wrapper(*args, **kw):
+    def wrapper(*args, ** kw):
         ob = args[0]
         try:
             cls = ob.__class__
                     pass
                 mro = cls.__mro__[1:]
             except TypeError:
-                mro = object,   # must be an ExtensionClass or some such  :(
+                mro = object, # must be an ExtensionClass or some such  :(
         for t in mro:
             if t in registry:
-                return registry[t](*args, **kw)
+                return registry[t](*args, ** kw)
         else:
-            return func(*args, **kw)
+            return func(*args, ** kw)
     try:
         wrapper.__name__ = func.__name__
     except (TypeError, AttributeError):
 
 #@simplegeneric
 def iter_importer_modules(importer, prefix=''):
-    ""
     if not hasattr(importer, 'iter_modules'):
         return []
     return importer.iter_modules(prefix)
     def get_filename(self, fullname=None):
         fullname = self._fix_name(fullname)
         mod_type = self.etc[2]
-        if self.etc[2] == imp.PKG_DIRECTORY:
+        if mod_type == imp.PKG_DIRECTORY:
             return self._get_delegate().get_filename()
-        elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
+        elif mod_type in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
             return self.filename
         return None
 
     from zipimport import zipimporter
 
     def iter_zipimport_modules(importer, prefix=''):
-        dirlist = zipimport._zip_directory_cache[importer.archive].keys()
-        dirlist.sort()
+        dirlist = sorted(zipimport._zip_directory_cache[importer.archive])
         _prefix = importer.prefix
         plen = len(_prefix)
         yielded = {}
     import mechanism will find the latter.
 
     Items of the following types can be affected by this discrepancy:
-        ``imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY``
+    :data:`imp.C_EXTENSION`, :data:`imp.PY_SOURCE`, :data:`imp.PY_COMPILED`,
+    :data:`imp.PKG_DIRECTORY`
     """
     if fullname.startswith('.'):
         raise ImportError("Relative module names not supported")
         # frozen package.  Return the path unchanged in that case.
         return path
 
-    pname = os.path.join(*name.split('.')) # Reconstitute as relative path
+    pname = os.path.join(*name.split('.'))  # Reconstitute as relative path
     # Just in case os.extsep != '.'
     sname = os.extsep.join(name.split('.'))
     sname_pkg = sname + os.extsep + "pkg"
     init_py = "__init__" + os.extsep + "py"
 
-    path = path[:] # Start with a copy of the existing path
+    path = path[:]  # Start with a copy of the existing path
 
     for dir in sys.path:
         if not isinstance(dir, basestring) or not os.path.isdir(dir):
                     line = line.rstrip('\n')
                     if not line or line.startswith('#'):
                         continue
-                    path.append(line) # Don't check for existence!
+                    path.append(line)  # Don't check for existence!
                 f.close()
 
     return path
     resource_name = os.path.join(*parts)
     return loader.get_data(resource_name)
 
+
 ##########################
 # PEP 376 Implementation #
 ##########################
 
-DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
+DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED', 'RESOURCES')
 
 # Cache
-_cache_name = {} # maps names to Distribution instances
-_cache_name_egg = {} # maps names to EggInfoDistribution instances
-_cache_path = {} # maps paths to Distribution instances
-_cache_path_egg = {} # maps paths to EggInfoDistribution instances
-_cache_generated = False # indicates if .dist-info distributions are cached
-_cache_generated_egg = False # indicates if .dist-info and .egg are cached
+_cache_name = {}  # maps names to Distribution instances
+_cache_name_egg = {}  # maps names to EggInfoDistribution instances
+_cache_path = {}  # maps paths to Distribution instances
+_cache_path_egg = {}  # maps paths to EggInfoDistribution instances
+_cache_generated = False  # indicates if .dist-info distributions are cached
+_cache_generated_egg = False  # indicates if .dist-info and .egg are cached
 _cache_enabled = True
 
 
 
     _cache_enabled = True
 
+
 def disable_cache():
     """
     Disables the internal cache.
 
     _cache_enabled = False
 
+
 def clear_cache():
     """ Clears the internal cache. """
-    global _cache_name, _cache_name_egg, cache_path, _cache_path_egg, \
-           _cache_generated, _cache_generated_egg
+    global _cache_name, _cache_name_egg, _cache_path, _cache_path_egg, \
+        _cache_generated, _cache_generated_egg
 
     _cache_name = {}
     _cache_name_egg = {}
     _cache_generated_egg = False
 
 
-def _yield_distributions(include_dist, include_egg):
+def _yield_distributions(include_dist, include_egg, paths=sys.path):
     """
     Yield .dist-info and .egg(-info) distributions, based on the arguments
 
     :parameter include_dist: yield .dist-info distributions
     :parameter include_egg: yield .egg(-info) distributions
     """
-    for path in sys.path:
+    for path in paths:
         realpath = os.path.realpath(path)
         if not os.path.isdir(realpath):
             continue
                                   dir.endswith('.egg')):
                 yield EggInfoDistribution(dist_path)
 
-
-def _generate_cache(use_egg_info=False):
+def _generate_cache(use_egg_info=False, paths=sys.path):
     global _cache_generated, _cache_generated_egg
 
     if _cache_generated_egg or (_cache_generated and not use_egg_info):
         gen_dist = not _cache_generated
         gen_egg = use_egg_info
 
-        for dist in _yield_distributions(gen_dist, gen_egg):
+        for dist in _yield_distributions(gen_dist, gen_egg, paths):
             if isinstance(dist, Distribution):
                 _cache_path[dist.path] = dist
                 if not dist.name in _cache_name:
     name = ''
     """The name of the distribution."""
     metadata = None
-    """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
+    """A :class:`distutils2.metadata.Metadata` instance loaded with
     the distribution's ``METADATA`` file."""
     requested = False
     """A boolean that indicates whether the ``REQUESTED`` metadata file is
             self.metadata = _cache_path[path].metadata
         else:
             metadata_path = os.path.join(path, 'METADATA')
-            self.metadata = DistributionMetadata(path=metadata_path)
+            self.metadata = Metadata(path=metadata_path)
 
         self.path = path
         self.name = self.metadata['name']
         if _cache_enabled and not path in _cache_path:
             _cache_path[path] = self
 
+    def __repr__(self):
+        return '%s-%s at %s' % (self.name, self.metadata.version, self.path)
+
     def _get_records(self, local=False):
-        RECORD = os.path.join(self.path, 'RECORD')
-        record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',')
+        RECORD = self.get_distinfo_file('RECORD')
+        record_reader = csv_reader(RECORD, delimiter=',')
         for row in record_reader:
             path, md5, size = row[:] + [None for i in xrange(len(row), 3)]
             if local:
                 path = os.path.join(sys.prefix, path)
             yield path, md5, size
 
+    def get_resource_path(self, relative_path):
+        resources_file = self.get_distinfo_file('RESOURCES')
+        resources_reader = csv_reader(resources_file, delimiter=',')
+        for relative, destination in resources_reader:
+            if relative == relative_path:
+                return destination
+        raise KeyError('No resource file with relative path %s were installed' %
+                       relative_path)
+
     def get_installed_files(self, local=False):
         """
         Iterates over the ``RECORD`` entries and returns a tuple
             distinfo_dirname, path = path.split(os.sep)[-2:]
             if distinfo_dirname != self.path.split(os.sep)[-1]:
                 raise DistutilsError("Requested dist-info file does not "
-                    "belong to the %s distribution. '%s' was requested." \
-                    % (self.name, os.sep.join([distinfo_dirname, path])))
+                                     "belong to the %s distribution. '%s' was requested." \
+                                     % (self.name, os.sep.join([distinfo_dirname, path])))
 
         # The file must be relative
         if path not in DIST_FILES:
             raise DistutilsError("Requested an invalid dist-info file: "
-                "%s" % path)
+                                 "%s" % path)
 
         # Convert the relative path back to absolute
         path = os.path.join(self.path, path)
     name = ''
     """The name of the distribution."""
     metadata = None
-    """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with
+    """A :class:`distutils2.metadata.Metadata` instance loaded with
     the distribution's ``METADATA`` file."""
-    _REQUIREMENT = re.compile( \
-        r'(?P<name>[-A-Za-z0-9_.]+)\s*' \
-        r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' \
-        r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' \
-        r'(?P<extras>\[.*\])?')
+    _REQUIREMENT = re.compile(\
+                              r'(?P<name>[-A-Za-z0-9_.]+)\s*' \
+                              r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' \
+                              r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' \
+                              r'(?P<extras>\[.*\])?')
 
-    def __init__(self, path):
+    def __init__(self, path, display_warnings=False):
         self.path = path
-
+        self.display_warnings = display_warnings
         if _cache_enabled and path in _cache_path_egg:
             self.metadata = _cache_path_egg[path].metadata
             self.name = self.metadata['Name']
             if isinstance(strs, basestring):
                 for s in strs.splitlines():
                     s = s.strip()
-                    if s and not s.startswith('#'): # skip blank lines/comments
+                    # skip blank lines/comments
+                    if s and not s.startswith('#'):
                         yield s
             else:
                 for ss in strs:
         if path.endswith('.egg'):
             if os.path.isdir(path):
                 meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
-                self.metadata = DistributionMetadata(path=meta_path)
+                self.metadata = Metadata(path=meta_path)
                 try:
                     req_path = os.path.join(path, 'EGG-INFO', 'requires.txt')
                     requires = open(req_path, 'r').read()
                 except IOError:
                     requires = None
             else:
+                # FIXME handle the case where zipfile is not available
                 zipf = zipimport.zipimporter(path)
                 fileobj = StringIO.StringIO(zipf.get_data('EGG-INFO/PKG-INFO'))
-                self.metadata = DistributionMetadata(fileobj=fileobj)
+                self.metadata = Metadata(fileobj=fileobj)
                 try:
                     requires = zipf.get_data('EGG-INFO/requires.txt')
                 except IOError:
                     requires = req_f.read()
                 except IOError:
                     requires = None
-            self.metadata = DistributionMetadata(path=path)
+            self.metadata = Metadata(path=path)
             self.name = self.metadata['name']
         else:
             raise ValueError('The path must end with .egg-info or .egg')
 
-        provides = "%s (%s)" % (self.metadata['name'],
-                                self.metadata['version'])
-        if self.metadata['Metadata-Version'] == '1.2':
-            self.metadata['Provides-Dist'] += (provides,)
-        else:
-            self.metadata['Provides'] += (provides,)
+
+        if requires is not None:
+            if self.metadata['Metadata-Version'] == '1.1':
+                # we can't have 1.1 metadata *and* Setuptools requires
+                for field in ('Obsoletes', 'Requires', 'Provides'):
+                    del self.metadata[field]
+
         reqs = []
+
         if requires is not None:
             for line in yield_lines(requires):
-                if line[0] == '[':
+                if line[0] == '[' and self.display_warnings:
                     warnings.warn('distutils2 does not support extensions '
                                   'in requires.txt')
                     break
                 else:
                     match = self._REQUIREMENT.match(line.strip())
                     if not match:
-                        raise ValueError('Distribution %s has ill formed '
-                                         'requires.txt file (%s)' %
-                                         (self.name, line))
+                        # this happens when we encounter extras
+                        # since they are written at the end of the file
+                        # we just exit
+                        break
+                        #raise ValueError('Distribution %s has ill formed '
+                        #                 'requires.txt file (%s)' %
+                        #                 (self.name, line))
                     else:
                         if match.group('extras'):
                             s = (('Distribution %s uses extra requirements '
-                                  'which are not supported in distutils') \
-                                         % (self.name))
+                                 'which are not supported in distutils') \
+                                 % (self.name))
                             warnings.warn(s)
                         name = match.group('name')
                         version = None
                             version = match.group('first')
                             if match.group('rest'):
                                 version += match.group('rest')
-                            version = version.replace(' ', '') # trim spaces
+                            version = version.replace(' ', '')  # trim spaces
                         if version is None:
                             reqs.append(name)
                         else:
                             reqs.append('%s (%s)' % (name, version))
-            if self.metadata['Metadata-Version'] == '1.2':
+
+            if len(reqs) > 0:
                 self.metadata['Requires-Dist'] += reqs
-            else:
-                self.metadata['Requires'] += reqs
+
 
         if _cache_enabled:
             _cache_path_egg[self.path] = self
 
+    def __repr__(self):
+        return '%s-%s at %s' % (self.name, self.metadata.version, self.path)
+
     def get_installed_files(self, local=False):
+
+        def _md5(path):
+            f = open(path)
+            try:
+                content = f.read()
+            finally:
+                f.close()
+            return md5(content).hexdigest()
+
+        def _size(path):
+            return os.stat(path)[ST_SIZE]
+
+        path = self.path
+        if local:
+            path = path.replace('/', os.sep)
+
+        # XXX What about scripts and data files ?
+        if os.path.isfile(path):
+            return [(path, _md5(path), _size(path))]
+        else:
+            files = []
+            for root, dir, files_ in os.walk(path):
+                for item in files_:
+                    item = os.path.join(root, item)
+                    files.append((item, _md5(item), _size(item)))
+            return files
+
         return []
 
     def uses(self, path):
 
     def __eq__(self, other):
         return isinstance(other, EggInfoDistribution) and \
-               self.path == other.path
+            self.path == other.path
 
     # See http://docs.python.org/reference/datamodel#object.__hash__
     __hash__ = object.__hash__
 
 
-def _normalize_dist_name(name):
-    """Returns a normalized name from the given *name*.
-    :rtype: string"""
-    return name.replace('-', '_')
-
-
 def distinfo_dirname(name, version):
     """
     The *name* and *version* parameters are converted into their
     :returns: directory name
     :rtype: string"""
     file_extension = '.dist-info'
-    name = _normalize_dist_name(name)
+    name = name.replace('-', '_')
     normalized_version = suggest_normalized_version(version)
     # Because this is a lookup procedure, something will be returned even if
     #   it is a version that cannot be normalized
     return '-'.join([name, normalized_version]) + file_extension
 
 
-def get_distributions(use_egg_info=False):
+def get_distributions(use_egg_info=False, paths=sys.path):
     """
     Provides an iterator that looks for ``.dist-info`` directories in
     ``sys.path`` and returns :class:`Distribution` instances for each one of
             instances
     """
     if not _cache_enabled:
-        for dist in _yield_distributions(True, use_egg_info):
+        for dist in _yield_distributions(True, use_egg_info, paths):
             yield dist
     else:
-        _generate_cache(use_egg_info)
+        _generate_cache(use_egg_info, paths)
 
         for dist in _cache_path.itervalues():
             yield dist
                 yield dist
 
 
-def get_distribution(name, use_egg_info=False):
+def get_distribution(name, use_egg_info=False, paths=None):
     """
     Scans all elements in ``sys.path`` and looks for all directories
     ending with ``.dist-info``. Returns a :class:`Distribution`
 
     :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None
     """
+    if paths == None:
+        paths = sys.path
+
     if not _cache_enabled:
-        for dist in _yield_distributions(True, use_egg_info):
+        for dist in _yield_distributions(True, use_egg_info, paths):
             if dist.name == name:
                 return dist
     else:
-        _generate_cache(use_egg_info)
+        _generate_cache(use_egg_info, paths)
 
         if name in _cache_name:
             return _cache_name[name][0]
                     predicate = VersionPredicate(obs)
                 except ValueError:
                     raise DistutilsError(('Distribution %s has ill formed' +
-                                          ' obsoletes field') % (dist.name,))
+                                         ' obsoletes field') % (dist.name,))
                 if name == o_components[0] and predicate.match(version):
                     yield dist
                     break
                 p_name, p_ver = p_components
                 if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')':
                     raise DistutilsError(('Distribution %s has invalid ' +
-                                          'provides field: %s') \
-                                           % (dist.name, p))
-                p_ver = p_ver[1:-1] # trim off the parenthesis
+                                         'provides field: %s') \
+                                         % (dist.name, p))
+                p_ver = p_ver[1:-1]  # trim off the parenthesis
                 if p_name == name and predicate.match(p_ver):
                     yield dist
                     break
     for dist in get_distributions():
         if dist.uses(path):
             yield dist
+
+def resource_path(distribution_name, relative_path):
+    dist = get_distribution(distribution_name)
+    if dist != None:
+        return dist.get_resource_path(relative_path)
+    raise LookupError('No distribution named %s is installed.' %
+                      distribution_name)
+
+def resource_open(distribution_name, relative_path, * args, ** kwargs):
+    file = open(resource_path(distribution_name, relative_path), * args,
+                ** kwargs)
+    return file

File distutils2/_backport/shutil.py

-"""Utility functions for copying files and directory trees.
+"""Utility functions for copying and archiving files and directory trees.
 
 XXX The functions here don't copy the resource fork or other metadata on Mac.
 
 import stat
 from os.path import abspath
 import fnmatch
-from warnings import warn
+import errno
+
+try:
+    import bz2
+    _BZ2_SUPPORTED = True
+except ImportError:
+    _BZ2_SUPPORTED = False
 
 try:
     from pwd import getpwnam
 except ImportError:
     getgrnam = None
 
-__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
-           "copytree","move","rmtree","Error", "SpecialFileError",
-           "ExecError","make_archive"]
+__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
+           "copytree", "move", "rmtree", "Error", "SpecialFileError",
+           "ExecError", "make_archive", "get_archive_formats",
+           "register_archive_format", "unregister_archive_format",
+           "get_unpack_formats", "register_unpack_format",
+           "unregister_unpack_format", "unpack_archive"]
 
 class Error(EnvironmentError):
     pass
 class ExecError(EnvironmentError):
     """Raised when a command could not be executed"""
 
+class ReadError(EnvironmentError):
+    """Raised when an archive cannot be read"""
+
+class RegistryError(Exception):
+    """Raised when a registery operation with the archiving
+    and unpacking registeries fails"""
+
+
 try:
     WindowsError
 except NameError:
 
 def _samefile(src, dst):
     # Macintosh, Unix.
-    if hasattr(os.path,'samefile'):
+    if hasattr(os.path, 'samefile'):
         try:
             return os.path.samefile(src, dst)
         except OSError:
 def copyfile(src, dst):
     """Copy data from src to dst"""
     if _samefile(src, dst):
-        raise Error, "`%s` and `%s` are the same file" % (src, dst)
+        raise Error("`%s` and `%s` are the same file" % (src, dst))
 
-    fsrc = None
-    fdst = None
     for fn in [src, dst]:
         try:
             st = os.stat(fn)
             # XXX What about other special files? (sockets, devices...)
             if stat.S_ISFIFO(st.st_mode):
                 raise SpecialFileError("`%s` is a named pipe" % fn)
+
+    fsrc = open(src, 'rb')
     try:
-        fsrc = open(src, 'rb')
         fdst = open(dst, 'wb')
-        copyfileobj(fsrc, fdst)
+        try:
+            copyfileobj(fsrc, fdst)
+        finally:
+            fdst.close()
     finally:
-        if fdst:
-            fdst.close()
-        if fsrc:
-            fsrc.close()
+        fsrc.close()
 
 def copymode(src, dst):
     """Copy mode bits from src to dst"""
     if hasattr(os, 'chmod'):
         os.chmod(dst, mode)
     if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
-        os.chflags(dst, st.st_flags)
-
+        try:
+            os.chflags(dst, st.st_flags)
+        except OSError, why:
+            if (not hasattr(errno, 'EOPNOTSUPP') or
+                why.errno != errno.EOPNOTSUPP):
+                raise
 
 def copy(src, dst):
     """Copy data and mode bits ("cp src dst").
         return set(ignored_names)
     return _ignore_patterns
 
-def copytree(src, dst, symlinks=False, ignore=None):
-    """Recursively copy a directory tree using copy2().
+def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
+             ignore_dangling_symlinks=False):
+    """Recursively copy a directory tree.
 
     The destination directory must not already exist.
     If exception(s) occur, an Error is raised with a list of reasons.
     If the optional symlinks flag is true, symbolic links in the
     source tree result in symbolic links in the destination tree; if
     it is false, the contents of the files pointed to by symbolic
-    links are copied.
+    links are copied. If the file pointed by the symlink doesn't
+    exist, an exception will be added in the list of errors raised in
+    an Error exception at the end of the copy process.
+
+    You can set the optional ignore_dangling_symlinks flag to true if you
+    want to silence this exception. Notice that this has no effect on
+    platforms that don't support os.symlink.
 
     The optional ignore argument is a callable. If given, it
     is called with the `src` parameter, which is the directory
     list of names relative to the `src` directory that should
     not be copied.
 
-    XXX Consider this example code rather than the ultimate tool.
+    The optional copy_function argument is a callable that will be used
+    to copy each file. It will be called with the source path and the
+    destination path as arguments. By default, copy2() is used, but any
+    function that supports the same signature (like copy()) can be used.
 
     """
     names = os.listdir(src)
         srcname = os.path.join(src, name)
         dstname = os.path.join(dst, name)
         try:
-            if symlinks and os.path.islink(srcname):
+            if os.path.islink(srcname):
                 linkto = os.readlink(srcname)
-                os.symlink(linkto, dstname)
+                if symlinks:
+                    os.symlink(linkto, dstname)
+                else:
+                    # ignore dangling symlink if the flag is on
+                    if not os.path.exists(linkto) and ignore_dangling_symlinks:
+                        continue
+                    # otherwise let the copy occurs. copy2 will raise an error
+                    copy_function(srcname, dstname)
             elif os.path.isdir(srcname):
-                copytree(srcname, dstname, symlinks, ignore)
+                copytree(srcname, dstname, symlinks, ignore, copy_function)
             else:
                 # Will raise a SpecialFileError for unsupported file types
-                copy2(srcname, dstname)
+                copy_function(srcname, dstname)
         # catch the Error from the recursive copytree so that we can
         # continue with other files
         except Error, err:
         else:
             errors.extend((src, dst, str(why)))
     if errors:
-        raise Error, errors
+        raise Error(errors)
 
 def rmtree(path, ignore_errors=False, onerror=None):
     """Recursively delete a directory tree.
     names = []
     try:
         names = os.listdir(path)
-    except os.error, err:
+    except os.error:
         onerror(os.listdir, path, sys.exc_info())
     for name in names:
         fullname = os.path.join(path, name)
         else:
             try:
                 os.remove(fullname)
-            except os.error, err:
+            except os.error:
                 onerror(os.remove, fullname, sys.exc_info())
     try:
         os.rmdir(path)
     if os.path.isdir(dst):
         real_dst = os.path.join(dst, _basename(src))
         if os.path.exists(real_dst):
-            raise Error, "Destination path '%s' already exists" % real_dst
+            raise Error("Destination path '%s' already exists" % real_dst)
     try:
         os.rename(src, real_dst)
     except OSError:
         if os.path.isdir(src):
             if _destinsrc(src, dst):
-                raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
+                raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
             copytree(src, real_dst, symlinks=True)
             rmtree(src)
         else:
     """Create a (possibly compressed) tar file from all the files under
     'base_dir'.
 
-    'compress' must be "gzip" (the default), "compress", "bzip2", or None.
-    (compress will be deprecated in Python 3.2)
+    'compress' must be "gzip" (the default), "bzip2", or None.
 
     'owner' and 'group' can be used to define an owner and a group for the
     archive that is being built. If not provided, the current owner and group
     will be used.
 
-    The output tar file will be named 'base_dir' +  ".tar", possibly plus
-    the appropriate compression extension (".gz", ".bz2" or ".Z").
+    The output tar file will be named 'base_name' +  ".tar", possibly plus
+    the appropriate compression extension (".gz", or ".bz2").
 
     Returns the output filename.
     """
-    tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
-    compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
+    tar_compression = {'gzip': 'gz', None: ''}
+    compress_ext = {'gzip': '.gz'}
+
+    if _BZ2_SUPPORTED:
+        tar_compression['bzip2'] = 'bz2'
+        compress_ext['bzip2'] = '.bz2'
 
     # flags for compression program, each element of list will be an argument
-    if compress is not None and compress not in compress_ext.keys():
-        raise ValueError, \
-              ("bad value for 'compress': must be None, 'gzip', 'bzip2' "
-               "or 'compress'")
+    if compress is not None and compress not in compress_ext:
+        raise ValueError("bad value for 'compress', or compression format not "
+                         "supported: %s" % compress)
 
-    archive_name = base_name + '.tar'
-    if compress != 'compress':
-        archive_name += compress_ext.get(compress, '')
+    archive_name = base_name + '.tar' + compress_ext.get(compress, '')
+    archive_dir = os.path.dirname(archive_name)
 
-    archive_dir = os.path.dirname(archive_name)
     if not os.path.exists(archive_dir):
         if logger is not None:
-            logger.info("creating %s" % archive_dir)
+            logger.info("creating %s", archive_dir)
         if not dry_run:
             os.makedirs(archive_dir)
 
-
     # creating the tarball
+    # XXX late import because of circular dependency between shutil and
+    # tarfile :(
     from distutils2._backport import tarfile
 
     if logger is not None:
-        logger.info('Creating tar archive')
+        logger.info('creating tar archive')
 
     uid = _get_uid(owner)
     gid = _get_gid(group)
         finally:
             tar.close()
 
-    # compression using `compress`
-    # XXX this block will be removed in Python 3.2
-    if compress == 'compress':
-        warn("'compress' will be deprecated.", PendingDeprecationWarning)
-        # the option varies depending on the platform
-        compressed_name = archive_name + compress_ext[compress]
-        if sys.platform == 'win32':
-            cmd = [compress, archive_name, compressed_name]
-        else:
-            cmd = [compress, '-f', archive_name]
-        from distutils2.spawn import spawn
-        spawn(cmd, dry_run=dry_run)
-        return compressed_name
-
     return archive_name
 
-def _call_external_zip(directory, verbose=False):
+def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
     # XXX see if we want to keep an external call here
     if verbose:
         zipoptions = "-r"
     except DistutilsExecError:
         # XXX really should distinguish between "couldn't find
         # external 'zip' command" and "zip failed".
-        raise ExecError, \
-            ("unable to create zip file '%s': "
+        raise ExecError("unable to create zip file '%s': "
             "could neither import the 'zipfile' module nor "
             "find a standalone zip utility") % zip_filename
 
 def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
     """Create a zip file from all the files under 'base_dir'.
 
-    The output zip file will be named 'base_dir' + ".zip".  Uses either the
+    The output zip file will be named 'base_name' + ".zip".  Uses either the
     "zipfile" Python module (if available) or the InfoZIP "zip" utility
     (if installed and found on the default search path).  If neither tool is
     available, raises ExecError.  Returns the name of the output zip
         zipfile = None
 
     if zipfile is None:
-        _call_external_zip(base_dir, verbose)
+        _call_external_zip(base_dir, zip_filename, verbose, dry_run)
     else:
         if logger is not None:
             logger.info("creating '%s' and adding '%s' to it",
 _ARCHIVE_FORMATS = {
     'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
     'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
-    'ztar':  (_make_tarball, [('compress', 'compress')],
-                "compressed tar file"),
     'tar':   (_make_tarball, [('compress', None)], "uncompressed tar file"),
-    'zip':   (_make_zipfile, [],"ZIP file")
+    'zip':   (_make_zipfile, [], "ZIP file"),
     }
 
+if _BZ2_SUPPORTED:
+    _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
+                                "bzip2'ed tar-file")
+
 def get_archive_formats():
     """Returns a list of supported formats for archiving and unarchiving.
 
     Each element of the returned sequence is a tuple (name, description)
     """
     formats = [(name, registry[2]) for name, registry in
-               _ARCHIVE_FORMATS.items()]
+               _ARCHIVE_FORMATS.iteritems()]
     formats.sort()
     return formats
 
     if not isinstance(extra_args, (tuple, list)):
         raise TypeError('extra_args needs to be a sequence')
     for element in extra_args:
-        if not isinstance(element, (tuple, list)) or len(element) !=2 :
+        if not isinstance(element, (tuple, list)) or len(element) !=2:
             raise TypeError('extra_args elements are : (arg_name, value)')
 
     _ARCHIVE_FORMATS[name] = (function, extra_args, description)
     """Create an archive file (eg. zip or tar).
 
     'base_name' is the name of the file to create, minus any format-specific
-    extension; 'format' is the archive format: one of "zip", "tar", "ztar",
+    extension; 'format' is the archive format: one of "zip", "tar", "bztar"
     or "gztar".
 
     'root_dir' is a directory that will be the root directory of the
     try:
         format_info = _ARCHIVE_FORMATS[format]
     except KeyError:
-        raise ValueError, "unknown archive format '%s'" % format
+        raise ValueError("unknown archive format '%s'" % format)
 
     func = format_info[0]
     for arg, val in format_info[1]:
             os.chdir(save_cwd)
 
     return filename
+
+
+def get_unpack_formats():
+    """Returns a list of supported formats for unpacking.
+
+    Each element of the returned sequence is a tuple
+    (name, extensions, description)
+    """
+    formats = [(name, info[0], info[3]) for name, info in
+               _UNPACK_FORMATS.iteritems()]
+    formats.sort()
+    return formats
+
+def _check_unpack_options(extensions, function, extra_args):
+    """Checks what gets registered as an unpacker."""
+    # first make sure no other unpacker is registered for this extension
+    existing_extensions = {}
+    for name, info in _UNPACK_FORMATS.iteritems():
+        for ext in info[0]:
+            existing_extensions[ext] = name
+
+    for extension in extensions:
+        if extension in existing_extensions:
+            msg = '%s is already registered for "%s"'
+            raise RegistryError(msg % (extension,
+                                       existing_extensions[extension]))
+
+    if not callable(function):
+        raise TypeError('The registered function must be a callable')
+
+
+def register_unpack_format(name, extensions, function, extra_args=None,
+                           description=''):
+    """Registers an unpack format.
+
+    `name` is the name of the format. `extensions` is a list of extensions
+    corresponding to the format.
+
+    `function` is the callable that will be
+    used to unpack archives. The callable will receive archives to unpack.
+    If it's unable to handle an archive, it needs to raise a ReadError
+    exception.
+
+    If provided, `extra_args` is a sequence of
+    (name, value) tuples that will be passed as arguments to the callable.
+    description can be provided to describe the format, and will be returned
+    by the get_unpack_formats() function.
+    """
+    if extra_args is None:
+        extra_args = []
+    _check_unpack_options(extensions, function, extra_args)
+    _UNPACK_FORMATS[name] = extensions, function, extra_args, description
+
+def unregister_unpack_format(name):
+    """Removes the pack format from the registery."""
+    del _UNPACK_FORMATS[name]
+
+def _ensure_directory(path):
+    """Ensure that the parent directory of `path` exists"""
+    dirname = os.path.dirname(path)
+    if not os.path.isdir(dirname):
+        os.makedirs(dirname)
+
+def _unpack_zipfile(filename, extract_dir):
+    """Unpack zip `filename` to `extract_dir`
+    """
+    try:
+        import zipfile
+    except ImportError:
+        raise ReadError('zlib not supported, cannot unpack this archive.')
+
+    if not zipfile.is_zipfile(filename):
+        raise ReadError("%s is not a zip file" % filename)
+
+    zip = zipfile.ZipFile(filename)
+    try:
+        for info in zip.infolist():
+            name = info.filename
+
+            # don't extract absolute paths or ones with .. in them
+            if name.startswith('/') or '..' in name:
+                continue
+
+            target = os.path.join(extract_dir, *name.split('/'))
+            if not target:
+                continue
+
+            _ensure_directory(target)
+            if not name.endswith('/'):
+                # file
+                data = zip.read(info.filename)
+                f = open(target, 'wb')
+                try:
+                    f.write(data)
+                finally:
+                    f.close()
+                    del data
+    finally:
+        zip.close()
+
+def _unpack_tarfile(filename, extract_dir):
+    """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
+    """
+    from distutils2._backport import tarfile
+    try:
+        tarobj = tarfile.open(filename)
+    except tarfile.TarError:
+        raise ReadError(
+            "%s is not a compressed or uncompressed tar file" % filename)
+    try:
+        tarobj.extractall(extract_dir)
+    finally:
+        tarobj.close()
+
+_UNPACK_FORMATS = {
+    'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
+    'tar':   (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
+    'zip':   (['.zip'], _unpack_zipfile, [], "ZIP file")
+    }
+
+if _BZ2_SUPPORTED:
+    _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
+                                "bzip2'ed tar-file")
+
+def _find_unpack_format(filename):
+    for name, info in _UNPACK_FORMATS.iteritems():
+        for extension in info[0]:
+            if filename.endswith(extension):
+                return name
+    return None
+
+def unpack_archive(filename, extract_dir=None, format=None):
+    """Unpack an archive.
+
+    `filename` is the name of the archive.
+
+    `extract_dir` is the name of the target directory, where the archive
+    is unpacked. If not provided, the current working directory is used.
+
+    `format` is the archive format: one of "zip", "tar", or "gztar". Or any
+    other registered format. If not provided, unpack_archive will use the
+    filename extension and see if an unpacker was registered for that
+    extension.
+
+    In case none is found, a ValueError is raised.
+    """
+    if extract_dir is None:
+        extract_dir = os.getcwd()
+
+    func = None
+
+    if format is not None:
+        try:
+            format_info = _UNPACK_FORMATS[format]
+        except KeyError:
+            raise ValueError("Unknown unpack format '{0}'".format(format))
+
+        func = format_info[0]
+        func(filename, extract_dir, **dict(format_info[1]))
+    else:
+        # we need to look at the registered unpackers supported extensions
+        format = _find_unpack_format(filename)
+        if format is None:
+            raise ReadError("Unknown archive format '{0}'".format(filename))
+
+        func = _UNPACK_FORMATS[format][1]
+        kwargs = dict(_UNPACK_FORMATS[format][2])
+        func(filename, extract_dir, **kwargs)
+
+    if func is None:
+        raise ValueError('Unknown archive format: %s' % filename)
+
+    return extract_dir

File distutils2/_backport/sysconfig.py

-"""Provide access to Python's configuration information.
-
-"""
+"""Provide access to Python's configuration information."""
+import os
 import sys
-import os
 import re
 from os.path import pardir, realpath
 from ConfigParser import RawConfigParser
 _SCHEMES.read(_CONFIG_FILE)
 _VAR_REPL = re.compile(r'\{([^{]*?)\}')
 
+
 def _expand_globals(config):
     if config.has_section('globals'):
         globals = config.items('globals')
     #
     for section in config.sections():
         variables = dict(config.items(section))
+
         def _replacer(matchobj):
             name = matchobj.group(1)
             if name in variables:
                 return variables[name]
             return matchobj.group(0)
+
         for option, value in config.items(section):
             config.set(section, option, _VAR_REPL.sub(_replacer, value))
 
 if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
     _PROJECT_BASE = realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
 
+
 def is_python_build():
     for fn in ("Setup.dist", "Setup.local"):
         if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
 
 
 def _subst_vars(path, local_vars):
-    """In the string `path`, replace tokens like {some.thing} with the corresponding value from the map `local_vars`.
+    """In the string `path`, replace tokens like {some.thing} with the
+    corresponding value from the map `local_vars`.
 
     If there is no corresponding value, leave the token unchanged.
-
     """
     def _replacer(matchobj):
         name = matchobj.group(1)
         return matchobj.group(0)
     return _VAR_REPL.sub(_replacer, path)
 
+
 def _extend_dict(target_dict, other_dict):
-    target_keys = target_dict.keys()
-    for key, value in other_dict.items():
-        if key in target_keys:
+    for key, value in other_dict.iteritems():
+        if key in target_dict:
             continue
         target_dict[key] = value
 
+
 def _expand_vars(scheme, vars):
     res = {}
     if vars is None:
         res[key] = os.path.normpath(_subst_vars(value, vars))
     return res
 
+def format_value(value, vars):
+    def _replacer(matchobj):
+         name = matchobj.group(1)
+         if name in vars:
+             return vars[name]
+         return matchobj.group(0)
+    return _VAR_REPL.sub(_replacer, value)
+ 
+
 def _get_default_scheme():
     if os.name == 'posix':
         # the default scheme for posix is posix_prefix
         return 'posix_prefix'
     return os.name
 
+
 def _getuserbase():
     env_base = os.environ.get("PYTHONUSERBASE", None)
+
     def joinuser(*args):
         return os.path.expanduser(os.path.join(*args))
 
     optional dictionary is passed in as the second argument, it is
     used instead of a new dictionary.
     """
-    import re
     # Regexes needed for parsing Makefile (and similar syntaxes,
     # like old-style Setup files).
     _variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
                             if name not in done:
                                 done[name] = value
 
-
             else:
                 # bogus variable reference; just drop it since we can't deal
                 variables.remove(name)
 
 
 def get_makefile_filename():
+    """Return the path of the Makefile."""
     if _PYTHON_BUILD:
         return os.path.join(_PROJECT_BASE, "Makefile")
     return os.path.join(get_path('stdlib'), "config", "Makefile")
     if _PYTHON_BUILD:
         vars['LDSHARED'] = vars['BLDSHARED']
 
+
 def _init_non_posix(vars):
     """Initialize the module as appropriate for NT"""
     # set basic install directories
     optional dictionary is passed in as the second argument, it is
     used instead of a new dictionary.
     """
-    import re
     if vars is None:
         vars = {}
     define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
         m = define_rx.match(line)
         if m:
             n, v = m.group(1, 2)
-            try: v = int(v)
-            except ValueError: pass
+            try:
+                v = int(v)
+            except ValueError:
+                pass
             vars[n] = v
         else:
             m = undef_rx.match(line)
                 vars[m.group(1)] = 0
     return vars
 
+
 def get_config_h_filename():
-    """Returns the path of pyconfig.h."""
+    """Return the path of pyconfig.h."""
     if _PYTHON_BUILD:
         if os.name == "nt":
             inc_dir = os.path.join(_PROJECT_BASE, "PC")
         inc_dir = get_path('platinclude')
     return os.path.join(inc_dir, 'pyconfig.h')
 
+
 def get_scheme_names():
-    """Returns a tuple containing the schemes names."""
+    """Return a tuple containing the schemes names."""
     return tuple(sorted(_SCHEMES.sections()))
 
+
 def get_path_names():
-    """Returns a tuple containing the paths names."""
+    """Return a tuple containing the paths names."""
     # xxx see if we want a static list
     return _SCHEMES.options('posix_prefix')
 
+
 def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
-    """Returns a mapping containing an install scheme.
+    """Return a mapping containing an install scheme.
 
     ``scheme`` is the install scheme name. If not provided, it will
     return the default scheme for the current platform.
     else:
         return dict(_SCHEMES.items(scheme))
 
+
 def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
-    """Returns a path corresponding to the scheme.
+    """Return a path corresponding to the scheme.
 
     ``scheme`` is the install scheme name.
     """
     return get_paths(scheme, vars, expand)[name]
 
+
 def get_config_vars(*args):
     """With no arguments, return a dictionary of all configuration
     variables relevant for the current platform.
     With arguments, return a list of values that result from looking up
     each argument in the configuration variable dictionary.
     """
-    import re
     global _CONFIG_VARS
     if _CONFIG_VARS is None:
         _CONFIG_VARS = {}
         else:
             _CONFIG_VARS['srcdir'] = realpath(_CONFIG_VARS['srcdir'])
 
-
         # Convert srcdir into an absolute path if it appears necessary.
         # Normally it is relative to the build directory.  However, during
         # testing, for example, we might be running a non-installed python
                 _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
 
         if sys.platform == 'darwin':
-            kernel_version = os.uname()[2] # Kernel version (8.4.3)
+            kernel_version = os.uname()[2]  # Kernel version (8.4.3)
             major_version = int(kernel_version.split('.')[0])
 
             if major_version < 8:
     else:
         return _CONFIG_VARS
 
+
 def get_config_var(name):
     """Return the value of a single variable using the dictionary returned by
     'get_config_vars()'.
     """
     return get_config_vars().get(name)
 
+
 def get_platform():
     """Return a string that identifies the current platform.
 
 
     For other non-POSIX platforms, currently just returns 'sys.platform'.
     """
-    import re
     if os.name == 'nt':
         # sniff sys.version for architecture.
         prefix = " bit ("
         if i == -1:
             return sys.platform
         j = sys.version.find(")", i)
-        look = sys.version[i+len(prefix):j].lower()
+        look = sys.version[i + len(prefix):j].lower()
         if look == 'amd64':
             return 'win-amd64'
         if look == 'itanium':
         return "%s-%s.%s" % (osname, version, release)
     elif osname[:6] == "cygwin":
         osname = "cygwin"
-        rel_re = re.compile (r'[\d.]+')
+        rel_re = re.compile(r'[\d.]+')
         m = rel_re.match(release)
         if m:
             release = m.group()
                     machine = 'universal'
                 else:
                     raise ValueError(
-                       "Don't know machine value for archs=%r"%(archs,))
+                       "Don't know machine value for archs=%r" % (archs,))
 
             elif machine == 'i386':
                 # On OSX the machine type returned by uname is always the
                 # 32-bit variant, even if the executable architecture is
                 # the 64-bit variant
-                if sys.maxint >= 2**32:
+                if sys.maxint >= (2 ** 32):
                     machine = 'x86_64'
 
             elif machine in ('PowerPC', 'Power_Macintosh'):
                 # Pick a sane name for the PPC architecture.
                 # See 'i386' case
-                if sys.maxint >= 2**32:
+                if sys.maxint >= (2 ** 32):
                     machine = 'ppc64'
                 else:
                     machine = 'ppc'
 def get_python_version():
     return _PY_VERSION_SHORT
 
+
 def _print_dict(title, data):
-    for index, (key, value) in enumerate(sorted(data.items())):
+    for index, (key, value) in enumerate(sorted(data.iteritems())):
         if index == 0:
             print '%s: ' % (title)
         print '\t%s = "%s"' % (key, value)
 
+
 def _main():
     """Display all information sysconfig detains."""
     print 'Platform: "%s"' % get_platform()
     print
     _print_dict('Variables', get_config_vars())
 
+
 if __name__ == '__main__':
     _main()

File distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/INSTALLER

Empty file added.

File distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/METADATA

+Metadata-version: 1.2
+Name: babar
+Version: 0.1
+Author: FELD Boris

File distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RECORD

Empty file added.

File distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/REQUESTED

Empty file added.

File distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RESOURCES

+babar.png,babar.png
+babar.cfg,babar.cfg

File distutils2/_backport/tests/fake_dists/babar.cfg

+Config

File distutils2/_backport/tests/fake_dists/babar.png

Added
New image

File distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO

+Metadata-Version: 1.2
+Name: coconuts-aster
+Version: 10.3
+Provides-Dist: strawberry (0.6)
+Provides-Dist: banana (0.4)

File distutils2/_backport/tests/test_pkgutil.py

 # -*- coding: utf-8 -*-
 """Tests for PEP 376 pkgutil functionality"""
+import imp
 import sys
+
+import csv
 import os
-import csv
-import imp
+import shutil
 import tempfile
-import shutil
 import zipfile
 try:
     from hashlib import md5
 except ImportError:
     from distutils2._backport.hashlib import md5
 
+from distutils2.errors import DistutilsError
+from distutils2.metadata import Metadata
 from distutils2.tests import unittest, run_unittest, support, TESTFN
+
 from distutils2._backport import pkgutil
+from distutils2._backport.pkgutil import (
+                                          Distribution, EggInfoDistribution, get_distribution, get_distributions,
+                                          provides_distribution, obsoletes_distribution, get_file_users,
+                                          distinfo_dirname, _yield_distributions)
 
 try:
     from os.path import relpath
         self.assertEqual(res1, RESOURCE_DATA)
         res2 = pkgutil.get_data(pkg, 'sub/res.txt')
         self.assertEqual(res2, RESOURCE_DATA)
+
+        names = []
+        for loader, name, ispkg in pkgutil.iter_modules([zip_file]):
+            names.append(name)
+        self.assertEqual(names, ['test_getdata_zipfile'])
+
         del sys.path[0]
 
         del sys.modules[pkg]
 
-
 # Adapted from Python 2.7's trunk
 
 
     def setUp(self):
         super(TestPkgUtilDistribution, self).setUp()
         self.fake_dists_path = os.path.abspath(
-            os.path.join(os.path.dirname(__file__), 'fake_dists'))
+                                               os.path.join(os.path.dirname(__file__), 'fake_dists'))
         pkgutil.disable_cache()
 
         self.distinfo_dirs = [os.path.join(self.fake_dists_path, dir)
             # Setup the RECORD file for this dist
             record_file = os.path.join(distinfo_dir, 'RECORD')
             record_writer = csv.writer(open(record_file, 'w'), delimiter=',',
-                quoting=csv.QUOTE_NONE)
+                                       quoting=csv.QUOTE_NONE)
             dist_location = distinfo_dir.replace('.dist-info', '')
 
             for path, dirs, files in os.walk(dist_location):
                                            os.path.join(path, f)))
             for file in ['INSTALLER', 'METADATA', 'REQUESTED']:
                 record_writer.writerow(record_pieces(
-                    os.path.join(distinfo_dir, file)))
+                                       os.path.join(distinfo_dir, file)))
             record_writer.writerow([relpath(record_file, sys.prefix)])
-            del record_writer # causes the RECORD file to close
+            del record_writer  # causes the RECORD file to close
             record_reader = csv.reader(open(record_file, 'rb'))
             record_data = []
             for row in record_reader:
                 path, md5_, size = row[:] + \
-                                   [None for i in xrange(len(row), 3)]
-                record_data.append([path, (md5_, size,)])
+                    [None for i in xrange(len(row), 3)]
+                record_data.append([path, (md5_, size, )])
             self.records[distinfo_dir] = dict(record_data)
 
     def tearDown(self):
     def test_instantiation(self):
         # Test the Distribution class's instantiation provides us with usable
         # attributes.
-        # Import the Distribution class
-        from distutils2._backport.pkgutil import distinfo_dirname, Distribution
-
         here = os.path.abspath(os.path.dirname(__file__))
         name = 'choxie'
         version = '2.0.0.9'
         dist_path = os.path.join(here, 'fake_dists',
-            distinfo_dirname(name, version))
+                                 distinfo_dirname(name, version))
         dist = Distribution(dist_path)
 
         self.assertEqual(dist.name, name)
-        from distutils2.metadata import DistributionMetadata
-        self.assertTrue(isinstance(dist.metadata, DistributionMetadata))
+        self.assertTrue(isinstance(dist.metadata, Metadata))
         self.assertEqual(dist.metadata['version'], version)
         self.assertTrue(isinstance(dist.requested, type(bool())))
 
     def test_installed_files(self):
         # Test the iteration of installed files.
         # Test the distribution's installed files
-        from distutils2._backport.pkgutil import Distribution
         for distinfo_dir in self.distinfo_dirs:
             dist = Distribution(distinfo_dir)
             for path, md5_, size in dist.get_installed_files():
                 record_data = self.records[dist.path]
-                self.assertTrue(path in record_data.keys())
+                self.assertIn(path, record_data)
                 self.assertEqual(md5_, record_data[path][0])
                 self.assertEqual(size, record_data[path][1])
 
         # Criteria to test against
         distinfo_name = 'grammar-1.0a4'
         distinfo_dir = os.path.join(self.fake_dists_path,
-            distinfo_name + '.dist-info')
+                                    distinfo_name + '.dist-info')
         true_path = [self.fake_dists_path, distinfo_name, \
-                     'grammar', 'utils.py']
+            'grammar', 'utils.py']
         true_path = relpath(os.path.join(*true_path), sys.prefix)
         false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff',
             '__init__.py']
         false_path = relpath(os.path.join(*false_path), sys.prefix)
 
         # Test if the distribution uses the file in question
-        from distutils2._backport.pkgutil import Distribution
         dist = Distribution(distinfo_dir)
         self.assertTrue(dist.uses(true_path))
         self.assertFalse(dist.uses(false_path))
 
     def test_get_distinfo_file(self):
         # Test the retrieval of dist-info file objects.
-        from distutils2._backport.pkgutil import Distribution
         distinfo_name = 'choxie-2.0.0.9'
         other_distinfo_name = 'grammar-1.0a4'
         distinfo_dir = os.path.join(self.fake_dists_path,
-            distinfo_name + '.dist-info')
+                                    distinfo_name + '.dist-info')
         dist = Distribution(distinfo_dir)
         # Test for known good file matches
         distinfo_files = [
             # Is it the correct file?
             self.assertEqual(value.name, os.path.join(distinfo_dir, distfile))
 
-        from distutils2.errors import DistutilsError
         # Test an absolute path that is part of another distributions dist-info
         other_distinfo_file = os.path.join(self.fake_dists_path,
-            other_distinfo_name + '.dist-info', 'REQUESTED')
+                                           other_distinfo_name + '.dist-info', 'REQUESTED')
         self.assertRaises(DistutilsError, dist.get_distinfo_file,
-            other_distinfo_file)
+                          other_distinfo_file)
         # Test for a file that does not exist and should not exist
         self.assertRaises(DistutilsError, dist.get_distinfo_file, \
                           'ENTRYPOINTS')
 
     def test_get_distinfo_files(self):
         # Test for the iteration of RECORD path entries.
-        from distutils2._backport.pkgutil import Distribution
         distinfo_name = 'towel_stuff-0.1'
         distinfo_dir = os.path.join(self.fake_dists_path,
-            distinfo_name + '.dist-info')
+                                    distinfo_name + '.dist-info')
         dist = Distribution(distinfo_dir)
         # Test for the iteration of the raw path
         distinfo_record_paths = self.records[distinfo_dir].keys()
         self.assertEqual(sorted(found), sorted(distinfo_record_paths))
         # Test for the iteration of local absolute paths
         distinfo_record_paths = [os.path.join(sys.prefix, path)
-            for path in self.records[distinfo_dir].keys()]
+            for path in self.records[distinfo_dir]]
         found = [path for path in dist.get_distinfo_files(local=True)]
         self.assertEqual(sorted(found), sorted(distinfo_record_paths))
 
+    def test_get_resources_path(self):
+        distinfo_name = 'babar-0.1'
+        distinfo_dir = os.path.join(self.fake_dists_path,
+                                    distinfo_name + '.dist-info')
+        dist = Distribution(distinfo_dir)
+        resource_path = dist.get_resource_path('babar.png')
+        self.assertEqual(resource_path, 'babar.png')
+        self.assertRaises(KeyError, dist.get_resource_path, 'notexist')
+
+
 
 class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher,
                         unittest.TestCase):
             ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'),
             ]
 
-        # Import the function in question
-        from distutils2._backport.pkgutil import distinfo_dirname
-
         # Loop through the items to validate the results
         for name, version, standard_dirname in items:
             dirname = distinfo_dirname(name, version)
         # Lookup all distributions found in the ``sys.path``.
         # This test could potentially pick up other installed distributions
         fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'),
-            ('towel-stuff', '0.1')]
+                      ('towel-stuff', '0.1'), ('babar', '0.1')]
         found_dists = []
 
-        # Import the function in question
-        from distutils2._backport.pkgutil import get_distributions, \
-                                                 Distribution, \
-                                                 EggInfoDistribution
-
         # Verify the fake dists have been found.
         dists = [dist for dist in get_distributions()]
         for dist in dists:
             if not isinstance(dist, Distribution):
                 self.fail("item received was not a Distribution instance: "
-                    "%s" % type(dist))
-            if dist.name in dict(fake_dists).keys() and \
-               dist.path.startswith(self.fake_dists_path):
-                found_dists.append((dist.name, dist.metadata['version'],))
+                          "%s" % type(dist))
+            if dist.name in dict(fake_dists) and \
+                dist.path.startswith(self.fake_dists_path):
+                    found_dists.append((dist.name, dist.metadata['version'], ))
             else:
                 # check that it doesn't find anything more than this
                 self.assertFalse(dist.path.startswith(self.fake_dists_path))
 
         # Now, test if the egg-info distributions are found correctly as well
         fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'),
+                       ('coconuts-aster', '10.3'),
                        ('banana', '0.4'), ('strawberry', '0.6'),
                        ('truffles', '5.0'), ('nut', 'funkyversion')]
         found_dists = []
                     isinstance(dist, EggInfoDistribution)):
                 self.fail("item received was not a Distribution or "
                           "EggInfoDistribution instance: %s" % type(dist))
-            if dist.name in dict(fake_dists).keys() and \
-               dist.path.startswith(self.fake_dists_path):
-                found_dists.append((dist.name, dist.metadata['version']))
+            if dist.name in dict(fake_dists) and \
<