[Python-checkins] distutils2: merged Pumazi work
tarek.ziade
python-checkins at python.org
Mon Apr 5 23:09:20 CEST 2010
tarek.ziade pushed 4c5701baf92f to distutils2:
http://hg.python.org/distutils2/rev/4c5701baf92f
changeset: 109:4c5701baf92f
parent: 85:a59c69e26de6
parent: 108:aade6bc810a5
user: Tarek Ziade <tarek at ziade.org>
date: Mon Apr 05 23:06:50 2010 +0200
summary: merged Pumazi work
files:
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,272 @@
+# -*- coding: utf-8 -*-
+"""Tests for PEP 376 pkgutil functionality"""
+import unittest2
+import sys
+import os
+import csv
+import hashlib
+
+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 = hashlib.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/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()
--
Repository URL: http://hg.python.org/distutils2
More information about the Python-checkins
mailing list