[Python-checkins] distutils2: merged upstream

tarek.ziade python-checkins at python.org
Sun Jul 4 11:48:38 CEST 2010


tarek.ziade pushed 0b00b7e6e0b8 to distutils2:

http://hg.python.org/distutils2/rev/0b00b7e6e0b8
changeset:   258:0b00b7e6e0b8
parent:      257:2f6b9df54955
parent:      122:69952f1f6af5
user:        Konrad Delong <konryd at gmail.com>
date:        Fri Apr 30 13:47:23 2010 -0400
summary:     merged upstream
files:       docs/design/wiki.rst

diff --git a/docs/design/wiki.rst b/docs/design/wiki.rst
--- a/docs/design/wiki.rst
+++ b/docs/design/wiki.rst
@@ -332,6 +332,7 @@
 
 What happens?
 ~~~~~~~~~~~~~
+
 As an example, ``mailman/database/schemas/blah.schema``:
 
 1. The file ``mailman/database/schemas/blah.schema`` in the source
@@ -397,8 +398,17 @@
   be nice to not need an equivalent of ``setup.py develop``. Can we just walk up
   the folder hierarchy from the module until we find a setup.cfg? A setup.cfg is
   necessary if you use distutils2, is it not?
+
+  -> information founded in setup.cfg will be put in the *FILES* file upon
+  installation in the egg-info directory. 
+  IOW in the unbuit-egg case, we would need to create that dir, then use 
+  pkgutil APIs.
+
 * If sysconfig.cfg lands in Python 2.7, what happens when we run distutils2 in
   2.4?
+
+  -> A backport of sysconfig.cfg is provided within the distutils2 distribution.
+  
 * Our new glob-based [resources] section is much more compact (and consistent
   with other systems, like bash) than the explicit MANIFEST.in directives, but
   they don't offer some of the old features. Is it okay to lose exclude,
@@ -406,6 +416,8 @@
   cover their behavior? I think we could probably use a [resource:exclude]
   section with additional exclude globs in it.
 
+  -> let's list the use cases we don't cover, and see
+
 API
 ===
 
diff --git a/src/distutils2/_backport/pkgutil.py b/src/distutils2/_backport/pkgutil.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/pkgutil.py
@@ -0,0 +1,791 @@
+"""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 os
+import sys
+import imp
+import os.path
+from csv import reader as csv_reader
+from types import ModuleType
+from distutils2.errors import DistutilsError
+from distutils2.metadata import DistributionMetadata
+from distutils2.version import suggest_normalized_version
+
+__all__ = [
+    'get_importer', 'iter_importers', 'get_loader', 'find_loader',
+    'walk_packages', 'iter_modules',
+    'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
+    'Distribution', 'distinfo_dirname', 'get_distributions',
+    'get_distribution', 'get_file_users', 
+]
+
+def read_code(stream):
+    # This helper is needed in order for the PEP 302 emulation to
+    # correctly handle compiled files
+    import marshal
+
+    magic = stream.read(4)
+    if magic != imp.get_magic():
+        return None
+
+    stream.read(4) # Skip timestamp
+    return marshal.load(stream)
+
+
+def simplegeneric(func):
+    """Make a trivial single-dispatch generic function"""
+    registry = {}
+    def wrapper(*args, **kw):
+        ob = args[0]
+        try:
+            cls = ob.__class__
+        except AttributeError:
+            cls = type(ob)
+        try:
+            mro = cls.__mro__
+        except AttributeError:
+            try:
+                class cls(cls, object):
+                    pass
+                mro = cls.__mro__[1:]
+            except TypeError:
+                mro = object,   # must be an ExtensionClass or some such  :(
+        for t in mro:
+            if t in registry:
+                return registry[t](*args, **kw)
+        else:
+            return func(*args, **kw)
+    try:
+        wrapper.__name__ = func.__name__
+    except (TypeError, AttributeError):
+        pass    # Python 2.3 doesn't allow functions to be renamed
+
+    def register(typ, func=None):
+        if func is None:
+            return lambda f: register(typ, f)
+        registry[typ] = func
+        return func
+
+    wrapper.__dict__ = func.__dict__
+    wrapper.__doc__ = func.__doc__
+    wrapper.register = register
+    return wrapper
+
+
+def walk_packages(path=None, prefix='', onerror=None):
+    """Yields (module_loader, name, ispkg) for all modules recursively
+    on path, or, if path is None, all accessible modules.
+
+    'path' should be either None or a list of paths to look for
+    modules in.
+
+    'prefix' is a string to output on the front of every module name
+    on output.
+
+    Note that this function must import all *packages* (NOT all
+    modules!) on the given path, in order to access the __path__
+    attribute to find submodules.
+
+    'onerror' is a function which gets called with one argument (the
+    name of the package which was being imported) if any exception
+    occurs while trying to import a package.  If no onerror function is
+    supplied, ImportErrors are caught and ignored, while all other
+    exceptions are propagated, terminating the search.
+
+    Examples:
+
+    # list all modules python can access
+    walk_packages()
+
+    # list all submodules of ctypes
+    walk_packages(ctypes.__path__, ctypes.__name__+'.')
+    """
+
+    def seen(p, m={}):
+        if p in m:
+            return True
+        m[p] = True
+
+    for importer, name, ispkg in iter_modules(path, prefix):
+        yield importer, name, ispkg
+
+        if ispkg:
+            try:
+                __import__(name)
+            except ImportError:
+                if onerror is not None:
+                    onerror(name)
+            except Exception:
+                if onerror is not None:
+                    onerror(name)
+                else:
+                    raise
+            else:
+                path = getattr(sys.modules[name], '__path__', None) or []
+
+                # don't traverse path items we've seen before
+                path = [p for p in path if not seen(p)]
+
+                for item in walk_packages(path, name+'.', onerror):
+                    yield item
+
+
+def iter_modules(path=None, prefix=''):
+    """Yields (module_loader, name, ispkg) for all submodules on path,
+    or, if path is None, all top-level modules on sys.path.
+
+    'path' should be either None or a list of paths to look for
+    modules in.
+
+    'prefix' is a string to output on the front of every module name
+    on output.
+    """
+
+    if path is None:
+        importers = iter_importers()
+    else:
+        importers = map(get_importer, path)
+
+    yielded = {}
+    for i in importers:
+        for name, ispkg in iter_importer_modules(i, prefix):
+            if name not in yielded:
+                yielded[name] = 1
+                yield i, name, ispkg
+
+
+#@simplegeneric
+def iter_importer_modules(importer, prefix=''):
+    if not hasattr(importer, 'iter_modules'):
+        return []
+    return importer.iter_modules(prefix)
+
+iter_importer_modules = simplegeneric(iter_importer_modules)
+
+
+class ImpImporter:
+    """PEP 302 Importer that wraps Python's "classic" import algorithm
+
+    ImpImporter(dirname) produces a PEP 302 importer that searches that
+    directory.  ImpImporter(None) produces a PEP 302 importer that searches
+    the current sys.path, plus any modules that are frozen or built-in.
+
+    Note that ImpImporter does not currently support being used by placement
+    on sys.meta_path.
+    """
+
+    def __init__(self, path=None):
+        self.path = path
+
+    def find_module(self, fullname, path=None):
+        # Note: we ignore 'path' argument since it is only used via meta_path
+        subname = fullname.split(".")[-1]
+        if subname != fullname and self.path is None:
+            return None
+        if self.path is None:
+            path = None
+        else:
+            path = [os.path.realpath(self.path)]
+        try:
+            file, filename, etc = imp.find_module(subname, path)
+        except ImportError:
+            return None
+        return ImpLoader(fullname, file, filename, etc)
+
+    def iter_modules(self, prefix=''):
+        if self.path is None or not os.path.isdir(self.path):
+            return
+
+        yielded = {}
+        import inspect
+
+        filenames = os.listdir(self.path)
+        filenames.sort()  # handle packages before same-named modules
+
+        for fn in filenames:
+            modname = inspect.getmodulename(fn)
+            if modname=='__init__' or modname in yielded:
+                continue
+
+            path = os.path.join(self.path, fn)
+            ispkg = False
+
+            if not modname and os.path.isdir(path) and '.' not in fn:
+                modname = fn
+                for fn in os.listdir(path):
+                    subname = inspect.getmodulename(fn)
+                    if subname=='__init__':
+                        ispkg = True
+                        break
+                else:
+                    continue    # not a package
+
+            if modname and '.' not in modname:
+                yielded[modname] = 1
+                yield prefix + modname, ispkg
+
+
+class ImpLoader:
+    """PEP 302 Loader that wraps Python's "classic" import algorithm
+    """
+    code = source = None
+
+    def __init__(self, fullname, file, filename, etc):
+        self.file = file
+        self.filename = filename
+        self.fullname = fullname
+        self.etc = etc
+
+    def load_module(self, fullname):
+        self._reopen()
+        try:
+            mod = imp.load_module(fullname, self.file, self.filename, self.etc)
+        finally:
+            if self.file:
+                self.file.close()
+        # Note: we don't set __loader__ because we want the module to look
+        # normal; i.e. this is just a wrapper for standard import machinery
+        return mod
+
+    def get_data(self, pathname):
+        return open(pathname, "rb").read()
+
+    def _reopen(self):
+        if self.file and self.file.closed:
+            mod_type = self.etc[2]
+            if mod_type==imp.PY_SOURCE:
+                self.file = open(self.filename, 'rU')
+            elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION):
+                self.file = open(self.filename, 'rb')
+
+    def _fix_name(self, fullname):
+        if fullname is None:
+            fullname = self.fullname
+        elif fullname != self.fullname:
+            raise ImportError("Loader for module %s cannot handle "
+                              "module %s" % (self.fullname, fullname))
+        return fullname
+
+    def is_package(self, fullname):
+        fullname = self._fix_name(fullname)
+        return self.etc[2]==imp.PKG_DIRECTORY
+
+    def get_code(self, fullname=None):
+        fullname = self._fix_name(fullname)
+        if self.code is None:
+            mod_type = self.etc[2]
+            if mod_type==imp.PY_SOURCE:
+                source = self.get_source(fullname)
+                self.code = compile(source, self.filename, 'exec')
+            elif mod_type==imp.PY_COMPILED:
+                self._reopen()
+                try:
+                    self.code = read_code(self.file)
+                finally:
+                    self.file.close()
+            elif mod_type==imp.PKG_DIRECTORY:
+                self.code = self._get_delegate().get_code()
+        return self.code
+
+    def get_source(self, fullname=None):
+        fullname = self._fix_name(fullname)
+        if self.source is None:
+            mod_type = self.etc[2]
+            if mod_type==imp.PY_SOURCE:
+                self._reopen()
+                try:
+                    self.source = self.file.read()
+                finally:
+                    self.file.close()
+            elif mod_type==imp.PY_COMPILED:
+                if os.path.exists(self.filename[:-1]):
+                    f = open(self.filename[:-1], 'rU')
+                    self.source = f.read()
+                    f.close()
+            elif mod_type==imp.PKG_DIRECTORY:
+                self.source = self._get_delegate().get_source()
+        return self.source
+
+
+    def _get_delegate(self):
+        return ImpImporter(self.filename).find_module('__init__')
+
+    def get_filename(self, fullname=None):
+        fullname = self._fix_name(fullname)
+        mod_type = self.etc[2]
+        if self.etc[2]==imp.PKG_DIRECTORY:
+            return self._get_delegate().get_filename()
+        elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
+            return self.filename
+        return None
+
+
+try:
+    import zipimport
+    from zipimport import zipimporter
+
+    def iter_zipimport_modules(importer, prefix=''):
+        dirlist = zipimport._zip_directory_cache[importer.archive].keys()
+        dirlist.sort()
+        _prefix = importer.prefix
+        plen = len(_prefix)
+        yielded = {}
+        import inspect
+        for fn in dirlist:
+            if not fn.startswith(_prefix):
+                continue
+
+            fn = fn[plen:].split(os.sep)
+
+            if len(fn)==2 and fn[1].startswith('__init__.py'):
+                if fn[0] not in yielded:
+                    yielded[fn[0]] = 1
+                    yield fn[0], True
+
+            if len(fn)!=1:
+                continue
+
+            modname = inspect.getmodulename(fn[0])
+            if modname=='__init__':
+                continue
+
+            if modname and '.' not in modname and modname not in yielded:
+                yielded[modname] = 1
+                yield prefix + modname, False
+
+    iter_importer_modules.register(zipimporter, iter_zipimport_modules)
+
+except ImportError:
+    pass
+
+
+def get_importer(path_item):
+    """Retrieve a PEP 302 importer for the given path item
+
+    The returned importer is cached in sys.path_importer_cache
+    if it was newly created by a path hook.
+
+    If there is no importer, a wrapper around the basic import
+    machinery is returned. This wrapper is never inserted into
+    the importer cache (None is inserted instead).
+
+    The cache (or part of it) can be cleared manually if a
+    rescan of sys.path_hooks is necessary.
+    """
+    try:
+        importer = sys.path_importer_cache[path_item]
+    except KeyError:
+        for path_hook in sys.path_hooks:
+            try:
+                importer = path_hook(path_item)
+                break
+            except ImportError:
+                pass
+        else:
+            importer = None
+        sys.path_importer_cache.setdefault(path_item, importer)
+
+    if importer is None:
+        try:
+            importer = ImpImporter(path_item)
+        except ImportError:
+            importer = None
+    return importer
+
+
+def iter_importers(fullname=""):
+    """Yield PEP 302 importers for the given module name
+
+    If fullname contains a '.', the importers will be for the package
+    containing fullname, otherwise they will be importers for sys.meta_path,
+    sys.path, and Python's "classic" import machinery, in that order.  If
+    the named module is in a package, that package is imported as a side
+    effect of invoking this function.
+
+    Non PEP 302 mechanisms (e.g. the Windows registry) used by the
+    standard import machinery to find files in alternative locations
+    are partially supported, but are searched AFTER sys.path. Normally,
+    these locations are searched BEFORE sys.path, preventing sys.path
+    entries from shadowing them.
+
+    For this to cause a visible difference in behaviour, there must
+    be a module or package name that is accessible via both sys.path
+    and one of the non PEP 302 file system mechanisms. In this case,
+    the emulation will find the former version, while the builtin
+    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
+    """
+    if fullname.startswith('.'):
+        raise ImportError("Relative module names not supported")
+    if '.' in fullname:
+        # Get the containing package's __path__
+        pkg = '.'.join(fullname.split('.')[:-1])
+        if pkg not in sys.modules:
+            __import__(pkg)
+        path = getattr(sys.modules[pkg], '__path__', None) or []
+    else:
+        for importer in sys.meta_path:
+            yield importer
+        path = sys.path
+    for item in path:
+        yield get_importer(item)
+    if '.' not in fullname:
+        yield ImpImporter()
+
+def get_loader(module_or_name):
+    """Get a PEP 302 "loader" object for module_or_name
+
+    If the module or package is accessible via the normal import
+    mechanism, a wrapper around the relevant part of that machinery
+    is returned.  Returns None if the module cannot be found or imported.
+    If the named module is not already imported, its containing package
+    (if any) is imported, in order to establish the package __path__.
+
+    This function uses iter_importers(), and is thus subject to the same
+    limitations regarding platform-specific special import locations such
+    as the Windows registry.
+    """
+    if module_or_name in sys.modules:
+        module_or_name = sys.modules[module_or_name]
+    if isinstance(module_or_name, ModuleType):
+        module = module_or_name
+        loader = getattr(module, '__loader__', None)
+        if loader is not None:
+            return loader
+        fullname = module.__name__
+    else:
+        fullname = module_or_name
+    return find_loader(fullname)
+
+def find_loader(fullname):
+    """Find a PEP 302 "loader" object for fullname
+
+    If fullname contains dots, path must be the containing package's __path__.
+    Returns None if the module cannot be found or imported. This function uses
+    iter_importers(), and is thus subject to the same limitations regarding
+    platform-specific special import locations such as the Windows registry.
+    """
+    for importer in iter_importers(fullname):
+        loader = importer.find_module(fullname)
+        if loader is not None:
+            return loader
+
+    return None
+
+
+def extend_path(path, name):
+    """Extend a package's path.
+
+    Intended use is to place the following code in a package's __init__.py:
+
+        from pkgutil import extend_path
+        __path__ = extend_path(__path__, __name__)
+
+    This will add to the package's __path__ all subdirectories of
+    directories on sys.path named after the package.  This is useful
+    if one wants to distribute different parts of a single logical
+    package as multiple directories.
+
+    It also looks for *.pkg files beginning where * matches the name
+    argument.  This feature is similar to *.pth files (see site.py),
+    except that it doesn't special-case lines starting with 'import'.
+    A *.pkg file is trusted at face value: apart from checking for
+    duplicates, all entries found in a *.pkg file are added to the
+    path, regardless of whether they are exist the filesystem.  (This
+    is a feature.)
+
+    If the input path is not a list (as is the case for frozen
+    packages) it is returned unchanged.  The input path is not
+    modified; an extended copy is returned.  Items are only appended
+    to the copy at the end.
+
+    It is assumed that sys.path is a sequence.  Items of sys.path that
+    are not (unicode or 8-bit) strings referring to existing
+    directories are ignored.  Unicode items of sys.path that cause
+    errors when used as filenames may cause this function to raise an
+    exception (in line with os.path.isdir() behavior).
+    """
+
+    if not isinstance(path, list):
+        # This could happen e.g. when this is called from inside a
+        # frozen package.  Return the path unchanged in that case.
+        return 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
+
+    for dir in sys.path:
+        if not isinstance(dir, basestring) or not os.path.isdir(dir):
+            continue
+        subdir = os.path.join(dir, pname)
+        # XXX This may still add duplicate entries to path on
+        # case-insensitive filesystems
+        initfile = os.path.join(subdir, init_py)
+        if subdir not in path and os.path.isfile(initfile):
+            path.append(subdir)
+        # XXX Is this the right thing for subpackages like zope.app?
+        # It looks for a file named "zope.app.pkg"
+        pkgfile = os.path.join(dir, sname_pkg)
+        if os.path.isfile(pkgfile):
+            try:
+                f = open(pkgfile)
+            except IOError, msg:
+                sys.stderr.write("Can't open %s: %s\n" %
+                                 (pkgfile, msg))
+            else:
+                for line in f:
+                    line = line.rstrip('\n')
+                    if not line or line.startswith('#'):
+                        continue
+                    path.append(line) # Don't check for existence!
+                f.close()
+
+    return path
+
+def get_data(package, resource):
+    """Get a resource from a package.
+
+    This is a wrapper round the PEP 302 loader get_data API. The package
+    argument should be the name of a package, in standard module format
+    (foo.bar). The resource argument should be in the form of a relative
+    filename, using '/' as the path separator. The parent directory name '..'
+    is not allowed, and nor is a rooted name (starting with a '/').
+
+    The function returns a binary string, which is the contents of the
+    specified resource.
+
+    For packages located in the filesystem, which have already been imported,
+    this is the rough equivalent of
+
+        d = os.path.dirname(sys.modules[package].__file__)
+        data = open(os.path.join(d, resource), 'rb').read()
+
+    If the package cannot be located or loaded, or it uses a PEP 302 loader
+    which does not support get_data(), then None is returned.
+    """
+
+    loader = get_loader(package)
+    if loader is None or not hasattr(loader, 'get_data'):
+        return None
+    mod = sys.modules.get(package) or loader.load_module(package)
+    if mod is None or not hasattr(mod, '__file__'):
+        return None
+
+    # Modify the resource name to be compatible with the loader.get_data
+    # signature - an os.path format "filename" starting with the dirname of
+    # the package's __file__
+    parts = resource.split('/')
+    parts.insert(0, os.path.dirname(mod.__file__))
+    resource_name = os.path.join(*parts)
+    return loader.get_data(resource_name)
+
+##########################
+# PEP 376 Implementation #
+##########################
+
+DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',)
+
+class Distribution(object):
+    """Created with the *path* of the ``.dist-info`` directory provided to the
+    constructor. It reads the metadata contained in METADATA when it is
+    instantiated."""
+
+    # Attribute documenting for Sphinx style documentation, see for more info:
+    #   http://sphinx.pocoo.org/ext/autodoc.html#dir-autoattribute
+    name = ''
+    """The name of the distribution."""
+    metadata = None
+    """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with 
+    the distribution's METADATA file."""
+    requested = False
+    """A boolean that indicates whether the REQUESTED metadata file is present
+    (in other words, whether the package was installed by user request)."""
+
+    def __init__(self, path):
+        self.path = path
+        metadata_path = os.path.join(path, 'METADATA')
+        self.metadata = DistributionMetadata(path=metadata_path)
+        self.name = self.metadata['name']
+
+    def _get_records(self, local=False):
+        RECORD = os.path.join(self.path, 'RECORD')
+        record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',')
+        for row in record_reader:
+            path, md5, size = row[:] + [ None for i in xrange(len(row), 3) ]
+            if local:
+                path = path.replace('/', os.sep)
+                path = os.path.join(sys.prefix, path)
+            yield path, md5, size
+
+    def get_installed_files(self, local=False):
+        """
+        Iterates over the RECORD entries and returns a tuple (path, md5, size)
+        for each line. If *local* is ``True``, the returned path is transformed
+        into a local absolute path. Otherwise the raw value from RECORD is
+        returned.
+
+        A local absolute path is an absolute path in which occurrences of
+        ``'/'`` have been replaced by the system separator given by ``os.sep``.
+
+        :parameter local: flag to say if the path should be returned a local
+                          absolute path
+        :type local: boolean
+        :returns: iterator of (path, md5, size)
+        """
+        return self._get_records(local)
+
+
+    def uses(self, path):
+        """
+        Returns ``True`` if path is listed in RECORD. *path* can be a local 
+        absolute path or a relative ``'/'``-separated path.
+
+        :rtype: boolean
+        """
+        for p, md5, size in self._get_records():
+            local_absolute = os.path.join(sys.prefix, p)
+            if path == p or path == local_absolute:
+                return True
+        return False
+
+    def get_distinfo_file(self, path, binary=False):
+        """
+        Returns a file located under the ``.dist-info`` directory. Returns a
+        ``file`` instance for the file pointed by *path*.
+
+        :parameter path: a ``'/'``-separated path relative to the ``.dist-info``
+                         directory or an absolute path; If *path* is an absolute 
+                         path and doesn't start with the ``.dist-info``
+                         directory path, a :class:`DistutilsError` is raised
+        :type path: string
+        :parameter binary: If *binary* is ``True``, opens the file in read-only
+                           binary mode (rb), otherwise opens it in read-only
+                           mode (r).
+        :rtype: file object
+        """
+        open_flags = 'r'
+        if binary:
+            open_flags += 'b'
+
+        # Check if it is an absolute path
+        if path.find(os.sep) >= 0:
+            # it's an absolute path?
+            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])))
+
+        # The file must be relative
+        if path not in DIST_FILES:
+            raise DistutilsError("Requested an invalid dist-info file: "
+                "%s" % path)
+
+        # Convert the relative path back to absolute
+        path = os.path.join(self.path, path)
+        return open(path, open_flags)
+
+    def get_distinfo_files(self, local=False):
+        """
+        Iterates over the RECORD entries and returns paths for each line if the 
+        path is pointing to a file located in the ``.dist-info`` directory or 
+        one of its subdirectories.
+
+        :parameter local: If *local* is ``True``, each returned path is
+                          transformed into a local absolute path. Otherwise the
+                          raw value from RECORD is returned.
+        :type local: boolean
+        :returns: iterator of paths
+        """
+        for path, md5, size in self._get_records(local):
+            yield path
+
+
+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
+    filename-escaped form, i.e. any ``'-'`` characters are replaced with ``'_'``
+    other than the one in ``'dist-info'`` and the one separating the name from 
+    the version number.
+
+    :parameter name: is converted to a standard distribution name by replacing
+                     any runs of non- alphanumeric characters with a single
+                     ``'-'``.
+    :type name: string
+    :parameter version: is converted to a standard version string. Spaces become
+                        dots, and all other non-alphanumeric characters (except
+                        dots) become dashes, with runs of multiple dashes
+                        condensed to a single dash.
+    :type version: string
+    :returns: directory name
+    :rtype: string"""
+    file_extension = '.dist-info'
+    name = _normalize_dist_name(name)
+    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
+    if normalized_version is None:
+        # Unable to achieve normality?
+        normalized_version = version
+    return '-'.join([name, normalized_version]) + file_extension
+
+def get_distributions():
+    """
+    Provides an iterator that looks for ``.dist-info`` directories in
+    ``sys.path`` and returns :class:`Distribution` instances for each one of
+    them.
+
+    :rtype: iterator of :class:`Distribution` instances"""
+    for path in sys.path:
+        realpath = os.path.realpath(path)
+        if not os.path.isdir(realpath):
+            continue
+        for dir in os.listdir(realpath):
+            if dir.endswith('.dist-info'):
+                dist = Distribution(os.path.join(realpath, dir))
+                yield dist
+
+def get_distribution(name):
+    """
+    Scans all elements in ``sys.path`` and looks for all directories ending with
+    ``.dist-info``. Returns a :class:`Distribution` corresponding to the 
+    ``.dist-info`` directory that contains the METADATA that matches *name* for
+    the *name* metadata.
+
+    This function only returns the first result founded, as no more than one
+    value is expected. If the directory is not found, ``None`` is returned.
+
+    :rtype: :class:`Distribution` or None"""
+    found = None
+    for dist in get_distributions():
+        if dist.name == name:
+            found = dist
+            break
+    return found
+
+def get_file_users(path):
+    """
+    Iterates over all distributions to find out which distributions uses
+    *path*.
+
+    :parameter path: can be a local absolute path or a relative
+                     ``'/'``-separated path.
+    :type path: string
+    :rtype: iterator of :class:`Distribution` instances"""
+    for dist in get_distributions():
+        if dist.uses(path):
+            yield dist
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
@@ -0,0 +1,6 @@
+Metadata-Version: 1.2
+Name: choxie
+Version: 2.0.0.9
+Summary: Chocolate with a kick!
+Requires-Dist: towel-stuff (0.1)
+Provides-Dist: truffles (1.0)
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+from towel_stuff import Towel
+
+class Chocolate(object):
+    """A piece of chocolate."""
+
+    def wrap_with_towel(self):
+        towel = Towel()
+        towel.wrap(self)
+        return towel
diff --git a/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/truffles.py b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/truffles.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/choxie-2.0.0.9/truffles.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+from choxie.chocolate import Chocolate
+
+class Truffle(Chocolate):
+    """A truffle."""
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: grammar
+Version: 1.0a4
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/RECORD b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/RECORD
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/__init__.py b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/__init__.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+
diff --git a/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/utils.py b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/utils.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/grammar-1.0a4/grammar/utils.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from random import randint
+
+def is_valid_grammar(sentence):
+    if randint(0, 10) < 2:
+        return False
+    else:
+        return True
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: towel-stuff
+Version: 0.1
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED
new file mode 100644
diff --git a/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+class Towel(object):
+    """A towel, that one should never be without."""
+
+    def __init__(self, color='tie-dye'):
+        self.color = color
+        self.wrapped_obj = None
+
+    def wrap(self, obj):
+        """Wrap an object up in our towel."""
+        self.wrapped_obj = obj
+
+    def unwrap(self):
+        """Unwrap whatever is in our towel and return whatever it is."""
+        obj = self.wrapped_obj
+        self.wrapped_obj = None
+        return obj
diff --git a/src/distutils2/_backport/tests/test_pkgutil.py b/src/distutils2/_backport/tests/test_pkgutil.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/_backport/tests/test_pkgutil.py
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 -*-
+"""Tests for PEP 376 pkgutil functionality"""
+import unittest2
+import sys
+import os
+import csv
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
+from test.test_support import run_unittest, TESTFN
+
+import distutils2._backport.pkgutil
+
+# TODO Add a test for getting a distribution that is provided by another
+#   distribution.
+
+# TODO Add a test for absolute pathed RECORD items (e.g. /etc/myapp/config.ini)
+
+class TestPkgUtilDistribution(unittest2.TestCase):
+    """Tests the pkgutil.Distribution class"""
+
+    def setUp(self):
+        super(TestPkgUtilDistribution, self).setUp()
+
+        self.fake_dists_path = os.path.abspath(
+            os.path.join(os.path.dirname(__file__), 'fake_dists'))
+        self.distinfo_dirs = [ os.path.join(self.fake_dists_path, dir)
+            for dir in os.listdir(self.fake_dists_path)
+            if dir.endswith('.dist-info')
+            ]
+
+        def get_hexdigest(file):
+            md5_hash = md5()
+            md5_hash.update(open(file).read())
+            return md5_hash.hexdigest()
+        def record_pieces(file):
+            path = os.path.relpath(file, sys.prefix)
+            digest = get_hexdigest(file)
+            size = os.path.getsize(file)
+            return [path, digest, size]
+
+        self.records = {}
+        for distinfo_dir in self.distinfo_dirs:
+            # 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)
+            dist_location = distinfo_dir.replace('.dist-info', '')
+
+            for path, dirs, files in os.walk(dist_location):
+                for f in files:
+                    record_writer.writerow(record_pieces(os.path.join(path, f)))
+            for file in ['INSTALLER', 'METADATA', 'REQUESTED']:
+                record_writer.writerow(record_pieces(
+                    os.path.join(distinfo_dir, file)))
+            record_writer.writerow([os.path.relpath(record_file, sys.prefix)])
+            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,)])
+            self.records[distinfo_dir] = dict(record_data)
+
+    def tearDown(self):
+        self.records = None
+        for distinfo_dir in self.distinfo_dirs:
+            record_file = os.path.join(distinfo_dir, 'RECORD')
+            open(record_file, 'w').close()
+        super(TestPkgUtilDistribution, self).tearDown()
+
+    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))
+        dist = Distribution(dist_path)
+
+        self.assertEqual(dist.name, name)
+        from distutils2.metadata import DistributionMetadata
+        self.assertTrue(isinstance(dist.metadata, DistributionMetadata))
+        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.assertEqual(md5_, record_data[path][0])
+                self.assertEqual(size, record_data[path][1])
+
+    def test_uses(self):
+        """Test to determine if a distribution uses a specified file."""
+        # Criteria to test against
+        distinfo_name = 'grammar-1.0a4'
+        distinfo_dir = os.path.join(self.fake_dists_path,
+            distinfo_name + '.dist-info')
+        true_path = [self.fake_dists_path, distinfo_name, 'grammar', 'utils.py']
+        true_path = os.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 = os.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')
+        dist = Distribution(distinfo_dir)
+        # Test for known good file matches
+        distinfo_files = [
+            # Relative paths
+            'INSTALLER', 'METADATA',
+            # Absolute paths
+            os.path.join(distinfo_dir, 'RECORD'),
+            os.path.join(distinfo_dir, 'REQUESTED'),
+            ]
+
+        for distfile in distinfo_files:
+            value = dist.get_distinfo_file(distfile)
+            self.assertTrue(isinstance(value, file))
+            # 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')
+        self.assertRaises(DistutilsError, dist.get_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')
+        dist = Distribution(distinfo_dir)
+        # Test for the iteration of the raw path
+        distinfo_record_paths = self.records[distinfo_dir].keys()
+        found = [ path for path in dist.get_distinfo_files() ]
+        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()
+            ]
+        found = [ path for path in dist.get_distinfo_files(local=True) ]
+        self.assertEqual(sorted(found), sorted(distinfo_record_paths))
+
+
+class TestPkgUtilFunctions(unittest2.TestCase):
+    """Tests for the new functionality added in PEP 376."""
+
+    def setUp(self):
+        super(TestPkgUtilFunctions, self).setUp()
+        # Setup the path environment with our fake distributions
+        current_path = os.path.abspath(os.path.dirname(__file__))
+        self.sys_path = sys.path[:]
+        self.fake_dists_path = os.path.join(current_path, 'fake_dists')
+        sys.path[0:0] = [self.fake_dists_path]
+
+    def tearDown(self):
+        super(TestPkgUtilFunctions, self).tearDown()
+        sys.path[:] = self.sys_path
+
+    def test_distinfo_dirname(self):
+        """Given a name and a version, we expect the distinfo_dirname function
+        to return a standard distribution information directory name."""
+
+        items = [ # (name, version, standard_dirname)
+            # Test for a very simple single word name and decimal version number
+            ('docutils', '0.5', 'docutils-0.5.dist-info'),
+            # Test for another except this time with a '-' in the name, which
+            #   needs to be transformed during the name lookup
+            ('python-ldap', '2.5', 'python_ldap-2.5.dist-info'),
+            # Test for both '-' in the name and a funky version number
+            ('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)
+            self.assertEqual(dirname, standard_dirname)
+
+    def test_get_distributions(self):
+        """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')]
+        found_dists = []
+
+        # Import the function in question
+        from distutils2._backport.pkgutil import get_distributions, Distribution
+
+        # 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():
+                found_dists.append((dist.name, dist.metadata['version'],))
+            # otherwise we don't care what other distributions are found
+
+        # Finally, test that we found all that we were looking for
+        self.assertListEqual(sorted(found_dists), sorted(fake_dists))
+
+    def test_get_distribution(self):
+        """Test for looking up a distribution by name."""
+        # Test the lookup of the towel-stuff distribution
+        name = 'towel-stuff' # Note: This is different from the directory name
+
+        # Import the function in question
+        from distutils2._backport.pkgutil import get_distribution, Distribution
+
+        # Lookup the distribution
+        dist = get_distribution(name)
+        self.assertTrue(isinstance(dist, Distribution))
+        self.assertEqual(dist.name, name)
+
+        # Verify that an unknown distribution returns None
+        self.assertEqual(None, get_distribution('bogus'))
+
+        # Verify partial name matching doesn't work
+        self.assertEqual(None, get_distribution('towel'))
+
+    def test_get_file_users(self):
+        """Test the iteration of distributions that use a file."""
+        from distutils2._backport.pkgutil import get_file_users, Distribution
+        name = 'towel_stuff-0.1'
+        path = os.path.join(self.fake_dists_path, name,
+            'towel_stuff', '__init__.py')
+        for dist in get_file_users(path):
+            self.assertTrue(isinstance(dist, Distribution))
+            self.assertEqual(dist.name, name)
+
+
+def test_suite():
+    suite = unittest2.TestSuite()
+    testcase_loader = unittest2.loader.defaultTestLoader.loadTestsFromTestCase
+    suite.addTest(testcase_loader(TestPkgUtilFunctions))
+    suite.addTest(testcase_loader(TestPkgUtilDistribution))
+    return suite
+
+def test_main():
+    run_unittest(test_suite())
+
+if __name__ == "__main__":
+    test_main()
diff --git a/src/distutils2/command/check.py b/src/distutils2/command/check.py
--- a/src/distutils2/command/check.py
+++ b/src/distutils2/command/check.py
@@ -25,14 +25,14 @@
         self.restructuredtext = 0
         self.metadata = 1
         self.strict = 0
-        self._warnings = 0
+        self._warnings = []
 
     def finalize_options(self):
         pass
 
     def warn(self, msg):
         """Counts the number of warnings that occurs."""
-        self._warnings += 1
+        self._warnings.append(msg)
         return Command.warn(self, msg)
 
     def run(self):
@@ -48,8 +48,9 @@
 
         # let's raise an error in strict mode, if we have at least
         # one warning
-        if self.strict and self._warnings > 0:
-            raise DistutilsSetupError('Please correct your package.')
+        if self.strict and len(self._warnings) > 0:
+            msg = '\n'.join(self._warnings)
+            raise DistutilsSetupError(msg)
 
     def check_metadata(self):
         """Ensures that all required elements of meta-data are supplied.
diff --git a/src/distutils2/command/register.py b/src/distutils2/command/register.py
--- a/src/distutils2/command/register.py
+++ b/src/distutils2/command/register.py
@@ -224,39 +224,11 @@
     def build_post_data(self, action):
         # figure the data to send - the metadata plus some additional
         # information used by the package server
-        meta = self.distribution.metadata
-        data = {
-            ':action': action,
-            'metadata_version' : meta.version,
-            'name': meta['Name'],
-            'version': meta['Version'],
-            'summary': meta['Summary'],
-            'home_page': meta['Home-page'],
-            'author': meta['Author'],
-            'author_email': meta['Author-email'],
-            'license': meta['License'],
-            'description': meta['Description'],
-            'keywords': meta['Keywords'],
-            'platform': meta['Platform'],
-            'classifier': meta['Classifier'],
-            'download_url': meta['Download-URL'],
-        }
-
-        if meta.version == '1.2':
-            data['requires_dist'] = meta['Requires-Dist']
-            data['requires_python'] = meta['Requires-Python']
-            data['requires_external'] = meta['Requires-External']
-            data['provides_dist'] = meta['Provides-Dist']
-            data['obsoletes_dist'] = meta['Obsoletes-Dist']
-            data['project_url'] = meta['Project-Url']
-
-        elif meta.version == '1.1':
-            data['provides'] = meta['Provides']
-            data['requires'] = meta['Requires']
-            data['obsoletes'] = meta['Obsoletes']
-
+        data = self._metadata_to_pypy_dict()
+        data[':action'] = action
         return data
 
+    # XXX to be refactored with upload.upload_file
     def post_to_server(self, data, auth=None):
         ''' Post a query to the server, and return a string response.
         '''
diff --git a/src/distutils2/command/upload.py b/src/distutils2/command/upload.py
--- a/src/distutils2/command/upload.py
+++ b/src/distutils2/command/upload.py
@@ -7,7 +7,7 @@
 from urllib2 import urlopen, Request, HTTPError
 from base64 import standard_b64encode
 import urlparse
-import cStringIO as StringIO
+import StringIO as StringIO
 try:
     from hashlib import md5
 except ImportError:
@@ -62,6 +62,7 @@
         for command, pyversion, filename in self.distribution.dist_files:
             self.upload_file(command, pyversion, filename)
 
+    # XXX to be refactored with register.post_to_server
     def upload_file(self, command, pyversion, filename):
         # Makes sure the repository URL is compliant
         schema, netloc, url, params, query, fragments = \
@@ -83,42 +84,17 @@
         # Fill in the data - send all the meta-data in case we need to
         # register a new release
         content = open(filename,'rb').read()
-        meta = self.distribution.metadata
 
-        data = {
-            # action
-            ':action': 'file_upload',
-            'protcol_version': '1',
+        data = self._metadata_to_pypy_dict()
 
-            # identify release
-            'name': meta['Name'],
-            'version': meta['Version'],
+        # extra upload infos
+        data[':action'] = 'file_upload'
+        data['protcol_version'] = '1'
+        data['content'] = [os.path.basename(filename), content]
+        data['filetype'] = command
+        data['pyversion'] = pyversion
+        data['md5_digest'] = md5(content).hexdigest()
 
-            # file content
-            'content': (os.path.basename(filename),content),
-            'filetype': command,
-            'pyversion': pyversion,
-            'md5_digest': md5(content).hexdigest(),
-
-            # additional meta-data
-            # XXX Implement 1.1
-            'metadata_version' : '1.0',
-            'name': meta['Name'],
-            'version': meta['Version'],
-            'summary': meta['Summary'],
-            'home_page': meta['Home-page'],
-            'author': meta['Author'],
-            'author_email': meta['Author-email'],
-            'license': meta['License'],
-            'description': meta['Description'],
-            'keywords': meta['Keywords'],
-            'platform': meta['Platform'],
-            'classifiers': meta['Classifier'],
-            'download_url': meta['Download-URL'],
-            #'provides': meta['Provides'],
-            #'requires': meta['Requires'],
-            #'obsoletes': meta['Obsoletes'],
-            }
         comment = ''
         if command == 'bdist_rpm':
             dist, version, id = platform.dist()
@@ -129,8 +105,8 @@
         data['comment'] = comment
 
         if self.sign:
-            data['gpg_signature'] = (os.path.basename(filename) + ".asc",
-                                     open(filename+".asc").read())
+            data['gpg_signature'] = [(os.path.basename(filename) + ".asc",
+                                      open(filename+".asc").read())]
 
         # set up the authentication
         auth = "Basic " + standard_b64encode(self.username + ":" +
@@ -141,29 +117,41 @@
         sep_boundary = '\n--' + boundary
         end_boundary = sep_boundary + '--'
         body = StringIO.StringIO()
-        for key, value in data.items():
+        file_fields = ('content', 'gpg_signature')
+
+        for key, values in data.items():
             # handle multiple entries for the same name
-            if not isinstance(value, list):
-                value = [value]
-            for value in value:
-                if isinstance(value, tuple):
-                    fn = ';filename="%s"' % value[0]
-                    value = value[1]
-                else:
-                    fn = ""
+            if not isinstance(values, (tuple, list)):
+                values = [values]
 
+            content_dispo = 'Content-Disposition: form-data; name="%s"' % key
+
+            if key in file_fields:
+                filename_, content = values
+                filename_ = ';filename="%s"' % filename_
                 body.write(sep_boundary)
-                body.write('\nContent-Disposition: form-data; name="%s"'%key)
-                body.write(fn)
+                body.write("\n")
+                body.write(content_dispo)
+                body.write(filename_)
                 body.write("\n\n")
-                body.write(value)
-                if value and value[-1] == '\r':
-                    body.write('\n')  # write an extra newline (lurve Macs)
+                body.write(content)
+            else:
+                for value in values:
+                    body.write(sep_boundary)
+                    body.write("\n")
+                    body.write(content_dispo)
+                    body.write("\n\n")
+                    body.write(value)
+                    if value and value[-1] == '\r':
+                        # write an extra newline (lurve Macs)
+                        body.write('\n')
+
         body.write(end_boundary)
         body.write("\n")
         body = body.getvalue()
 
-        self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
+        self.announce("Submitting %s to %s" % (filename, self.repository),
+                      log.INFO)
 
         # build the Request
         headers = {'Content-type':
diff --git a/src/distutils2/config.py b/src/distutils2/config.py
--- a/src/distutils2/config.py
+++ b/src/distutils2/config.py
@@ -107,6 +107,40 @@
 
         return {}
 
+    def _metadata_to_pypy_dict(self):
+        meta = self.distribution.metadata
+        data = {
+            'metadata_version' : meta.version,
+            'name': meta['Name'],
+            'version': meta['Version'],
+            'summary': meta['Summary'],
+            'home_page': meta['Home-page'],
+            'author': meta['Author'],
+            'author_email': meta['Author-email'],
+            'license': meta['License'],
+            'description': meta['Description'],
+            'keywords': meta['Keywords'],
+            'platform': meta['Platform'],
+            'classifier': meta['Classifier'],
+            'download_url': meta['Download-URL'],
+        }
+
+        if meta.version == '1.2':
+            data['requires_dist'] = meta['Requires-Dist']
+            data['requires_python'] = meta['Requires-Python']
+            data['requires_external'] = meta['Requires-External']
+            data['provides_dist'] = meta['Provides-Dist']
+            data['obsoletes_dist'] = meta['Obsoletes-Dist']
+            data['project_url'] = [','.join(url) for url in
+                                   meta['Project-URL']]
+
+        elif meta.version == '1.1':
+            data['provides'] = meta['Provides']
+            data['requires'] = meta['Requires']
+            data['obsoletes'] = meta['Obsoletes']
+
+        return data
+
     def initialize_options(self):
         """Initialize options."""
         self.repository = None
diff --git a/src/distutils2/metadata.py b/src/distutils2/metadata.py
--- a/src/distutils2/metadata.py
+++ b/src/distutils2/metadata.py
@@ -77,8 +77,8 @@
                'Requires-Python', 'Requires-External')
 
 _345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python',
-        'Obsoletes-Dist', 'Requires-External', 'Maintainer',
-        'Maintainer-email')
+                'Obsoletes-Dist', 'Requires-External', 'Maintainer',
+                'Maintainer-email', 'Project-URL')
 
 _ALL_FIELDS = []
 
@@ -153,6 +153,7 @@
         'Requires', 'Provides', 'Obsoletes-Dist',
         'Provides-Dist', 'Requires-Dist', 'Requires-External',
         'Project-URL')
+_LISTTUPLEFIELDS = ('Project-URL',)
 
 _ELEMENTSFIELD = ('Keywords',)
 
@@ -355,7 +356,11 @@
                 valid, val = self._platform(val)
                 if not valid:
                     continue
-                res.append(self._encode_field(val))
+                if name not in _LISTTUPLEFIELDS:
+                    res.append(self._encode_field(val))
+                else:
+                    # That's for Project-URL
+                    res.append((self._encode_field(val[0]), val[1]))
             return res
 
         elif name in _ELEMENTSFIELD:
@@ -382,6 +387,30 @@
             warnings = self._check_rst_data(self['Description'])
         else:
             warnings = []
+
+        # checking metadata 1.2 (XXX needs to check 1.1, 1.0)
+        if self['Metadata-Version'] != '1.2':
+            return missing, warnings
+
+
+        def is_valid_predicates(value):
+            for v in value:
+                if not is_valid_predicate(v.split(';')[0]):
+                    return False
+            return True
+
+        for fields, controller in ((_PREDICATE_FIELDS, is_valid_predicates),
+                                  (_VERSIONS_FIELDS, is_valid_versions),
+                                  (_VERSION_FIELDS, is_valid_version)):
+            for field in fields:
+                value = self[field]
+                if value == 'UNKNOWN':
+                    continue
+
+                if not controller(value):
+                    warnings.append('Wrong value for "%s": %s' \
+                            % (field, value))
+
         return missing, warnings
 
     def keys(self):
diff --git a/src/distutils2/mkpkg.py b/src/distutils2/mkpkg.py
--- a/src/distutils2/mkpkg.py
+++ b/src/distutils2/mkpkg.py
@@ -73,774 +73,776 @@
 }
 
 troveList = [
-      'Development Status :: 1 - Planning',
-		'Development Status :: 2 - Pre-Alpha',
-		'Development Status :: 3 - Alpha',
-		'Development Status :: 4 - Beta',
-		'Development Status :: 5 - Production/Stable',
-		'Development Status :: 6 - Mature',
-		'Development Status :: 7 - Inactive',
-		'Environment :: Console',
-		'Environment :: Console :: Curses',
-		'Environment :: Console :: Framebuffer',
-		'Environment :: Console :: Newt',
-		'Environment :: Console :: svgalib',
-		'Environment :: Handhelds/PDA\'s',
-		'Environment :: MacOS X',
-		'Environment :: MacOS X :: Aqua',
-		'Environment :: MacOS X :: Carbon',
-		'Environment :: MacOS X :: Cocoa',
-		'Environment :: No Input/Output (Daemon)',
-		'Environment :: Other Environment',
-		'Environment :: Plugins',
-		'Environment :: Web Environment',
-		'Environment :: Web Environment :: Buffet',
-		'Environment :: Web Environment :: Mozilla',
-		'Environment :: Web Environment :: ToscaWidgets',
-		'Environment :: Win32 (MS Windows)',
-		'Environment :: X11 Applications',
-		'Environment :: X11 Applications :: Gnome',
-		'Environment :: X11 Applications :: GTK',
-		'Environment :: X11 Applications :: KDE',
-		'Environment :: X11 Applications :: Qt',
-		'Framework :: BFG',
-		'Framework :: Buildout',
-		'Framework :: Chandler',
-		'Framework :: CubicWeb',
-		'Framework :: Django',
-		'Framework :: IDLE',
-		'Framework :: Paste',
-		'Framework :: Plone',
-		'Framework :: Pylons',
-		'Framework :: Setuptools Plugin',
-		'Framework :: Trac',
-		'Framework :: TurboGears',
-		'Framework :: TurboGears :: Applications',
-		'Framework :: TurboGears :: Widgets',
-		'Framework :: Twisted',
-		'Framework :: ZODB',
-		'Framework :: Zope2',
-		'Framework :: Zope3',
-		'Intended Audience :: Customer Service',
-		'Intended Audience :: Developers',
-		'Intended Audience :: Education',
-		'Intended Audience :: End Users/Desktop',
-		'Intended Audience :: Financial and Insurance Industry',
-		'Intended Audience :: Healthcare Industry',
-		'Intended Audience :: Information Technology',
-		'Intended Audience :: Legal Industry',
-		'Intended Audience :: Manufacturing',
-		'Intended Audience :: Other Audience',
-		'Intended Audience :: Religion',
-		'Intended Audience :: Science/Research',
-		'Intended Audience :: System Administrators',
-		'Intended Audience :: Telecommunications Industry',
-		'License :: Aladdin Free Public License (AFPL)',
-		'License :: DFSG approved',
-		'License :: Eiffel Forum License (EFL)',
-		'License :: Free For Educational Use',
-		'License :: Free For Home Use',
-		'License :: Free for non-commercial use',
-		'License :: Freely Distributable',
-		'License :: Free To Use But Restricted',
-		'License :: Freeware',
-		'License :: Netscape Public License (NPL)',
-		'License :: Nokia Open Source License (NOKOS)',
-		'License :: OSI Approved',
-		'License :: OSI Approved :: Academic Free License (AFL)',
-		'License :: OSI Approved :: Apache Software License',
-		'License :: OSI Approved :: Apple Public Source License',
-		'License :: OSI Approved :: Artistic License',
-		'License :: OSI Approved :: Attribution Assurance License',
-		'License :: OSI Approved :: BSD License',
-		'License :: OSI Approved :: Common Public License',
-		'License :: OSI Approved :: Eiffel Forum License',
-		'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)',
-		'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)',
-		'License :: OSI Approved :: GNU Affero General Public License v3',
-		'License :: OSI Approved :: GNU Free Documentation License (FDL)',
-		'License :: OSI Approved :: GNU General Public License (GPL)',
-		'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
-		'License :: OSI Approved :: IBM Public License',
-		'License :: OSI Approved :: Intel Open Source License',
-		'License :: OSI Approved :: ISC License (ISCL)',
-		'License :: OSI Approved :: Jabber Open Source License',
-		'License :: OSI Approved :: MIT License',
-		'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)',
-		'License :: OSI Approved :: Motosoto License',
-		'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)',
-		'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
-		'License :: OSI Approved :: Nethack General Public License',
-		'License :: OSI Approved :: Nokia Open Source License',
-		'License :: OSI Approved :: Open Group Test Suite License',
-		'License :: OSI Approved :: Python License (CNRI Python License)',
-		'License :: OSI Approved :: Python Software Foundation License',
-		'License :: OSI Approved :: Qt Public License (QPL)',
-		'License :: OSI Approved :: Ricoh Source Code Public License',
-		'License :: OSI Approved :: Sleepycat License',
-		'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)',
-		'License :: OSI Approved :: Sun Public License',
-		'License :: OSI Approved :: University of Illinois/NCSA Open Source License',
-		'License :: OSI Approved :: Vovida Software License 1.0',
-		'License :: OSI Approved :: W3C License',
-		'License :: OSI Approved :: X.Net License',
-		'License :: OSI Approved :: zlib/libpng License',
-		'License :: OSI Approved :: Zope Public License',
-		'License :: Other/Proprietary License',
-		'License :: Public Domain',
-		'License :: Repoze Public License',
-		'Natural Language :: Afrikaans',
-		'Natural Language :: Arabic',
-		'Natural Language :: Bengali',
-		'Natural Language :: Bosnian',
-		'Natural Language :: Bulgarian',
-		'Natural Language :: Catalan',
-		'Natural Language :: Chinese (Simplified)',
-		'Natural Language :: Chinese (Traditional)',
-		'Natural Language :: Croatian',
-		'Natural Language :: Czech',
-		'Natural Language :: Danish',
-		'Natural Language :: Dutch',
-		'Natural Language :: English',
-		'Natural Language :: Esperanto',
-		'Natural Language :: Finnish',
-		'Natural Language :: French',
-		'Natural Language :: German',
-		'Natural Language :: Greek',
-		'Natural Language :: Hebrew',
-		'Natural Language :: Hindi',
-		'Natural Language :: Hungarian',
-		'Natural Language :: Icelandic',
-		'Natural Language :: Indonesian',
-		'Natural Language :: Italian',
-		'Natural Language :: Japanese',
-		'Natural Language :: Javanese',
-		'Natural Language :: Korean',
-		'Natural Language :: Latin',
-		'Natural Language :: Latvian',
-		'Natural Language :: Macedonian',
-		'Natural Language :: Malay',
-		'Natural Language :: Marathi',
-		'Natural Language :: Norwegian',
-		'Natural Language :: Panjabi',
-		'Natural Language :: Persian',
-		'Natural Language :: Polish',
-		'Natural Language :: Portuguese',
-		'Natural Language :: Portuguese (Brazilian)',
-		'Natural Language :: Romanian',
-		'Natural Language :: Russian',
-		'Natural Language :: Serbian',
-		'Natural Language :: Slovak',
-		'Natural Language :: Slovenian',
-		'Natural Language :: Spanish',
-		'Natural Language :: Swedish',
-		'Natural Language :: Tamil',
-		'Natural Language :: Telugu',
-		'Natural Language :: Thai',
-		'Natural Language :: Turkish',
-		'Natural Language :: Ukranian',
-		'Natural Language :: Urdu',
-		'Natural Language :: Vietnamese',
-		'Operating System :: BeOS',
-		'Operating System :: MacOS',
-		'Operating System :: MacOS :: MacOS 9',
-		'Operating System :: MacOS :: MacOS X',
-		'Operating System :: Microsoft',
-		'Operating System :: Microsoft :: MS-DOS',
-		'Operating System :: Microsoft :: Windows',
-		'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier',
-		'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
-		'Operating System :: Microsoft :: Windows :: Windows CE',
-		'Operating System :: Microsoft :: Windows :: Windows NT/2000',
-		'Operating System :: OS/2',
-		'Operating System :: OS Independent',
-		'Operating System :: Other OS',
-		'Operating System :: PalmOS',
-		'Operating System :: PDA Systems',
-		'Operating System :: POSIX',
-		'Operating System :: POSIX :: AIX',
-		'Operating System :: POSIX :: BSD',
-		'Operating System :: POSIX :: BSD :: BSD/OS',
-		'Operating System :: POSIX :: BSD :: FreeBSD',
-		'Operating System :: POSIX :: BSD :: NetBSD',
-		'Operating System :: POSIX :: BSD :: OpenBSD',
-		'Operating System :: POSIX :: GNU Hurd',
-		'Operating System :: POSIX :: HP-UX',
-		'Operating System :: POSIX :: IRIX',
-		'Operating System :: POSIX :: Linux',
-		'Operating System :: POSIX :: Other',
-		'Operating System :: POSIX :: SCO',
-		'Operating System :: POSIX :: SunOS/Solaris',
-		'Operating System :: Unix',
-		'Programming Language :: Ada',
-		'Programming Language :: APL',
-		'Programming Language :: ASP',
-		'Programming Language :: Assembly',
-		'Programming Language :: Awk',
-		'Programming Language :: Basic',
-		'Programming Language :: C',
-		'Programming Language :: C#',
-		'Programming Language :: C++',
-		'Programming Language :: Cold Fusion',
-		'Programming Language :: Cython',
-		'Programming Language :: Delphi/Kylix',
-		'Programming Language :: Dylan',
-		'Programming Language :: Eiffel',
-		'Programming Language :: Emacs-Lisp',
-		'Programming Language :: Erlang',
-		'Programming Language :: Euler',
-		'Programming Language :: Euphoria',
-		'Programming Language :: Forth',
-		'Programming Language :: Fortran',
-		'Programming Language :: Haskell',
-		'Programming Language :: Java',
-		'Programming Language :: JavaScript',
-		'Programming Language :: Lisp',
-		'Programming Language :: Logo',
-		'Programming Language :: ML',
-		'Programming Language :: Modula',
-		'Programming Language :: Objective C',
-		'Programming Language :: Object Pascal',
-		'Programming Language :: OCaml',
-		'Programming Language :: Other',
-		'Programming Language :: Other Scripting Engines',
-		'Programming Language :: Pascal',
-		'Programming Language :: Perl',
-		'Programming Language :: PHP',
-		'Programming Language :: Pike',
-		'Programming Language :: Pliant',
-		'Programming Language :: PL/SQL',
-		'Programming Language :: PROGRESS',
-		'Programming Language :: Prolog',
-		'Programming Language :: Python',
-		'Programming Language :: Python :: 2',
-		'Programming Language :: Python :: 2.3',
-		'Programming Language :: Python :: 2.4',
-		'Programming Language :: Python :: 2.5',
-		'Programming Language :: Python :: 2.6',
-		'Programming Language :: Python :: 2.7',
-		'Programming Language :: Python :: 3',
-		'Programming Language :: Python :: 3.0',
-		'Programming Language :: Python :: 3.1',
-		'Programming Language :: Python :: 3.2',
-		'Programming Language :: REBOL',
-		'Programming Language :: Rexx',
-		'Programming Language :: Ruby',
-		'Programming Language :: Scheme',
-		'Programming Language :: Simula',
-		'Programming Language :: Smalltalk',
-		'Programming Language :: SQL',
-		'Programming Language :: Tcl',
-		'Programming Language :: Unix Shell',
-		'Programming Language :: Visual Basic',
-		'Programming Language :: XBasic',
-		'Programming Language :: YACC',
-		'Programming Language :: Zope',
-		'Topic :: Adaptive Technologies',
-		'Topic :: Artistic Software',
-		'Topic :: Communications',
-		'Topic :: Communications :: BBS',
-		'Topic :: Communications :: Chat',
-		'Topic :: Communications :: Chat :: AOL Instant Messenger',
-		'Topic :: Communications :: Chat :: ICQ',
-		'Topic :: Communications :: Chat :: Internet Relay Chat',
-		'Topic :: Communications :: Chat :: Unix Talk',
-		'Topic :: Communications :: Conferencing',
-		'Topic :: Communications :: Email',
-		'Topic :: Communications :: Email :: Address Book',
-		'Topic :: Communications :: Email :: Email Clients (MUA)',
-		'Topic :: Communications :: Email :: Filters',
-		'Topic :: Communications :: Email :: Mailing List Servers',
-		'Topic :: Communications :: Email :: Mail Transport Agents',
-		'Topic :: Communications :: Email :: Post-Office',
-		'Topic :: Communications :: Email :: Post-Office :: IMAP',
-		'Topic :: Communications :: Email :: Post-Office :: POP3',
-		'Topic :: Communications :: Fax',
-		'Topic :: Communications :: FIDO',
-		'Topic :: Communications :: File Sharing',
-		'Topic :: Communications :: File Sharing :: Gnutella',
-		'Topic :: Communications :: File Sharing :: Napster',
-		'Topic :: Communications :: Ham Radio',
-		'Topic :: Communications :: Internet Phone',
-		'Topic :: Communications :: Telephony',
-		'Topic :: Communications :: Usenet News',
-		'Topic :: Database',
-		'Topic :: Database :: Database Engines/Servers',
-		'Topic :: Database :: Front-Ends',
-		'Topic :: Desktop Environment',
-		'Topic :: Desktop Environment :: File Managers',
-		'Topic :: Desktop Environment :: Gnome',
-		'Topic :: Desktop Environment :: GNUstep',
-		'Topic :: Desktop Environment :: K Desktop Environment (KDE)',
-		'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes',
-		'Topic :: Desktop Environment :: PicoGUI',
-		'Topic :: Desktop Environment :: PicoGUI :: Applications',
-		'Topic :: Desktop Environment :: PicoGUI :: Themes',
-		'Topic :: Desktop Environment :: Screen Savers',
-		'Topic :: Desktop Environment :: Window Managers',
-		'Topic :: Desktop Environment :: Window Managers :: Afterstep',
-		'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: Applets',
-		'Topic :: Desktop Environment :: Window Managers :: Blackbox',
-		'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: CTWM',
-		'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: Enlightenment',
-		'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets',
-		'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15',
-		'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16',
-		'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17',
-		'Topic :: Desktop Environment :: Window Managers :: Fluxbox',
-		'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: FVWM',
-		'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: IceWM',
-		'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: MetaCity',
-		'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: Oroborus',
-		'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: Sawfish',
-		'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30',
-		'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30',
-		'Topic :: Desktop Environment :: Window Managers :: Waimea',
-		'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: Window Maker',
-		'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets',
-		'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes',
-		'Topic :: Desktop Environment :: Window Managers :: XFCE',
-		'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes',
-		'Topic :: Documentation',
-		'Topic :: Education',
-		'Topic :: Education :: Computer Aided Instruction (CAI)',
-		'Topic :: Education :: Testing',
-		'Topic :: Games/Entertainment',
-		'Topic :: Games/Entertainment :: Arcade',
-		'Topic :: Games/Entertainment :: Board Games',
-		'Topic :: Games/Entertainment :: First Person Shooters',
-		'Topic :: Games/Entertainment :: Fortune Cookies',
-		'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)',
-		'Topic :: Games/Entertainment :: Puzzle Games',
-		'Topic :: Games/Entertainment :: Real Time Strategy',
-		'Topic :: Games/Entertainment :: Role-Playing',
-		'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games',
-		'Topic :: Games/Entertainment :: Simulation',
-		'Topic :: Games/Entertainment :: Turn Based Strategy',
-		'Topic :: Home Automation',
-		'Topic :: Internet',
-		'Topic :: Internet :: File Transfer Protocol (FTP)',
-		'Topic :: Internet :: Finger',
-		'Topic :: Internet :: Log Analysis',
-		'Topic :: Internet :: Name Service (DNS)',
-		'Topic :: Internet :: Proxy Servers',
-		'Topic :: Internet :: WAP',
-		'Topic :: Internet :: WWW/HTTP',
-		'Topic :: Internet :: WWW/HTTP :: Browsers',
-		'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
-		'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
-		'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards',
-		'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary',
-		'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters',
-		'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
-		'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
-		'Topic :: Internet :: WWW/HTTP :: Site Management',
-		'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking',
-		'Topic :: Internet :: WWW/HTTP :: WSGI',
-		'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
-		'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
-		'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
-		'Topic :: Internet :: Z39.50',
-		'Topic :: Multimedia',
-		'Topic :: Multimedia :: Graphics',
-		'Topic :: Multimedia :: Graphics :: 3D Modeling',
-		'Topic :: Multimedia :: Graphics :: 3D Rendering',
-		'Topic :: Multimedia :: Graphics :: Capture',
-		'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera',
-		'Topic :: Multimedia :: Graphics :: Capture :: Scanners',
-		'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture',
-		'Topic :: Multimedia :: Graphics :: Editors',
-		'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based',
-		'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based',
-		'Topic :: Multimedia :: Graphics :: Graphics Conversion',
-		'Topic :: Multimedia :: Graphics :: Presentation',
-		'Topic :: Multimedia :: Graphics :: Viewers',
-		'Topic :: Multimedia :: Sound/Audio',
-		'Topic :: Multimedia :: Sound/Audio :: Analysis',
-		'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
-		'Topic :: Multimedia :: Sound/Audio :: CD Audio',
-		'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing',
-		'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping',
-		'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing',
-		'Topic :: Multimedia :: Sound/Audio :: Conversion',
-		'Topic :: Multimedia :: Sound/Audio :: Editors',
-		'Topic :: Multimedia :: Sound/Audio :: MIDI',
-		'Topic :: Multimedia :: Sound/Audio :: Mixers',
-		'Topic :: Multimedia :: Sound/Audio :: Players',
-		'Topic :: Multimedia :: Sound/Audio :: Players :: MP3',
-		'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis',
-		'Topic :: Multimedia :: Sound/Audio :: Speech',
-		'Topic :: Multimedia :: Video',
-		'Topic :: Multimedia :: Video :: Capture',
-		'Topic :: Multimedia :: Video :: Conversion',
-		'Topic :: Multimedia :: Video :: Display',
-		'Topic :: Multimedia :: Video :: Non-Linear Editor',
-		'Topic :: Office/Business',
-		'Topic :: Office/Business :: Financial',
-		'Topic :: Office/Business :: Financial :: Accounting',
-		'Topic :: Office/Business :: Financial :: Investment',
-		'Topic :: Office/Business :: Financial :: Point-Of-Sale',
-		'Topic :: Office/Business :: Financial :: Spreadsheet',
-		'Topic :: Office/Business :: Groupware',
-		'Topic :: Office/Business :: News/Diary',
-		'Topic :: Office/Business :: Office Suites',
-		'Topic :: Office/Business :: Scheduling',
-		'Topic :: Other/Nonlisted Topic',
-		'Topic :: Printing',
-		'Topic :: Religion',
-		'Topic :: Scientific/Engineering',
-		'Topic :: Scientific/Engineering :: Artificial Intelligence',
-		'Topic :: Scientific/Engineering :: Astronomy',
-		'Topic :: Scientific/Engineering :: Atmospheric Science',
-		'Topic :: Scientific/Engineering :: Bio-Informatics',
-		'Topic :: Scientific/Engineering :: Chemistry',
-		'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
-		'Topic :: Scientific/Engineering :: GIS',
-		'Topic :: Scientific/Engineering :: Human Machine Interfaces',
-		'Topic :: Scientific/Engineering :: Image Recognition',
-		'Topic :: Scientific/Engineering :: Information Analysis',
-		'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator',
-		'Topic :: Scientific/Engineering :: Mathematics',
-		'Topic :: Scientific/Engineering :: Medical Science Apps.',
-		'Topic :: Scientific/Engineering :: Physics',
-		'Topic :: Scientific/Engineering :: Visualization',
-		'Topic :: Security',
-		'Topic :: Security :: Cryptography',
-		'Topic :: Sociology',
-		'Topic :: Sociology :: Genealogy',
-		'Topic :: Sociology :: History',
-		'Topic :: Software Development',
-		'Topic :: Software Development :: Assemblers',
-		'Topic :: Software Development :: Bug Tracking',
-		'Topic :: Software Development :: Build Tools',
-		'Topic :: Software Development :: Code Generators',
-		'Topic :: Software Development :: Compilers',
-		'Topic :: Software Development :: Debuggers',
-		'Topic :: Software Development :: Disassemblers',
-		'Topic :: Software Development :: Documentation',
-		'Topic :: Software Development :: Embedded Systems',
-		'Topic :: Software Development :: Internationalization',
-		'Topic :: Software Development :: Interpreters',
-		'Topic :: Software Development :: Libraries',
-		'Topic :: Software Development :: Libraries :: Application Frameworks',
-		'Topic :: Software Development :: Libraries :: Java Libraries',
-		'Topic :: Software Development :: Libraries :: Perl Modules',
-		'Topic :: Software Development :: Libraries :: PHP Classes',
-		'Topic :: Software Development :: Libraries :: Pike Modules',
-		'Topic :: Software Development :: Libraries :: pygame',
-		'Topic :: Software Development :: Libraries :: Python Modules',
-		'Topic :: Software Development :: Libraries :: Ruby Modules',
-		'Topic :: Software Development :: Libraries :: Tcl Extensions',
-		'Topic :: Software Development :: Localization',
-		'Topic :: Software Development :: Object Brokering',
-		'Topic :: Software Development :: Object Brokering :: CORBA',
-		'Topic :: Software Development :: Pre-processors',
-		'Topic :: Software Development :: Quality Assurance',
-		'Topic :: Software Development :: Testing',
-		'Topic :: Software Development :: Testing :: Traffic Generation',
-		'Topic :: Software Development :: User Interfaces',
-		'Topic :: Software Development :: Version Control',
-		'Topic :: Software Development :: Version Control :: CVS',
-		'Topic :: Software Development :: Version Control :: RCS',
-		'Topic :: Software Development :: Version Control :: SCCS',
-		'Topic :: Software Development :: Widget Sets',
-		'Topic :: System',
-		'Topic :: System :: Archiving',
-		'Topic :: System :: Archiving :: Backup',
-		'Topic :: System :: Archiving :: Compression',
-		'Topic :: System :: Archiving :: Mirroring',
-		'Topic :: System :: Archiving :: Packaging',
-		'Topic :: System :: Benchmark',
-		'Topic :: System :: Boot',
-		'Topic :: System :: Boot :: Init',
-		'Topic :: System :: Clustering',
-		'Topic :: System :: Console Fonts',
-		'Topic :: System :: Distributed Computing',
-		'Topic :: System :: Emulators',
-		'Topic :: System :: Filesystems',
-		'Topic :: System :: Hardware',
-		'Topic :: System :: Hardware :: Hardware Drivers',
-		'Topic :: System :: Hardware :: Mainframes',
-		'Topic :: System :: Hardware :: Symmetric Multi-processing',
-		'Topic :: System :: Installation/Setup',
-		'Topic :: System :: Logging',
-		'Topic :: System :: Monitoring',
-		'Topic :: System :: Networking',
-		'Topic :: System :: Networking :: Firewalls',
-		'Topic :: System :: Networking :: Monitoring',
-		'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
-		'Topic :: System :: Networking :: Time Synchronization',
-		'Topic :: System :: Operating System',
-		'Topic :: System :: Operating System Kernels',
-		'Topic :: System :: Operating System Kernels :: BSD',
-		'Topic :: System :: Operating System Kernels :: GNU Hurd',
-		'Topic :: System :: Operating System Kernels :: Linux',
-		'Topic :: System :: Power (UPS)',
-		'Topic :: System :: Recovery Tools',
-		'Topic :: System :: Shells',
-		'Topic :: System :: Software Distribution',
-		'Topic :: System :: Systems Administration',
-		'Topic :: System :: Systems Administration :: Authentication/Directory',
-		'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
-		'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS',
-		'Topic :: System :: System Shells',
-		'Topic :: Terminals',
-		'Topic :: Terminals :: Serial',
-		'Topic :: Terminals :: Telnet',
-		'Topic :: Terminals :: Terminal Emulators/X Terminals',
-		'Topic :: Text Editors',
-		'Topic :: Text Editors :: Documentation',
-		'Topic :: Text Editors :: Emacs',
-		'Topic :: Text Editors :: Integrated Development Environments (IDE)',
-		'Topic :: Text Editors :: Text Processing',
-		'Topic :: Text Editors :: Word Processors',
-		'Topic :: Text Processing',
-		'Topic :: Text Processing :: Filters',
-		'Topic :: Text Processing :: Fonts',
-		'Topic :: Text Processing :: General',
-		'Topic :: Text Processing :: Indexing',
-		'Topic :: Text Processing :: Linguistic',
-		'Topic :: Text Processing :: Markup',
-		'Topic :: Text Processing :: Markup :: HTML',
-		'Topic :: Text Processing :: Markup :: LaTeX',
-		'Topic :: Text Processing :: Markup :: SGML',
-		'Topic :: Text Processing :: Markup :: VRML',
-		'Topic :: Text Processing :: Markup :: XML',
-		'Topic :: Utilities',
+        'Development Status :: 1 - Planning',
+        'Development Status :: 2 - Pre-Alpha',
+        'Development Status :: 3 - Alpha',
+        'Development Status :: 4 - Beta',
+        'Development Status :: 5 - Production/Stable',
+        'Development Status :: 6 - Mature',
+        'Development Status :: 7 - Inactive',
+        'Environment :: Console',
+        'Environment :: Console :: Curses',
+        'Environment :: Console :: Framebuffer',
+        'Environment :: Console :: Newt',
+        'Environment :: Console :: svgalib',
+        'Environment :: Handhelds/PDA\'s',
+        'Environment :: MacOS X',
+        'Environment :: MacOS X :: Aqua',
+        'Environment :: MacOS X :: Carbon',
+        'Environment :: MacOS X :: Cocoa',
+        'Environment :: No Input/Output (Daemon)',
+        'Environment :: Other Environment',
+        'Environment :: Plugins',
+        'Environment :: Web Environment',
+        'Environment :: Web Environment :: Buffet',
+        'Environment :: Web Environment :: Mozilla',
+        'Environment :: Web Environment :: ToscaWidgets',
+        'Environment :: Win32 (MS Windows)',
+        'Environment :: X11 Applications',
+        'Environment :: X11 Applications :: Gnome',
+        'Environment :: X11 Applications :: GTK',
+        'Environment :: X11 Applications :: KDE',
+        'Environment :: X11 Applications :: Qt',
+        'Framework :: BFG',
+        'Framework :: Buildout',
+        'Framework :: Chandler',
+        'Framework :: CubicWeb',
+        'Framework :: Django',
+        'Framework :: IDLE',
+        'Framework :: Paste',
+        'Framework :: Plone',
+        'Framework :: Pylons',
+        'Framework :: Setuptools Plugin',
+        'Framework :: Trac',
+        'Framework :: TurboGears',
+        'Framework :: TurboGears :: Applications',
+        'Framework :: TurboGears :: Widgets',
+        'Framework :: Twisted',
+        'Framework :: ZODB',
+        'Framework :: Zope2',
+        'Framework :: Zope3',
+        'Intended Audience :: Customer Service',
+        'Intended Audience :: Developers',
+        'Intended Audience :: Education',
+        'Intended Audience :: End Users/Desktop',
+        'Intended Audience :: Financial and Insurance Industry',
+        'Intended Audience :: Healthcare Industry',
+        'Intended Audience :: Information Technology',
+        'Intended Audience :: Legal Industry',
+        'Intended Audience :: Manufacturing',
+        'Intended Audience :: Other Audience',
+        'Intended Audience :: Religion',
+        'Intended Audience :: Science/Research',
+        'Intended Audience :: System Administrators',
+        'Intended Audience :: Telecommunications Industry',
+        'License :: Aladdin Free Public License (AFPL)',
+        'License :: DFSG approved',
+        'License :: Eiffel Forum License (EFL)',
+        'License :: Free For Educational Use',
+        'License :: Free For Home Use',
+        'License :: Free for non-commercial use',
+        'License :: Freely Distributable',
+        'License :: Free To Use But Restricted',
+        'License :: Freeware',
+        'License :: Netscape Public License (NPL)',
+        'License :: Nokia Open Source License (NOKOS)',
+        'License :: OSI Approved',
+        'License :: OSI Approved :: Academic Free License (AFL)',
+        'License :: OSI Approved :: Apache Software License',
+        'License :: OSI Approved :: Apple Public Source License',
+        'License :: OSI Approved :: Artistic License',
+        'License :: OSI Approved :: Attribution Assurance License',
+        'License :: OSI Approved :: BSD License',
+        'License :: OSI Approved :: Common Public License',
+        'License :: OSI Approved :: Eiffel Forum License',
+        'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)',
+        'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)',
+        'License :: OSI Approved :: GNU Affero General Public License v3',
+        'License :: OSI Approved :: GNU Free Documentation License (FDL)',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
+        'License :: OSI Approved :: IBM Public License',
+        'License :: OSI Approved :: Intel Open Source License',
+        'License :: OSI Approved :: ISC License (ISCL)',
+        'License :: OSI Approved :: Jabber Open Source License',
+        'License :: OSI Approved :: MIT License',
+        'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)',
+        'License :: OSI Approved :: Motosoto License',
+        'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)',
+        'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
+        'License :: OSI Approved :: Nethack General Public License',
+        'License :: OSI Approved :: Nokia Open Source License',
+        'License :: OSI Approved :: Open Group Test Suite License',
+        'License :: OSI Approved :: Python License (CNRI Python License)',
+        'License :: OSI Approved :: Python Software Foundation License',
+        'License :: OSI Approved :: Qt Public License (QPL)',
+        'License :: OSI Approved :: Ricoh Source Code Public License',
+        'License :: OSI Approved :: Sleepycat License',
+        'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)',
+        'License :: OSI Approved :: Sun Public License',
+        'License :: OSI Approved :: University of Illinois/NCSA Open Source License',
+        'License :: OSI Approved :: Vovida Software License 1.0',
+        'License :: OSI Approved :: W3C License',
+        'License :: OSI Approved :: X.Net License',
+        'License :: OSI Approved :: zlib/libpng License',
+        'License :: OSI Approved :: Zope Public License',
+        'License :: Other/Proprietary License',
+        'License :: Public Domain',
+        'License :: Repoze Public License',
+        'Natural Language :: Afrikaans',
+        'Natural Language :: Arabic',
+        'Natural Language :: Bengali',
+        'Natural Language :: Bosnian',
+        'Natural Language :: Bulgarian',
+        'Natural Language :: Catalan',
+        'Natural Language :: Chinese (Simplified)',
+        'Natural Language :: Chinese (Traditional)',
+        'Natural Language :: Croatian',
+        'Natural Language :: Czech',
+        'Natural Language :: Danish',
+        'Natural Language :: Dutch',
+        'Natural Language :: English',
+        'Natural Language :: Esperanto',
+        'Natural Language :: Finnish',
+        'Natural Language :: French',
+        'Natural Language :: German',
+        'Natural Language :: Greek',
+        'Natural Language :: Hebrew',
+        'Natural Language :: Hindi',
+        'Natural Language :: Hungarian',
+        'Natural Language :: Icelandic',
+        'Natural Language :: Indonesian',
+        'Natural Language :: Italian',
+        'Natural Language :: Japanese',
+        'Natural Language :: Javanese',
+        'Natural Language :: Korean',
+        'Natural Language :: Latin',
+        'Natural Language :: Latvian',
+        'Natural Language :: Macedonian',
+        'Natural Language :: Malay',
+        'Natural Language :: Marathi',
+        'Natural Language :: Norwegian',
+        'Natural Language :: Panjabi',
+        'Natural Language :: Persian',
+        'Natural Language :: Polish',
+        'Natural Language :: Portuguese',
+        'Natural Language :: Portuguese (Brazilian)',
+        'Natural Language :: Romanian',
+        'Natural Language :: Russian',
+        'Natural Language :: Serbian',
+        'Natural Language :: Slovak',
+        'Natural Language :: Slovenian',
+        'Natural Language :: Spanish',
+        'Natural Language :: Swedish',
+        'Natural Language :: Tamil',
+        'Natural Language :: Telugu',
+        'Natural Language :: Thai',
+        'Natural Language :: Turkish',
+        'Natural Language :: Ukranian',
+        'Natural Language :: Urdu',
+        'Natural Language :: Vietnamese',
+        'Operating System :: BeOS',
+        'Operating System :: MacOS',
+        'Operating System :: MacOS :: MacOS 9',
+        'Operating System :: MacOS :: MacOS X',
+        'Operating System :: Microsoft',
+        'Operating System :: Microsoft :: MS-DOS',
+        'Operating System :: Microsoft :: Windows',
+        'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier',
+        'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
+        'Operating System :: Microsoft :: Windows :: Windows CE',
+        'Operating System :: Microsoft :: Windows :: Windows NT/2000',
+        'Operating System :: OS/2',
+        'Operating System :: OS Independent',
+        'Operating System :: Other OS',
+        'Operating System :: PalmOS',
+        'Operating System :: PDA Systems',
+        'Operating System :: POSIX',
+        'Operating System :: POSIX :: AIX',
+        'Operating System :: POSIX :: BSD',
+        'Operating System :: POSIX :: BSD :: BSD/OS',
+        'Operating System :: POSIX :: BSD :: FreeBSD',
+        'Operating System :: POSIX :: BSD :: NetBSD',
+        'Operating System :: POSIX :: BSD :: OpenBSD',
+        'Operating System :: POSIX :: GNU Hurd',
+        'Operating System :: POSIX :: HP-UX',
+        'Operating System :: POSIX :: IRIX',
+        'Operating System :: POSIX :: Linux',
+        'Operating System :: POSIX :: Other',
+        'Operating System :: POSIX :: SCO',
+        'Operating System :: POSIX :: SunOS/Solaris',
+        'Operating System :: Unix',
+        'Programming Language :: Ada',
+        'Programming Language :: APL',
+        'Programming Language :: ASP',
+        'Programming Language :: Assembly',
+        'Programming Language :: Awk',
+        'Programming Language :: Basic',
+        'Programming Language :: C',
+        'Programming Language :: C#',
+        'Programming Language :: C++',
+        'Programming Language :: Cold Fusion',
+        'Programming Language :: Cython',
+        'Programming Language :: Delphi/Kylix',
+        'Programming Language :: Dylan',
+        'Programming Language :: Eiffel',
+        'Programming Language :: Emacs-Lisp',
+        'Programming Language :: Erlang',
+        'Programming Language :: Euler',
+        'Programming Language :: Euphoria',
+        'Programming Language :: Forth',
+        'Programming Language :: Fortran',
+        'Programming Language :: Haskell',
+        'Programming Language :: Java',
+        'Programming Language :: JavaScript',
+        'Programming Language :: Lisp',
+        'Programming Language :: Logo',
+        'Programming Language :: ML',
+        'Programming Language :: Modula',
+        'Programming Language :: Objective C',
+        'Programming Language :: Object Pascal',
+        'Programming Language :: OCaml',
+        'Programming Language :: Other',
+        'Programming Language :: Other Scripting Engines',
+        'Programming Language :: Pascal',
+        'Programming Language :: Perl',
+        'Programming Language :: PHP',
+        'Programming Language :: Pike',
+        'Programming Language :: Pliant',
+        'Programming Language :: PL/SQL',
+        'Programming Language :: PROGRESS',
+        'Programming Language :: Prolog',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.3',
+        'Programming Language :: Python :: 2.4',
+        'Programming Language :: Python :: 2.5',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.0',
+        'Programming Language :: Python :: 3.1',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: REBOL',
+        'Programming Language :: Rexx',
+        'Programming Language :: Ruby',
+        'Programming Language :: Scheme',
+        'Programming Language :: Simula',
+        'Programming Language :: Smalltalk',
+        'Programming Language :: SQL',
+        'Programming Language :: Tcl',
+        'Programming Language :: Unix Shell',
+        'Programming Language :: Visual Basic',
+        'Programming Language :: XBasic',
+        'Programming Language :: YACC',
+        'Programming Language :: Zope',
+        'Topic :: Adaptive Technologies',
+        'Topic :: Artistic Software',
+        'Topic :: Communications',
+        'Topic :: Communications :: BBS',
+        'Topic :: Communications :: Chat',
+        'Topic :: Communications :: Chat :: AOL Instant Messenger',
+        'Topic :: Communications :: Chat :: ICQ',
+        'Topic :: Communications :: Chat :: Internet Relay Chat',
+        'Topic :: Communications :: Chat :: Unix Talk',
+        'Topic :: Communications :: Conferencing',
+        'Topic :: Communications :: Email',
+        'Topic :: Communications :: Email :: Address Book',
+        'Topic :: Communications :: Email :: Email Clients (MUA)',
+        'Topic :: Communications :: Email :: Filters',
+        'Topic :: Communications :: Email :: Mailing List Servers',
+        'Topic :: Communications :: Email :: Mail Transport Agents',
+        'Topic :: Communications :: Email :: Post-Office',
+        'Topic :: Communications :: Email :: Post-Office :: IMAP',
+        'Topic :: Communications :: Email :: Post-Office :: POP3',
+        'Topic :: Communications :: Fax',
+        'Topic :: Communications :: FIDO',
+        'Topic :: Communications :: File Sharing',
+        'Topic :: Communications :: File Sharing :: Gnutella',
+        'Topic :: Communications :: File Sharing :: Napster',
+        'Topic :: Communications :: Ham Radio',
+        'Topic :: Communications :: Internet Phone',
+        'Topic :: Communications :: Telephony',
+        'Topic :: Communications :: Usenet News',
+        'Topic :: Database',
+        'Topic :: Database :: Database Engines/Servers',
+        'Topic :: Database :: Front-Ends',
+        'Topic :: Desktop Environment',
+        'Topic :: Desktop Environment :: File Managers',
+        'Topic :: Desktop Environment :: Gnome',
+        'Topic :: Desktop Environment :: GNUstep',
+        'Topic :: Desktop Environment :: K Desktop Environment (KDE)',
+        'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes',
+        'Topic :: Desktop Environment :: PicoGUI',
+        'Topic :: Desktop Environment :: PicoGUI :: Applications',
+        'Topic :: Desktop Environment :: PicoGUI :: Themes',
+        'Topic :: Desktop Environment :: Screen Savers',
+        'Topic :: Desktop Environment :: Window Managers',
+        'Topic :: Desktop Environment :: Window Managers :: Afterstep',
+        'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: Applets',
+        'Topic :: Desktop Environment :: Window Managers :: Blackbox',
+        'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: CTWM',
+        'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: Enlightenment',
+        'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets',
+        'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15',
+        'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16',
+        'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17',
+        'Topic :: Desktop Environment :: Window Managers :: Fluxbox',
+        'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: FVWM',
+        'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: IceWM',
+        'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: MetaCity',
+        'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: Oroborus',
+        'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: Sawfish',
+        'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30',
+        'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30',
+        'Topic :: Desktop Environment :: Window Managers :: Waimea',
+        'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: Window Maker',
+        'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets',
+        'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes',
+        'Topic :: Desktop Environment :: Window Managers :: XFCE',
+        'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes',
+        'Topic :: Documentation',
+        'Topic :: Education',
+        'Topic :: Education :: Computer Aided Instruction (CAI)',
+        'Topic :: Education :: Testing',
+        'Topic :: Games/Entertainment',
+        'Topic :: Games/Entertainment :: Arcade',
+        'Topic :: Games/Entertainment :: Board Games',
+        'Topic :: Games/Entertainment :: First Person Shooters',
+        'Topic :: Games/Entertainment :: Fortune Cookies',
+        'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)',
+        'Topic :: Games/Entertainment :: Puzzle Games',
+        'Topic :: Games/Entertainment :: Real Time Strategy',
+        'Topic :: Games/Entertainment :: Role-Playing',
+        'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games',
+        'Topic :: Games/Entertainment :: Simulation',
+        'Topic :: Games/Entertainment :: Turn Based Strategy',
+        'Topic :: Home Automation',
+        'Topic :: Internet',
+        'Topic :: Internet :: File Transfer Protocol (FTP)',
+        'Topic :: Internet :: Finger',
+        'Topic :: Internet :: Log Analysis',
+        'Topic :: Internet :: Name Service (DNS)',
+        'Topic :: Internet :: Proxy Servers',
+        'Topic :: Internet :: WAP',
+        'Topic :: Internet :: WWW/HTTP',
+        'Topic :: Internet :: WWW/HTTP :: Browsers',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters',
+        'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
+        'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
+        'Topic :: Internet :: WWW/HTTP :: Site Management',
+        'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking',
+        'Topic :: Internet :: WWW/HTTP :: WSGI',
+        'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+        'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
+        'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
+        'Topic :: Internet :: Z39.50',
+        'Topic :: Multimedia',
+        'Topic :: Multimedia :: Graphics',
+        'Topic :: Multimedia :: Graphics :: 3D Modeling',
+        'Topic :: Multimedia :: Graphics :: 3D Rendering',
+        'Topic :: Multimedia :: Graphics :: Capture',
+        'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera',
+        'Topic :: Multimedia :: Graphics :: Capture :: Scanners',
+        'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture',
+        'Topic :: Multimedia :: Graphics :: Editors',
+        'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based',
+        'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based',
+        'Topic :: Multimedia :: Graphics :: Graphics Conversion',
+        'Topic :: Multimedia :: Graphics :: Presentation',
+        'Topic :: Multimedia :: Graphics :: Viewers',
+        'Topic :: Multimedia :: Sound/Audio',
+        'Topic :: Multimedia :: Sound/Audio :: Analysis',
+        'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
+        'Topic :: Multimedia :: Sound/Audio :: CD Audio',
+        'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing',
+        'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping',
+        'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing',
+        'Topic :: Multimedia :: Sound/Audio :: Conversion',
+        'Topic :: Multimedia :: Sound/Audio :: Editors',
+        'Topic :: Multimedia :: Sound/Audio :: MIDI',
+        'Topic :: Multimedia :: Sound/Audio :: Mixers',
+        'Topic :: Multimedia :: Sound/Audio :: Players',
+        'Topic :: Multimedia :: Sound/Audio :: Players :: MP3',
+        'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis',
+        'Topic :: Multimedia :: Sound/Audio :: Speech',
+        'Topic :: Multimedia :: Video',
+        'Topic :: Multimedia :: Video :: Capture',
+        'Topic :: Multimedia :: Video :: Conversion',
+        'Topic :: Multimedia :: Video :: Display',
+        'Topic :: Multimedia :: Video :: Non-Linear Editor',
+        'Topic :: Office/Business',
+        'Topic :: Office/Business :: Financial',
+        'Topic :: Office/Business :: Financial :: Accounting',
+        'Topic :: Office/Business :: Financial :: Investment',
+        'Topic :: Office/Business :: Financial :: Point-Of-Sale',
+        'Topic :: Office/Business :: Financial :: Spreadsheet',
+        'Topic :: Office/Business :: Groupware',
+        'Topic :: Office/Business :: News/Diary',
+        'Topic :: Office/Business :: Office Suites',
+        'Topic :: Office/Business :: Scheduling',
+        'Topic :: Other/Nonlisted Topic',
+        'Topic :: Printing',
+        'Topic :: Religion',
+        'Topic :: Scientific/Engineering',
+        'Topic :: Scientific/Engineering :: Artificial Intelligence',
+        'Topic :: Scientific/Engineering :: Astronomy',
+        'Topic :: Scientific/Engineering :: Atmospheric Science',
+        'Topic :: Scientific/Engineering :: Bio-Informatics',
+        'Topic :: Scientific/Engineering :: Chemistry',
+        'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
+        'Topic :: Scientific/Engineering :: GIS',
+        'Topic :: Scientific/Engineering :: Human Machine Interfaces',
+        'Topic :: Scientific/Engineering :: Image Recognition',
+        'Topic :: Scientific/Engineering :: Information Analysis',
+        'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator',
+        'Topic :: Scientific/Engineering :: Mathematics',
+        'Topic :: Scientific/Engineering :: Medical Science Apps.',
+        'Topic :: Scientific/Engineering :: Physics',
+        'Topic :: Scientific/Engineering :: Visualization',
+        'Topic :: Security',
+        'Topic :: Security :: Cryptography',
+        'Topic :: Sociology',
+        'Topic :: Sociology :: Genealogy',
+        'Topic :: Sociology :: History',
+        'Topic :: Software Development',
+        'Topic :: Software Development :: Assemblers',
+        'Topic :: Software Development :: Bug Tracking',
+        'Topic :: Software Development :: Build Tools',
+        'Topic :: Software Development :: Code Generators',
+        'Topic :: Software Development :: Compilers',
+        'Topic :: Software Development :: Debuggers',
+        'Topic :: Software Development :: Disassemblers',
+        'Topic :: Software Development :: Documentation',
+        'Topic :: Software Development :: Embedded Systems',
+        'Topic :: Software Development :: Internationalization',
+        'Topic :: Software Development :: Interpreters',
+        'Topic :: Software Development :: Libraries',
+        'Topic :: Software Development :: Libraries :: Application Frameworks',
+        'Topic :: Software Development :: Libraries :: Java Libraries',
+        'Topic :: Software Development :: Libraries :: Perl Modules',
+        'Topic :: Software Development :: Libraries :: PHP Classes',
+        'Topic :: Software Development :: Libraries :: Pike Modules',
+        'Topic :: Software Development :: Libraries :: pygame',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Topic :: Software Development :: Libraries :: Ruby Modules',
+        'Topic :: Software Development :: Libraries :: Tcl Extensions',
+        'Topic :: Software Development :: Localization',
+        'Topic :: Software Development :: Object Brokering',
+        'Topic :: Software Development :: Object Brokering :: CORBA',
+        'Topic :: Software Development :: Pre-processors',
+        'Topic :: Software Development :: Quality Assurance',
+        'Topic :: Software Development :: Testing',
+        'Topic :: Software Development :: Testing :: Traffic Generation',
+        'Topic :: Software Development :: User Interfaces',
+        'Topic :: Software Development :: Version Control',
+        'Topic :: Software Development :: Version Control :: CVS',
+        'Topic :: Software Development :: Version Control :: RCS',
+        'Topic :: Software Development :: Version Control :: SCCS',
+        'Topic :: Software Development :: Widget Sets',
+        'Topic :: System',
+        'Topic :: System :: Archiving',
+        'Topic :: System :: Archiving :: Backup',
+        'Topic :: System :: Archiving :: Compression',
+        'Topic :: System :: Archiving :: Mirroring',
+        'Topic :: System :: Archiving :: Packaging',
+        'Topic :: System :: Benchmark',
+        'Topic :: System :: Boot',
+        'Topic :: System :: Boot :: Init',
+        'Topic :: System :: Clustering',
+        'Topic :: System :: Console Fonts',
+        'Topic :: System :: Distributed Computing',
+        'Topic :: System :: Emulators',
+        'Topic :: System :: Filesystems',
+        'Topic :: System :: Hardware',
+        'Topic :: System :: Hardware :: Hardware Drivers',
+        'Topic :: System :: Hardware :: Mainframes',
+        'Topic :: System :: Hardware :: Symmetric Multi-processing',
+        'Topic :: System :: Installation/Setup',
+        'Topic :: System :: Logging',
+        'Topic :: System :: Monitoring',
+        'Topic :: System :: Networking',
+        'Topic :: System :: Networking :: Firewalls',
+        'Topic :: System :: Networking :: Monitoring',
+        'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
+        'Topic :: System :: Networking :: Time Synchronization',
+        'Topic :: System :: Operating System',
+        'Topic :: System :: Operating System Kernels',
+        'Topic :: System :: Operating System Kernels :: BSD',
+        'Topic :: System :: Operating System Kernels :: GNU Hurd',
+        'Topic :: System :: Operating System Kernels :: Linux',
+        'Topic :: System :: Power (UPS)',
+        'Topic :: System :: Recovery Tools',
+        'Topic :: System :: Shells',
+        'Topic :: System :: Software Distribution',
+        'Topic :: System :: Systems Administration',
+        'Topic :: System :: Systems Administration :: Authentication/Directory',
+        'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
+        'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS',
+        'Topic :: System :: System Shells',
+        'Topic :: Terminals',
+        'Topic :: Terminals :: Serial',
+        'Topic :: Terminals :: Telnet',
+        'Topic :: Terminals :: Terminal Emulators/X Terminals',
+        'Topic :: Text Editors',
+        'Topic :: Text Editors :: Documentation',
+        'Topic :: Text Editors :: Emacs',
+        'Topic :: Text Editors :: Integrated Development Environments (IDE)',
+        'Topic :: Text Editors :: Text Processing',
+        'Topic :: Text Editors :: Word Processors',
+        'Topic :: Text Processing',
+        'Topic :: Text Processing :: Filters',
+        'Topic :: Text Processing :: Fonts',
+        'Topic :: Text Processing :: General',
+        'Topic :: Text Processing :: Indexing',
+        'Topic :: Text Processing :: Linguistic',
+        'Topic :: Text Processing :: Markup',
+        'Topic :: Text Processing :: Markup :: HTML',
+        'Topic :: Text Processing :: Markup :: LaTeX',
+        'Topic :: Text Processing :: Markup :: SGML',
+        'Topic :: Text Processing :: Markup :: VRML',
+        'Topic :: Text Processing :: Markup :: XML',
+        'Topic :: Utilities',
    ]
 
 def askYn(question, default = None, helptext = None):
-	while True:
-		answer = ask(question, default, helptext, required = True)
-		if answer and answer[0].lower() in 'yn':
-			return(answer[0].lower())
+    while True:
+        answer = ask(question, default, helptext, required = True)
+        if answer and answer[0].lower() in 'yn':
+            return(answer[0].lower())
 
-		print '\nERROR: You must select "Y" or "N".\n'
+        print '\nERROR: You must select "Y" or "N".\n'
 
 
 def ask(question, default = None, helptext = None, required = True,
         lengthy = False, multiline = False):
-	prompt = '%s: ' % ( question, )
-	if default:
-		prompt = '%s [%s]: ' % ( question, default )
-		if default and len(question) + len(default) > 70:
-			prompt = '%s\n    [%s]: ' % ( question, default )
-	if lengthy or multiline:
-		prompt += '\n   >'
+    prompt = '%s: ' % ( question, )
+    if default:
+        prompt = '%s [%s]: ' % ( question, default )
+        if default and len(question) + len(default) > 70:
+            prompt = '%s\n    [%s]: ' % ( question, default )
+    if lengthy or multiline:
+        prompt += '\n   >'
 
-	if not helptext: helptext = 'No additional help available.'
-	if helptext[0] == '\n': helptext = helptext[1:]
-	if helptext[-1] == '\n': helptext = helptext[:-1]
+    if not helptext: helptext = 'No additional help available.'
+    if helptext[0] == '\n': helptext = helptext[1:]
+    if helptext[-1] == '\n': helptext = helptext[:-1]
 
-	while True:
-		sys.stdout.write(prompt)
-		sys.stdout.flush()
+    while True:
+        sys.stdout.write(prompt)
+        sys.stdout.flush()
 
-		line = sys.stdin.readline().strip()
-		if line == '?':
-			print '=' * 70
-			print helptext
-			print '=' * 70
-			continue
-		if default and not line: return(default)
-		if not line and required:
-			print '*' * 70
-			print 'This value cannot be empty.'
-			print '==========================='
-			if helptext: print helptext
-			print '*' * 70
-			continue
-		return(line)
+        line = sys.stdin.readline().strip()
+        if line == '?':
+            print '=' * 70
+            print helptext
+            print '=' * 70
+            continue
+        if default and not line: return(default)
+        if not line and required:
+            print '*' * 70
+            print 'This value cannot be empty.'
+            print '==========================='
+            if helptext: print helptext
+            print '*' * 70
+            continue
+        return(line)
 
 
 def buildTroveDict(troveList):
-	dict = {}
-	for key in troveList:
-		subDict = dict
-		for subkey in key.split(' :: '):
-			if not subkey in subDict: subDict[subkey] = {}
-			subDict = subDict[subkey]
-	return(dict)
+    dict = {}
+    for key in troveList:
+        subDict = dict
+        for subkey in key.split(' :: '):
+            if not subkey in subDict: subDict[subkey] = {}
+            subDict = subDict[subkey]
+    return(dict)
 troveDict = buildTroveDict(troveList)
 
 
 class SetupClass:
-	def __init__(self):
-		self.config = None
-		self.classifierDict = {}
-		self.setupData = {}
-		self.setupData['classifier'] = self.classifierDict
-		self.setupData['packages'] = {}
+    def __init__(self):
+        self.config = None
+        self.classifierDict = {}
+        self.setupData = {}
+        self.setupData['classifier'] = self.classifierDict
+        self.setupData['packages'] = {}
 
-		self.loadConfigFile()
+        self.loadConfigFile()
 
 
-	def lookupOption(self, key):
-		if not self.config.has_option('DEFAULT', key): return(None)
-		return(self.config.get('DEFAULT', key))
+    def lookupOption(self, key):
+        if not self.config.has_option('DEFAULT', key): return(None)
+        return(self.config.get('DEFAULT', key))
 
 
-	def loadConfigFile(self):
-		self.config = ConfigParser.RawConfigParser()
-		self.config.read(os.path.expanduser('~/.mkpkgpy'))
-		self.setupData['author'] = self.lookupOption('author')
-		self.setupData['author_email'] = self.lookupOption('author_email')
+    def loadConfigFile(self):
+        self.config = ConfigParser.RawConfigParser()
+        self.config.read(os.path.expanduser('~/.mkpkgpy'))
+        self.setupData['author'] = self.lookupOption('author')
+        self.setupData['author_email'] = self.lookupOption('author_email')
 
 
-	def updateConfigFile(self):
-		valuesDifferent = False
-		for compareKey in ('author', 'author_email'):
-			if self.lookupOption(compareKey) != self.setupData[compareKey]:
-				valuesDifferent = True
-				self.config.set('DEFAULT', compareKey, self.setupData[compareKey])
+    def updateConfigFile(self):
+        valuesDifferent = False
+        for compareKey in ('author', 'author_email'):
+            if self.lookupOption(compareKey) != self.setupData[compareKey]:
+                valuesDifferent = True
+                self.config.set('DEFAULT', compareKey,
+                    self.setupData[compareKey])
 
-		if not valuesDifferent: return
+        if not valuesDifferent: return
 
-		self.config.write(open(os.path.expanduser('~/.pygiver'), 'w'))
+        self.config.write(open(os.path.expanduser('~/.pygiver'), 'w'))
 
 
-	def loadExistingSetup(self):
-		raise NotImplementedError
+    def loadExistingSetup(self):
+        raise NotImplementedError
 
 
-	def inspectFile(self, path):
-		fp = open(path, 'r')
-		for line in [ fp.readline() for x in range(10) ]:
-			m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line)
-			if m:
-				if m.group('major') == '3':
-					self.classifierDict['Programming Language :: Python :: 3'] = 1
-				else:
-					self.classifierDict['Programming Language :: Python :: 2'] = 1
-		fp.close()
+    def inspectFile(self, path):
+        fp = open(path, 'r')
+        for line in [ fp.readline() for x in range(10) ]:
+            m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line)
+            if m:
+                if m.group('major') == '3':
+                    self.classifierDict['Programming Language :: Python :: 3'] = 1
+                else:
+                    self.classifierDict['Programming Language :: Python :: 2'] = 1
+        fp.close()
 
 
-	def inspectDirectory(self):
-		dirName = os.path.basename(os.getcwd())
-		self.setupData['name'] = dirName
-		m = re.match(r'(.*)-(\d.+)', dirName)
-		if m:
-			self.setupData['name'] = m.group(1)
-			self.setupData['version'] = m.group(2)
+    def inspectDirectory(self):
+        dirName = os.path.basename(os.getcwd())
+        self.setupData['name'] = dirName
+        m = re.match(r'(.*)-(\d.+)', dirName)
+        if m:
+            self.setupData['name'] = m.group(1)
+            self.setupData['version'] = m.group(2)
 
-		for root, dirs, files in os.walk('.'):
-			for file in files:
-				if root == '.' and file == 'setup.py': continue
-				fileName = os.path.join(root, file)
-				self.inspectFile(fileName)
+        for root, dirs, files in os.walk('.'):
+            for file in files:
+                if root == '.' and file == 'setup.py': continue
+                fileName = os.path.join(root, file)
+                self.inspectFile(fileName)
 
-				if file == '__init__.py':
-					trySrc = os.path.join('.', 'src')
-					tmpRoot = root
-					if tmpRoot.startswith(trySrc):
-						tmpRoot = tmpRoot[len(trySrc):]
-					if tmpRoot.startswith(os.path.sep):
-						tmpRoot = tmpRoot[len(os.path.sep):]
+                if file == '__init__.py':
+                    trySrc = os.path.join('.', 'src')
+                    tmpRoot = root
+                    if tmpRoot.startswith(trySrc):
+                        tmpRoot = tmpRoot[len(trySrc):]
+                    if tmpRoot.startswith(os.path.sep):
+                        tmpRoot = tmpRoot[len(os.path.sep):]
 
-					self.setupData['packages'][tmpRoot] = root[1 + len(os.path.sep):]
+                    self.setupData['packages'][tmpRoot] = root[1 + len(os.path.sep):]
 
 
-	def queryUser(self):
-		self.setupData['name'] = ask('Package name', self.setupData['name'],
-		      helpText['name'])
-		self.setupData['version'] = ask('Current version number',
-		      self.setupData.get('version'), helpText['version'])
-		self.setupData['description'] = ask('Package description',
-		      self.setupData.get('description'), helpText['description'],
-		      lengthy = True)
-		self.setupData['author'] = ask('Author name',
-		      self.setupData.get('author'), helpText['author'])
-		self.setupData['author_email'] = ask('Author e-mail address',
-		      self.setupData.get('author_email'), helpText['author_email'])
-		self.setupData['url'] = ask('Project URL',
-		      self.setupData.get('url'), helpText['url'], required = False)
+    def queryUser(self):
+        self.setupData['name'] = ask('Package name', self.setupData['name'],
+              helpText['name'])
+        self.setupData['version'] = ask('Current version number',
+              self.setupData.get('version'), helpText['version'])
+        self.setupData['description'] = ask('Package description',
+              self.setupData.get('description'), helpText['description'],
+              lengthy = True)
+        self.setupData['author'] = ask('Author name',
+              self.setupData.get('author'), helpText['author'])
+        self.setupData['author_email'] = ask('Author e-mail address',
+              self.setupData.get('author_email'), helpText['author_email'])
+        self.setupData['url'] = ask('Project URL',
+              self.setupData.get('url'), helpText['url'], required = False)
 
-		if (askYn('Do you want to set Trove classifiers?',
-				helptext = helpText['do_classifier']) == 'y'):
-			self.setTroveClassifier()
+        if (askYn('Do you want to set Trove classifiers?',
+                helptext = helpText['do_classifier']) == 'y'):
+            self.setTroveClassifier()
 
 
-	def setTroveClassifier(self):
-		self.setTroveDevStatus(self.classifierDict)
-		self.setTroveLicense(self.classifierDict)
-		self.setTroveOther(self.classifierDict)
+    def setTroveClassifier(self):
+        self.setTroveDevStatus(self.classifierDict)
+        self.setTroveLicense(self.classifierDict)
+        self.setTroveOther(self.classifierDict)
 
 
-	def setTroveOther(self, classifierDict):
-		if askYn('Do you want to set other trove identifiers', 'n',
-				helpText['trove_generic']) != 'y': return
+    def setTroveOther(self, classifierDict):
+        if askYn('Do you want to set other trove identifiers', 'n',
+                helpText['trove_generic']) != 'y': return
 
-		self.walkTrove(classifierDict, [troveDict], '')
-	
-	def walkTrove(self, classifierDict, trovePath, desc):
-		trove = trovePath[-1]
+        self.walkTrove(classifierDict, [troveDict], '')
 
-		if not trove:
-			return
+    def walkTrove(self, classifierDict, trovePath, desc):
+        trove = trovePath[-1]
 
-		for key in sorted(trove.keys()):
-			if len(trove[key]) == 0:
-				if askYn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y':
-					classifierDict[desc[4:] + ' :: ' + key] = 1
-				continue
+        if not trove:
+            return
 
-			if askYn('Do you want to set items under\n   "%s" (%d sub-items)'
-					% ( key, len(trove[key]) ), 'n',
-					helpText['trove_generic']) == 'y':
-				self.walkTrove(classifierDict, trovePath + [trove[key]],
-						desc + ' :: ' + key)
+        for key in sorted(trove.keys()):
+            if len(trove[key]) == 0:
+                if askYn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y':
+                    classifierDict[desc[4:] + ' :: ' + key] = 1
+                continue
 
+            if askYn('Do you want to set items under\n   "%s" (%d sub-items)'
+                    % ( key, len(trove[key]) ), 'n',
+                    helpText['trove_generic']) == 'y':
+                self.walkTrove(classifierDict, trovePath + [trove[key]],
+                        desc + ' :: ' + key)
 
-	def setTroveLicense(self, classifierDict):
-		while True:
-			license = ask('What license do you use',
-			      helptext = helpText['trove_license'], required = False)
-			if not license: return
 
-			licenseWords = license.lower().split(' ')
+    def setTroveLicense(self, classifierDict):
+        while True:
+            license = ask('What license do you use',
+                  helptext = helpText['trove_license'], required = False)
+            if not license: return
 
-			foundList = []
-			for index in range(len(troveList)):
-				troveItem = troveList[index]
-				if not troveItem.startswith('License :: '): continue
-				troveItem = troveItem[11:].lower()
+            licenseWords = license.lower().split(' ')
 
-				allMatch = True
-				for word in licenseWords:
-					if not word in troveItem:
-						allMatch = False
-						break
-				if allMatch: foundList.append(index)
+            foundList = []
+            for index in range(len(troveList)):
+                troveItem = troveList[index]
+                if not troveItem.startswith('License :: '): continue
+                troveItem = troveItem[11:].lower()
 
-			question = 'Matching licenses:\n\n'
-			for i in xrange(1, len(foundList) + 1):
-				question += '   %s) %s\n' % ( i, troveList[foundList[i - 1]] )
-			question += ('\nType the number of the license you wish to use or '
-			         '? to try again:')
-			troveLicense = ask(question, required = False)
+                allMatch = True
+                for word in licenseWords:
+                    if not word in troveItem:
+                        allMatch = False
+                        break
+                if allMatch: foundList.append(index)
 
-			if troveLicense == '?': continue
-			if troveLicense == '': return
-			foundIndex = foundList[int(troveLicense) - 1]
-			classifierDict[troveList[foundIndex]] = 1
-			try:
-				return
-			except IndexError:
-				print 'ERROR: Invalid selection, type a number from the list above.'
+            question = 'Matching licenses:\n\n'
+            for i in xrange(1, len(foundList) + 1):
+                question += '   %s) %s\n' % ( i, troveList[foundList[i - 1]] )
+            question += ('\nType the number of the license you wish to use or '
+                     '? to try again:')
+            troveLicense = ask(question, required = False)
 
+            if troveLicense == '?': continue
+            if troveLicense == '': return
+            foundIndex = foundList[int(troveLicense) - 1]
+            classifierDict[troveList[foundIndex]] = 1
+            try:
+                return
+            except IndexError:
+                print("ERROR: Invalid selection, type a number from the list "
+                    "above.")
 
-	def setTroveDevStatus(self, classifierDict):
-		while True:
-			devStatus = ask('''Please select the project status:
+
+    def setTroveDevStatus(self, classifierDict):
+        while True:
+            devStatus = ask('''Please select the project status:
 
 1 - Planning
 2 - Pre-Alpha
@@ -851,71 +853,80 @@
 7 - Inactive
 
 Status''', required = False)
-			if devStatus:
-				try:
-					key = {
-						   '1' : 'Development Status :: 1 - Planning',
-						   '2' : 'Development Status :: 2 - Pre-Alpha',
-						   '3' : 'Development Status :: 3 - Alpha',
-						   '4' : 'Development Status :: 4 - Beta',
-						   '5' : 'Development Status :: 5 - Production/Stable',
-						   '6' : 'Development Status :: 6 - Mature',
-						   '7' : 'Development Status :: 7 - Inactive',
-						   }[devStatus]
-					classifierDict[key] = 1
-					return
-				except KeyError:
-					print 'ERROR: Invalid selection, type a single digit number.'
+            if devStatus:
+                try:
+                    key = {
+                           '1' : 'Development Status :: 1 - Planning',
+                           '2' : 'Development Status :: 2 - Pre-Alpha',
+                           '3' : 'Development Status :: 3 - Alpha',
+                           '4' : 'Development Status :: 4 - Beta',
+                           '5' : 'Development Status :: 5 - Production/Stable',
+                           '6' : 'Development Status :: 6 - Mature',
+                           '7' : 'Development Status :: 7 - Inactive',
+                           }[devStatus]
+                    classifierDict[key] = 1
+                    return
+                except KeyError:
+                    print("ERROR: Invalid selection, type a single digit "
+                        "number.")
 
+    def _dotted_packages(self, data):
+        packages = sorted(data.keys())
+        modified_pkgs = []
+        for pkg in packages:
+            pkg = pkg.lstrip('./')
+            pkg = pkg.replace('/', '.')
+            modified_pkgs.append(pkg)
+        return modified_pkgs
 
-	def writeSetup(self):
-		if os.path.exists('setup.py'): shutil.move('setup.py', 'setup.py.old')
+    def writeSetup(self):
+        if os.path.exists('setup.py'): shutil.move('setup.py', 'setup.py.old')
 
-		fp = open('setup.py', 'w')
-		fp.write('#!/usr/bin/env python\n\n')
-		fp.write('from distutils2.core import setup\n\n')
+        fp = open('setup.py', 'w')
+        fp.write('#!/usr/bin/env python\n\n')
+        fp.write('from distutils2.core import setup\n\n')
 
-		fp.write('from sys import version\n')
-		fp.write('if version < \'2.2.3\':\n')
-		fp.write('    from distutils2.dist import DistributionMetadata\n')
-		fp.write('    DistributionMetadata.classifier = None\n')
-		fp.write('    DistributionMetadata.download_url = None\n')
+        fp.write('from sys import version\n')
+        fp.write('if version < \'2.2.3\':\n')
+        fp.write('    from distutils2.dist import DistributionMetadata\n')
+        fp.write('    DistributionMetadata.classifier = None\n')
+        fp.write('    DistributionMetadata.download_url = None\n')
 
-		fp.write('setup(name = %s,\n' % repr(self.setupData['name']))
-		fp.write('        version = %s,\n' % repr(self.setupData['version']))
-		fp.write('        description = %s,\n'
-				% repr(self.setupData['description']))
-		fp.write('        author = %s,\n' % repr(self.setupData['author']))
-		fp.write('        author_email = %s,\n'
-				% repr(self.setupData['author_email']))
-		if self.setupData['url']:
-			fp.write('        url = %s,\n' % repr(self.setupData['url']))
-		if self.setupData['classifier']:
-			fp.write('        classifier = [\n')
-			for classifier in sorted(self.setupData['classifier'].keys()):
-				fp.write('              %s,\n' % repr(classifier))
-			fp.write('           ],\n')
-		if self.setupData['packages']:
-			fp.write('        packages = %s,\n'
-					% repr(sorted(self.setupData['packages'].keys())))
-			fp.write('        package_dir = %s,\n'
-					% repr(self.setupData['packages']))
-		fp.write('        #scripts = [\'path/to/script\']\n')
+        fp.write('setup(name = %s,\n' % repr(self.setupData['name']))
+        fp.write('        version = %s,\n' % repr(self.setupData['version']))
+        fp.write('        description = %s,\n'
+                % repr(self.setupData['description']))
+        fp.write('        author = %s,\n' % repr(self.setupData['author']))
+        fp.write('        author_email = %s,\n'
+                % repr(self.setupData['author_email']))
+        if self.setupData['url']:
+            fp.write('        url = %s,\n' % repr(self.setupData['url']))
+        if self.setupData['classifier']:
+            fp.write('        classifier = [\n')
+            for classifier in sorted(self.setupData['classifier'].keys()):
+                fp.write('              %s,\n' % repr(classifier))
+            fp.write('           ],\n')
+        if self.setupData['packages']:
+            fp.write('        packages = %s,\n'
+                    % repr(self._dotted_packages(self.setupData['packages'])))
+            fp.write('        package_dir = %s,\n'
+                    % repr(self.setupData['packages']))
+        fp.write('        #scripts = [\'path/to/script\']\n')
 
-		fp.write('        )\n')
-		fp.close()
-		os.chmod('setup.py', 0755)
+        fp.write('        )\n')
+        fp.close()
+        os.chmod('setup.py', 0755)
 
-		print 'Wrote "setup.py".'
+        print 'Wrote "setup.py".'
 
 
 def main():
-	setup = SetupClass()
-	setup.inspectDirectory()
-	setup.queryUser()
-	setup.updateConfigFile()
-	setup.writeSetup()
+    setup = SetupClass()
+    setup.inspectDirectory()
+    setup.queryUser()
+    setup.updateConfigFile()
+    setup.writeSetup()
 
 
 if __name__ == '__main__':
-	main()
+    main()
diff --git a/src/distutils2/tests/test_check.py b/src/distutils2/tests/test_check.py
--- a/src/distutils2/tests/test_check.py
+++ b/src/distutils2/tests/test_check.py
@@ -27,7 +27,7 @@
         # by default, check is checking the metadata
         # should have some warnings
         cmd = self._run()
-        self.assert_(cmd._warnings > 0)
+        self.assert_(len(cmd._warnings) > 0)
 
         # now let's add the required fields
         # and run it again, to make sure we don't get
@@ -37,7 +37,7 @@
                     'name': 'xxx', 'version': 'xxx'
                     }
         cmd = self._run(metadata)
-        self.assertEquals(cmd._warnings, 0)
+        self.assertEquals(len(cmd._warnings), 0)
 
         # now with the strict mode, we should
         # get an error if there are missing metadata
@@ -45,34 +45,35 @@
 
         # and of course, no error when all metadata are present
         cmd = self._run(metadata, strict=1)
-        self.assertEquals(cmd._warnings, 0)
+        self.assertEquals(len(cmd._warnings), 0)
 
     def test_check_restructuredtext(self):
         if not _HAS_DOCUTILS: # won't test without docutils
             return
         # let's see if it detects broken rest in long_description
         broken_rest = 'title\n===\n\ntest'
-        pkg_info, dist = self.create_dist(long_description=broken_rest)
+        pkg_info, dist = self.create_dist(description=broken_rest)
         cmd = check(dist)
         cmd.check_restructuredtext()
-        self.assertEquals(cmd._warnings, 1)
+        self.assertEquals(len(cmd._warnings), 1)
 
         # let's see if we have an error with strict=1
-        metadata = {'url': 'xxx', 'author': 'xxx',
+        metadata = {'home_page': 'xxx', 'author': 'xxx',
                     'author_email': 'xxx',
                     'name': 'xxx', 'version': 'xxx',
-                    'long_description': broken_rest}
+                    'description': broken_rest}
+
         self.assertRaises(DistutilsSetupError, self._run, metadata,
                           **{'strict': 1, 'restructuredtext': 1})
 
         # and non-broken rest
-        metadata['long_description'] = 'title\n=====\n\ntest'
+        metadata['description'] = 'title\n=====\n\ntest'
         cmd = self._run(metadata, strict=1, restructuredtext=1)
-        self.assertEquals(cmd._warnings, 0)
+        self.assertEquals(len(cmd._warnings), 0)
 
     def test_check_all(self):
 
-        metadata = {'url': 'xxx', 'author': 'xxx'}
+        metadata = {'home_page': 'xxx', 'author': 'xxx'}
         self.assertRaises(DistutilsSetupError, self._run,
                           {}, **{'strict': 1,
                                  'restructuredtext': 1})
diff --git a/src/distutils2/tests/test_metadata.py b/src/distutils2/tests/test_metadata.py
--- a/src/distutils2/tests/test_metadata.py
+++ b/src/distutils2/tests/test_metadata.py
@@ -185,6 +185,22 @@
 
         self.assertEquals(res, 0)
 
+    def test_project_url(self):
+        metadata = DistributionMetadata()
+        metadata['Project-URL'] = [('one', 'http://ok')]
+        self.assertEquals(metadata['Project-URL'],
+                          [('one', 'http://ok')])
+        self.assertEquals(metadata.version, '1.2')
+
+    def test_check(self):
+        metadata = DistributionMetadata()
+        metadata['Version'] = 'rr'
+        metadata['Requires-dist'] = ['Foo (a)']
+        missing, warnings = metadata.check()
+        self.assertEquals(missing, ['Name', 'Home-page'])
+        self.assertEquals(len(warnings), 2)
+
+
 def test_suite():
     return unittest2.makeSuite(DistributionMetadataTestCase)
 
diff --git a/src/distutils2/tests/test_register.py b/src/distutils2/tests/test_register.py
--- a/src/distutils2/tests/test_register.py
+++ b/src/distutils2/tests/test_register.py
@@ -7,6 +7,12 @@
 import urllib2
 import warnings
 
+try:
+    import docutils
+    DOCUTILS_SUPPORT = True
+except ImportError:
+    DOCUTILS_SUPPORT = False
+
 from distutils2.command import register as register_module
 from distutils2.command.register import register
 from distutils2.core import Distribution
@@ -186,6 +192,7 @@
         self.assertEquals(headers['Content-length'], '290')
         self.assertTrue('tarek' in req.data)
 
+    @unittest2.skipUnless(DOCUTILS_SUPPORT, 'needs docutils')
     def test_strict(self):
         # testing the script option
         # when on, the register command stops if
@@ -198,26 +205,20 @@
         cmd.strict = 1
         self.assertRaises(DistutilsSetupError, cmd.run)
 
-        # we don't test the reSt feature if docutils
-        # is not installed
-        try:
-            import docutils
-        except ImportError:
-            return
-
         # metadata are OK but long_description is broken
-        metadata = {'url': 'xxx', 'author': 'xxx',
+        metadata = {'home_page': 'xxx', 'author': 'xxx',
                     'author_email': u'éxéxé',
                     'name': 'xxx', 'version': 'xxx',
-                    'long_description': 'title\n==\n\ntext'}
+                    'description': 'title\n==\n\ntext'}
 
         cmd = self._get_cmd(metadata)
         cmd.ensure_finalized()
         cmd.strict = 1
+
         self.assertRaises(DistutilsSetupError, cmd.run)
 
         # now something that works
-        metadata['long_description'] = 'title\n=====\n\ntext'
+        metadata['description'] = 'title\n=====\n\ntext'
         cmd = self._get_cmd(metadata)
         cmd.ensure_finalized()
         cmd.strict = 1
diff --git a/src/distutils2/tests/test_util.py b/src/distutils2/tests/test_util.py
--- a/src/distutils2/tests/test_util.py
+++ b/src/distutils2/tests/test_util.py
@@ -6,7 +6,10 @@
 from StringIO import StringIO
 import subprocess
 
-from distutils2.errors import DistutilsPlatformError, DistutilsByteCompileError
+from distutils2.errors import (DistutilsPlatformError,
+                               DistutilsByteCompileError,
+                               DistutilsFileError)
+
 from distutils2.util import (convert_path, change_root,
                             check_environ, split_quoted, strtobool,
                             rfc822_escape, get_compiler_versions,
@@ -250,6 +253,10 @@
         finally:
             sys.dont_write_bytecode = old_dont_write_bytecode
 
+    def test_newer(self):
+        self.assertRaises(DistutilsFileError, util.newer, 'xxx', 'xxx')
+
+
 def test_suite():
     return unittest2.makeSuite(UtilTestCase)
 
diff --git a/src/distutils2/util.py b/src/distutils2/util.py
--- a/src/distutils2/util.py
+++ b/src/distutils2/util.py
@@ -8,11 +8,10 @@
 
 import sys, os, string, re
 
-from distutils2.errors import DistutilsPlatformError
+from distutils2.errors import (DistutilsPlatformError, DistutilsFileError,
+                               DistutilsByteCompileError)
 from distutils2.spawn import spawn, find_executable
 from distutils2 import log
-from distutils2.errors import DistutilsByteCompileError
-
 from distutils2._backport import sysconfig as _sysconfig
 
 _PLATFORM = None
diff --git a/src/setup.py b/src/setup.py
--- a/src/setup.py
+++ b/src/setup.py
@@ -65,7 +65,7 @@
 
 setup_kwargs = {}
 if sys.version < '2.6':
-    kwargs['scripts'] = 'distutils2/mkpkg.py'
+    setup_kwargs['scripts'] = ['distutils2/mkpkg.py']
 
 setup (name="Distutils2",
        version=VERSION,

--
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list