[Zope-CVS] CVS: Packages/zpkgtools/zpkgtools - cvsloader.py:1.1 dependencies.py:1.1 include.py:1.1 pep262.py:1.1 publication.py:1.1 setup.py:1.1

Fred L. Drake, Jr. fred at zope.com
Fri Mar 5 16:39:23 EST 2004


Update of /cvs-repository/Packages/zpkgtools/zpkgtools
In directory cvs.zope.org:/tmp/cvs-serv10053/zpkgtools

Added Files:
	cvsloader.py dependencies.py include.py pep262.py 
	publication.py setup.py 
Log Message:
first steps for the release assembler


=== Added File Packages/zpkgtools/zpkgtools/cvsloader.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Data loader that looks in CVS for content."""

import copy
import os
import posixpath
import re
import shutil
import tempfile


class CvsLoadingError(Exception):
    """Raised when there was some error loading from CVS."""

    def __init__(self, cvsurl, exitcode):
        self.cvsurl = cvsurl
        self.exitcode = exitcode
        Exception.__init__(self, ("could not load from %s (cvs exit code %d)"
                                  % (cvsurl.getUrl(), exitcode)))


_cvs_url_match = re.compile(
    """
    cvs://(?P<host>[^/]*)
    /(?P<cvsroot>[^:]*)
    (:(?P<path>[^:]*)
    (:(?P<tag>[^:]*))?)?$
    """,
    re.IGNORECASE | re.VERBOSE).match

_repository_url_match = re.compile(
    """
    repository:(?P<path>[^:]*)
    (:(?P<tag>[^:]*))?$
    """,
    re.IGNORECASE | re.VERBOSE).match

def parse(cvsurl):
    m = _cvs_url_match(cvsurl)
    if m is None:
        m = _repository_url_match(cvsurl)
        if m is None:
            raise ValueError("not a valid CVS url")
        return RepositoryUrl(m.group("path"), m.group("tag"))
    host = m.group("host")
    cvsroot = "/" + m.group("cvsroot")
    path = m.group("path")
    tag = m.group("tag") or ""
    username = None
    password = None
    type = None
    if "@" in host:
        userinfo, host = host.split("@", 1)
        if ":" in userinfo:
            username, password = userinfo.split(":", 1)
        else:
            username = userinfo
    if ":" in host:
        host, type = host.split(":", 1)
    return CvsUrl(type, host, cvsroot, path, tag, username, password)


def fromPath(path):
    path = os.path.normpath(path)
    if os.path.isdir(path):
        dirname = path
        basename = ""
    else:
        dirname, basename = os.path.split(path)
    cvsdir = os.path.join(dirname, "CVS")
    tagfile = os.path.join(cvsdir, "Tag")
    if os.path.isfile(tagfile):
        tag = _read_one_line(tagfile)[1:]
    else:
        tag = None
    if basename:
        # The tag may be overridden for specific files; check:
        entries = os.path.join(cvsdir, "Entries")
        if os.path.isfile(entries):
            entries = open(entries, "rU")
            for line in entries:
                parts = line.split("/")
                if os.path.normcase(parts[1]) == os.path.normcase(basename):
                    if len(parts) >= 6:
                        tag = parts[5][1:].rstrip() or tag
                    break
    modpath = _read_one_line(os.path.join(cvsdir, "Repository"))
    repo = _read_one_line(os.path.join(cvsdir, "Root"))
    host = ""
    type = username = None
    if repo[:1] == ":":
        parts = repo.split(":")
        type = parts[1]
        host = parts[2]
        cvsroot = parts[3]
    elif ":" in repo:
        host, cvsroot = repo.split(":", 1)
    if "@" in host:
        username, host = host.split("@")
    return CvsUrl(type, host, cvsroot,
                  posixpath.join(modpath, basename),
                  tag, username)


def _read_one_line(filename):
    f = open(filename, "rU")
    try:
        line = f.readline()
    finally:
        f.close()
    return line.rstrip()


class CvsUrl:
    def __init__(self, type, host, cvsroot, path,
                 tag=None, username=None, password=None):
        assert cvsroot.startswith("/")
        self.type = type or None
        self.host = host or None
        self.cvsroot = cvsroot
        self.path = path
        self.tag = tag or None
        self.username = username or None
        self.password = password or None

    def getCvsRoot(self):
        s = ""
        if self.type:
            s = ":%s:" % self.type
        if self.username:
            s = "%s%s@" % (s, self.username)
        if self.host:
            s = "%s%s:" % (s, self.host)
        return s + self.cvsroot

    def getUrl(self):
        host = self.host or ""
        if self.type:
            host = "%s:%s" % (host, self.type)
        if self.username:
            username = self.username
            if self.password:
                username = "%s:%s" % (username, self.password)
            host = "%s@%s" % (username, host)
        url = "cvs://%s%s:%s" % (host, self.cvsroot, self.path)
        if self.tag:
            url = "%s:%s" % (url, self.tag)
        return url


class RepositoryUrl:
    def __init__(self, path, tag=None):
        self.path = path or None
        self.tag = tag or None

    def getUrl(self):
        url = "repository:" + self.path
        if self.tag:
            url = "%s:%s" % (url, self.tag)
        return url

    def join(self, cvsurl):
        cvsurl = copy.copy(cvsurl)
        if self.path:
            path = posixpath.normpath(self.path)
            if path[:1] == "/":
                newpath = path[1:]
            else:
                newpath = posixpath.join(cvsurl.path, self.path)
        else:
            newpath = cvsurl.path
        cvsurl.path = posixpath.normpath(newpath)
        if self.tag:
            cvsurl.tag = self.tag
        return cvsurl


class CvsLoader:

    def __init__(self, cvsurl, tag=None):
        self.cvsurl = cvsurl
        self.tag = tag or None
        self.workdirs = []

    def cleanup(self):
        """Remove all checkouts that are present."""
        while self.workdirs:
            d = self.workdirs.pop()
            shutil.rmtree(d)

    def load(self, url):
        """Load resource from URL into a temporary location.

        Returns the location of the resource once loaded.
        """
        if isinstance(url, basestring):
            try:
                url = parse(url)
            except ValueError:
                raise TypeError(
                    "load() requires a cvs or repository URL; received %r"
                    % url)
        if isinstance(url, RepositoryUrl):
            cvsurl = url.join(self.cvsurl)
        elif isinstance(url, CvsUrl):
            cvsurl = copy.copy(url)
        else:
            raise TypeError("load() requires a cvs or repository URL")
        if not cvsurl.tag:
            cvsurl.tag = self.tag
        workdir = tempfile.mkdtemp(prefix="cvsloader-")
        cvsroot = cvsurl.getCvsRoot()
        tag = cvsurl.tag or "HEAD"
        path = cvsurl.path or "."

        rc = self.runCvsExport(cvsroot, workdir, tag, path)
        if rc:
            # some error occurred; haven't figured out what, and don't
            # really care; details went to standard error:
            shutil.rmtree(workdir)
            raise CvsLoadingError(cvsurl, rc)
        self.workdirs.append(workdir)

        if path == ".":
            return workdir
        elif self.isFileResource(cvsurl):
            basename = posixpath.basename(path)
            return os.path.join(workdir, basename, basename)
        else:
            basename = posixpath.basename(path)
            return os.path.join(workdir, basename)

    def runCvsExport(self, cvsroot, workdir, tag, path):
        # cvs -f -Q -z6 -d CVSROOT export -kk -d WORKDIR -r TAG PATH
        # separated out from load() to ease testing of load()
        # XXX not sure of a good way to test this method!
        wf = posixpath.basename(path)
        pwd = os.getcwd()
        os.chdir(workdir)
        try:
            rc = os.spawnlp(os.P_WAIT, "cvs",
                            "cvs", "-f", "-Q", "-z6", "-d", cvsroot,
                            "export", "-kk", "-d", wf, "-r", tag, path)
        finally:
            os.chdir(pwd)
        return rc

    # XXX CVS does some weird things with export; not sure how much
    # they mean yet.  Note that there's no way to tell if the resource
    # is a file or directory from the cvs: URL.
    #
    # - If the directory named with -d already exists, a CVS/
    #   directory is created within that and is populated, regardless
    #   of whether the requested resource is a file or directory.
    #
    # - If the directory does not already exist it is created, and no
    #   CVS/ directory is created within that.
    #
    # - If the requested resource is a file, it is created within the
    #   new directory, otherwise the directory is populated with the
    #   contents of the directory in the repository.
    #
    # "cvs rlog -R" gives a list of ,v files for the selected
    # resource.  If there's more than one, it's a directory.
    # Otherwise, it's a file if the path matches the repository root +
    # the path from the cvs: URL.

    def isFileResource(self, cvsurl):
        if isinstance(cvsurl, RepositoryUrl):
            cvsurl = cvsurl.join(self.cvsurl)
        if not cvsurl.path:
            # The whole repository is always a directory
            return False
        f = self.openCvsRLog(cvsurl.getCvsRoot(), cvsurl.path)
        line1 = f.readline().rstrip()
        line2 = f.readline()
        f.close()
        if line2:
            # more than one line; must be a directory
            return False
        module, base = posixpath.split(cvsurl.path)
        comma_v = posixpath.join(cvsurl.cvsroot, cvsurl.path) + ",v"
        comma_v_attic = posixpath.join(
            cvsurl.cvsroot, module, "Attic", base) + ",v"
        return line1 in (comma_v, comma_v_attic)

    # separate this out to ease testing

    def openCvsRLog(self, cvsroot, path):
        return os.popen(
            "cvs -f -d '%s' rlog -R -l '%s'" % (cvsroot, path), "r")


=== Added File Packages/zpkgtools/zpkgtools/dependencies.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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 handling dependency information."""

import re
import sets


_ident = "[a-zA-Z_][a-zA-Z_0-9]*"
_module_match = re.compile(r"%s(\.%s)*$" % (_ident, _ident)).match


def isModuleName(string):
    return _module_match(string) is not None


def load(f):
    deps = DependencyInfo()
    deps.load(f)
    return deps


class DependencyInfo(object):
    """Dependency information."""

    def __init__(self):
        self.modules = sets.Set()
        self.others = sets.Set()

    def load(self, f):
        while True:
            line = f.readline().strip()
            if not line:
                return
            if line[0] == "#":
                continue
            if isModuleName(line):
                self.modules.add(line)
            else:
                self.others.add(line)


=== Added File Packages/zpkgtools/zpkgtools/include.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Processor for inclusions when building a release."""

import glob
import os
import posixpath
import shutil
import urllib
import urllib2

from zpkgtools import cvsloader


# Names that are exluded from globbing results:
EXCLUDE_NAMES = ["CVS", ".cvsignore", "RCS", "SCCS", ".svn"]


class InclusionError(Exception):
    pass


class InclusionSpecificationError(ValueError, InclusionError):
    def __init__(self, message, filename, lineno):
        self.filename = filename
        self.lineno = lineno
        ValueError.__init__(self, message)


class InclusionProcessor:
    """Handler for processing inclusion specifications.

    Methods are provided for both reading specifications and creating
    the output tree.

    The following attributes are filled in by loadSpecification().
    These are exposed for use from the unit tests.

    excludes
      Iterable containing the absolute path names of the files in the
      source tree that should not be part of the destination.

    includes
      Mapping from relative path (relative to the destination) to
      either an absolute path in the source directory or a URL.

    """
    def __init__(self, source, destination, specfile=None):
        if not os.path.exists(source):
            raise InclusionError("source directory does not exist: %r"
                                 % source)
        self.source = os.path.abspath(source)
        self.destination = os.path.abspath(destination)
        prefix = os.path.commonprefix([self.source, self.destination])
        if prefix == self.source:
            raise InclusionError("destination directory may not be"
                                 " contained in the source directory")
        elif prefix == self.destination:
            raise InclusionError("source directory may not be"
                                 " contained in the destination directory")
        self.excludes = {}
        self.includes = {}
        f = None
        if specfile is None:
            # Read soruce/INCLUDES.txt, if it exists.
            specfile = os.path.join(source, "INCLUDES.txt")
            if os.path.exists(specfile):
                f = open(specfile, "rU")
            contextdir = self.source
        else:
            # Read the specified file, without testing for existance.
            f = open(specfile, "rU")
            contextdir = os.path.dirname(os.path.abspath(specfile))
        if f is not None:
            try:
                self.loadSpecification(f, specfile)
            finally:
                f.close()
        if os.path.isdir(os.path.join(contextdir, "CVS")):
            self.cvsurl = cvsloader.fromPath(contextdir)
        else:
            self.cvsurl = None
        self.cvs_loader = None

    def loadSpecification(self, f, filename):
        lineno = 0
        for line in f:
            lineno += 1
            line = line.strip()
            if line[:1] in ("", "#"):
                continue
            parts = line.split(None, 1)
            if len(parts) != 2:
                raise InclusionSpecificationError(
                    "inclusion specifications require"
                    " both target and source parts",
                    filename, lineno)
            dest, src = parts
            dest = self.normalizePath(dest, "destination", filename, lineno)
            src = self.normalizePathOrURL(src, "source", filename, lineno)
            if src == "-":
                path = os.path.join(self.source, dest)
                expansions = self.glob(path)
                if not expansions:
                    raise InclusionSpecificationError(
                        "exclusion %r doesn't match any files" % dest,
                        filename, lineno)
                for fn in expansions:
                    self.excludes[fn] = fn
            else:
                self.includes[dest] = src

    def glob(self, pattern):
        return [n for n in glob.glob(pattern)
                if os.path.basename(n) not in EXCLUDE_NAMES]

    def filterNames(self, names):
        return [n for n in names
                if n not in EXCLUDE_NAMES]

    def normalizePath(self, path, type, filename, lineno):
        if ":" in path:
            scheme, rest = urllib.splittype(path)
            if len(scheme) == 1:
                # looks like a drive letter for Windows; scream,
                # 'cause that's not allowable:
                raise InclusionSpecificationError(
                    "drive letters are not allowed in inclusions: %r"
                    % path,
                    filename, lineno)
        np = posixpath.normpath(path)
        if posixpath.isabs(np) or np[:1] == ".":
            raise InclusionSpecificationError(
                "%s path must not be absolute or refer to a location"
                " not contained in the source directory"
                % path,
                filename, lineno)
        return np.replace("/", os.sep)

    def normalizePathOrURL(self, path, type, filename, lineno):
        if ":" in path:
            scheme, rest = urllib.splittype(path)
            if len(scheme) != 1:
                # should normalize the URL, but skip that for now
                return path
        return self.normalizePath(path, type, filename, lineno)

    def createDistributionTree(self):
        """Create the output tree according to the loaded specification.

        The destination directory will be created if it doesn't
        already exist.
        """
        self.copyTree(self.source, self.destination)
        self.addIncludes()

    def copyTree(self, source, destination):
        """Populate the destination tree from the source tree.

        Files and directories will be created with the same permission
        bits and stat info as the source tree.

        Entries identified as exclusions will not be copied at all.
        """
        if not os.path.exists(destination):
            os.mkdir(destination)
        prefix = os.path.join(source, "")
        for dirname, dirs, files in os.walk(source):
            dirs[:] = self.filterNames(dirs)
            files = self.filterNames(files)

            # remove excluded directories:
            for dir in dirs[:]:
                fullpath = os.path.join(dirname, dir)
                if fullpath in self.excludes:
                    dirs.remove(dir)

            # reldir is the name of the directory to write to,
            # relative to destination.  It will be '' at the top
            # level.
            reldir = dirname[len(prefix):]
            if reldir:
                destdir = os.path.join(destination, reldir)
            else:
                destdir = destination
            for file in files:
                srcname = os.path.join(dirname, file)
                if srcname in self.excludes:
                    continue
                destname = os.path.join(destdir, file)
                # Copy file data, permission bits, and stat info;
                # owner/group are not copied.
                shutil.copy2(srcname, destname)

            for dir in dirs:
                srcname = os.path.join(dirname, dir)
                destname = os.path.join(destdir, dir)
                # Create the directory, copying over the permission
                # bits and stat info.
                os.mkdir(destname)
                shutil.copymode(srcname, destname)
                shutil.copystat(srcname, destname)

    def addIncludes(self):
        """Add files and directories based on the specification."""
        for relpath, source in self.includes.iteritems():
            self.addSingleInclude(relpath, source)

    def addSingleInclude(self, relpath, source):
        dirname, basename = os.path.split(relpath)
        if dirname:
            destdir = os.path.join(self.destination, dirname)
            if not os.path.exists(destdir):
                os.makedirs(destdir)
        else:
            # Known to exist, so no need to create it.
            destdir = self.destination

        # This is what we want to create:
        destination = os.path.join(destdir, basename)

        try:
            cvsurl = cvsloader.parse(source)
        except ValueError:
            # not a cvs: or repository: URL
            type, rest = urllib.splittype(source)
            if type:
                # some sort of URL
                self.includeFromUrl(source, destination)
            else:
                # local path
                self.includeFromLocalTree(os.path.join(self.source, source),
                                          destination)
        else:
            self.includeFromCvs(cvsurl, destination)

    def includeFromLocalTree(self, source, destination):
        # Check for file-ness here since copyTree() doesn't handle
        # individual files at all.
        if os.path.isfile(source):
            shutil.copy2(source, destination)
        else:
            self.copyTree(source, destination)

    def includeFromUrl(self, source, destination):
        # XXX treat FTP URLs specially to get permission bits and directories?
        inf = urllib2.urlopen(source)
        try:
            outf = open(destination, "w")
            try:
                shutil.copyfileobj(inf, outf)
            finally:
                outf.close()
        finally:
            inf.close()

    def includeFromCvs(self, cvsurl, destination):
        loader = self.getLoader(cvsurl)
        source = loader.load(cvsurl)
        if os.path.isfile(source):
            shutil.copy2(source, destination)
        else:
            self.copyTree(source, destination)

    def getLoader(self, cvsurl):
        if self.cvs_loader is None and self.cvsurl is not None:
            self.cvs_loader = cvsloader.CvsLoader(self.cvsurl)
        if self.cvs_loader is None:
            # We can create a temporary loader from a cvs: URL if we need to:
            if isinstance(cvsurl, cvsloader.CvsUrl):
                loader = cvsloader.CvsLoader(cvsurl)
            else:
                # We don't have a cvs: URL, and repository: URLs are
                # always relative to a cvs: URL, so we can't proceed:
                raise InclusionError(
                    "cannot load URL %s without base repository information"
                    % cvsurl.getUrl())
        else:
            loader = self.cvs_loader
        return loader


=== Added File Packages/zpkgtools/zpkgtools/pep262.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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 writing PEP 262 package metadata files."""

import sets

from distutils.dist import DistributionMetadata

from zpkgtools import dependencies
from zpkgtools import publication


class PackageData(dependencies.DependencyInfo, DistributionMetadata):
    def __init__(self):
        super(PackageData, self).__init__()
        self.files = {}
        self.provides = sets.Set()

    def load(self, f):
        partsline = f.readline()
        parts = partsline.split()
        for part in parts:
            if part == "PKG-INFO":
                publication.load(f, metadata=self, versioninfo=True)
            elif part == "FILES":
                self.loadFiles(f)
            elif part == "REQUIRES":
                super(PackageData, self).load(f)
            elif part == "PROVIDES":
                self.loadProvides(f)
            else:
                # unsupported section; skip to next blank line
                for line in f:
                    if not line.strip():
                        break

    def loadFiles(self, f):
        while True:
            line = f.readline().strip()
            if not line:
                return
            parts = line.split("\t")
            while len(parts) < 6:
                parts.append(None)
            path, size, perms, owner, group, digest = parts[:6]
            try:
                size = int(size)
            except (TypeError, ValueError):
                # Ugh!  but we don't want to lose the info, so just keep it.
                pass
            if perms == "unknown":
                perms = None
            self.files[path] = FileEntry(path, size, perms,
                                         owner, group, digest)

    def loadProvides(self, f):
        while True:
            line = f.readline().strip()
            if not line:
                return
            self.provides.add(line)


class FileEntry(object):
    __slots__ = "path", "size", "permissions", "owner", "group", "digest"

    def __init__(self, path, size, permissions, owner, group, digest):
        self.path = path
        self.size = size
        self.permissions = permissions
        self.owner = owner
        self.group = group
        self.digest = digest


=== Added File Packages/zpkgtools/zpkgtools/publication.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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 PKG-INFO files.

$Id: publication.py,v 1.1 2004/03/05 21:39:19 fdrake Exp $
"""
from distutils.dist import DistributionMetadata
from distutils.util import rfc822_escape
from email.Parser import Parser
from StringIO import StringIO


# 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."""
    print >>f, "Metadata-Version: 1.0"
    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."""
    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.

    Unsupported metadata formats cause a ValueError to be raised.
    """
    parser = Parser()
    msg = parser.parse(f, headersonly=True)
    return _loadmsg(msg, versioninfo, metadata)


def loads(text, versioninfo=False, metadata=None):
    """Parse PKG-INFO source text and return a DistributionMetadata instance.

    Unsupported metadata formats cause a ValueError to be raised.
    """
    parser = Parser()
    msg = parser.parsestr(text, headersonly=True)
    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.

    If the header occurs more than once, ValueError is raised.
    """
    headers = msg.get_all(name)
    if headers and len(headers) > 1:
        raise ValueError("header %r can only be given once" % name)
    if headers:
        return headers[0]
    else:
        return default


=== Added File Packages/zpkgtools/zpkgtools/setup.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Generator for distutils setup.py files."""

import os.path

from StringIO import StringIO


# These are both attributes of the publication data object and keyword
# arguments to setup().

STRING_ATTRIBUTES = [
    "name",
    "version",
    "license",
    "author",
    "author_email",
    "maintainer",
    "maintainer_email",
    "description",
    "long_description",
    "download_url",
    ]

LIST_ATTRIBUTES = [
    "keywords",
    "classifiers",
    ]

def generate(directory, publication, version, packageinfo=None):
    setup_py = os.path.join(directory, "setup.py")
    f = open(setup_py, "w")
    try:
        generate_py(f, publication, version, packageinfo)
    finally:
        f.close()

    # We don't always need to generate a setup.cfg, so use a StringIO
    # as an intermediate:
    f = StringIO()
    generate_cfg(f, publication, version, packageinfo)
    text = f.getvalue()
    if text.strip():
        setup_cfg = os.path.join(directory, "setup.cfg")
        f = open(setup_cfg, "w")
        try:
            f.write(CONFIG_HEADER)
            f.write(text)
        finally:
            f.close()


def generate_py(f, publication, version, packageinfo):
    """Generate the setup.py for a release."""
    print >>f, HEADER
    print >>f, "setup(version=%r," % version
    for name in STRING_ATTRIBUTES:
        dumpString(f, publication, name)
    if publication.platforms:
        print >>f, "      platforms=%r," % ", ".join(publication.platforms)
    for name in LIST_ATTRIBUTES:
        dumpList(f, publication, name)
    print >>f, "      )"


def generate_cfg(f, publication, version, packageinfo):
    """Generate the setup.cfg for a release."""
    # For now, do nothing.


def dumpString(f, publication, name):
    value = getattr(publication, name)
    if value is not None:
        if "\n" in value:
            # deal with multiline values
            pass
        else:
            print >>f, "      %s=%r," % (name, value)


def dumpList(f, publication, name):
    value = getattr(publication, name)
    if value:
        print >>f, "      %s=[" % name
        for v in value:
            print >>f, "          %r," % v
        print >>f, "          ],"


HEADER = """\
#! /usr/bin/env python
#
# THIS IS A GENERATED FILE.  DO NOT EDIT THIS DIRECTLY.

from distutils.core import setup
from distutils.core import Extension

"""

CONFIG_HEADER = """\
# THIS IS A GENERATED FILE.  DO NOT EDIT THIS DIRECTLY.

"""




More information about the Zope-CVS mailing list