##############################################################################
#
# 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)
