############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for reading and generating publication metadata files. Such files include the PKG-INFO files generated by `distutils` as well as the PUBLICATION.cfg files used by **zpkg**. :var PUBLICATION_CONF: The default name of the file containing publication data as used by **zpkg**. """ from distutils.dist import DistributionMetadata from distutils.util import rfc822_escape from email.Parser import HeaderParser from StringIO import StringIO PUBLICATION_CONF = "PUBLICATION.cfg" # XXX The dump() and dumps() methods are very similar to the # DistributionMetadata.write_pkg_info() method, but don't constrain # where the data is written. Much of this can be discarded if # portions of the PEP 262 patch (http://www.python.org/sf/562100) are # accepted. def dump(metadata, f): """Write package metadata to a file in PKG-INFO format. :param metadata: Metadata object to serialize. :param f: Open file object to write to. """ metadata_version = "1.0" if (metadata.maintainer or metadata.maintainer_email or metadata.url or metadata.get_classifiers()): metadata_version = "1.1" print >>f, "Metadata-Version:", metadata_version print >>f, "Name:", metadata.get_name() if metadata.version: print >>f, "Version:", metadata.get_version() if metadata.description: print >>f, "Summary:", metadata.get_description() if metadata.url: print >>f, "Home-page:", metadata.get_url() if metadata.author: print >>f, "Author:", metadata.get_author() if metadata.author_email: print >>f, "Author-email:", metadata.get_author_email() if metadata.maintainer: print >>f, "Maintainer:", metadata.get_maintainer() if metadata.maintainer_email: print >>f, "Maintainer-email:", metadata.get_maintainer_email() if metadata.license: print >>f, "License:", metadata.get_license() if metadata.url: print >>f, "Download-URL:", metadata.url if metadata.long_description: long_desc = rfc822_escape(metadata.get_long_description()) print >>f, "Description:", long_desc keywords = metadata.get_keywords() if keywords: print >>f, "Keywords:", ", ".join(keywords) for platform in metadata.get_platforms(): print >>f, "Platform:", platform for classifier in metadata.get_classifiers(): print >>f, "Classifier:", classifier def dumps(metadata): """Return package metadata serialized in PKG-INFO format. :return: String containing the serialized metadata. :rtype: str :param metadata: Metadata object to serialize. """ sio = StringIO() dump(metadata, sio) return sio.getvalue() def load(f, versioninfo=False, metadata=None): """Parse a PKG-INFO file and return a DistributionMetadata instance. :return: Populated metadata object. :rtype: `DistributionMetadata` :param versioninfo: Flag indicating whether version-specific information should be included. :param metadata: Metadata object which should be populated from the publication data. If omitted, a fresh `DistributionMetadata` instance will be used. """ parser = HeaderParser() msg = parser.parse(f) return _loadmsg(msg, versioninfo, metadata) def loads(text, versioninfo=False, metadata=None): """Parse PKG-INFO source text and return a DistributionMetadata instance. :return: Populated metadata object. :rtype: `DistributionMetadata` :param versioninfo: Flag indicating whether version-specific information should be included. :param metadata: Metadata object which should be populated from the publication data. If omitted, a fresh `DistributionMetadata` instance will be used. """ parser = HeaderParser() msg = parser.parsestr(text) return _loadmsg(msg, versioninfo, metadata) def _loadmsg(msg, versioninfo, metadata=None): if metadata is None: metadata = DistributionMetadata() if versioninfo: metadata.version = _get_single_header(msg, "Version") metadata.download_url = _get_single_header(msg, "Download-URL") metadata.name = _get_single_header(msg, "Name") metadata.author = _get_single_header(msg, "Author") metadata.author_email = _get_single_header(msg, "Author-email") metadata.maintainer = _get_single_header(msg, "Maintainer") metadata.maintainer_email = _get_single_header(msg, "Maintainer-email") metadata.url = _get_single_header(msg, "Home-page") metadata.license = _get_single_header(msg, "License") metadata.description = _get_single_header(msg, "Summary") metadata.long_description = _get_single_header(msg, "Description") keywords = _get_single_header(msg, "Keywords", "") keywords = [s.strip() for s in keywords.split(",") if s.strip()] metadata.keywords = keywords or None platforms = msg.get_all("Platform") if platforms: metadata.platforms = platforms classifiers = msg.get_all("Classifier") if classifiers: metadata.classifiers = classifiers return metadata def _get_single_header(msg, name, default=None): """Return the value for a header that only occurs once in the input. :raises ValueError: If the header occurs more than once. """ headers = msg.get_all(name) if headers and len(headers) > 1: raise ValueError("header %r can only be given once" % name) if headers: v = headers[0] if v == "UNKNOWN": return None else: return v else: return default ALPHA = "Development Status :: 3 - Alpha" BETA = "Development Status :: 4 - Beta" STABLE = "Development Status :: 5 - Production/Stable" def set_development_status(metadata, status): if not metadata.classifiers: metadata.classifiers = [status] return for i in range(len(metadata.classifiers)): classifier = metadata.classifiers[i] parts = [s.strip() for s in classifier.lower().split("::")] if parts[0] == "development status": metadata.classifiers[i] = status break else: metadata.classifiers.append(status)