[Zope-CVS] SVN: zf.zscp/trunk/src/zf/zscp/ Import the initial ZSCP
code.
Stephan Richter
srichter at cosmos.phy.tufts.edu
Wed Mar 1 19:35:41 EST 2006
Log message for revision 65707:
Import the initial ZSCP code.
Changed:
A zf.zscp/trunk/src/zf/zscp/
A zf.zscp/trunk/src/zf/zscp/DEPENDENCIES.cfg
A zf.zscp/trunk/src/zf/zscp/README.txt
A zf.zscp/trunk/src/zf/zscp/TASKS,txt
A zf.zscp/trunk/src/zf/zscp/__init__.py
A zf.zscp/trunk/src/zf/zscp/certification.py
A zf.zscp/trunk/src/zf/zscp/certification.txt
A zf.zscp/trunk/src/zf/zscp/certification.xmlt
A zf.zscp/trunk/src/zf/zscp/contact.py
A zf.zscp/trunk/src/zf/zscp/fields.py
A zf.zscp/trunk/src/zf/zscp/fileformat.py
A zf.zscp/trunk/src/zf/zscp/interfaces.py
A zf.zscp/trunk/src/zf/zscp/package.py
A zf.zscp/trunk/src/zf/zscp/publication.py
A zf.zscp/trunk/src/zf/zscp/publication.txt
A zf.zscp/trunk/src/zf/zscp/release.py
A zf.zscp/trunk/src/zf/zscp/release.txt
A zf.zscp/trunk/src/zf/zscp/release.xmlt
A zf.zscp/trunk/src/zf/zscp/tests.py
A zf.zscp/trunk/src/zf/zscp/website/
A zf.zscp/trunk/src/zf/zscp/website/__init__.py
A zf.zscp/trunk/src/zf/zscp/website/interfaces.py
A zf.zscp/trunk/src/zf/zscp/website/zscp.py
A zf.zscp/trunk/src/zf/zscp/zscp.py
A zf.zscp/trunk/src/zf/zscp/zscp.txt
-=-
Added: zf.zscp/trunk/src/zf/zscp/DEPENDENCIES.cfg
===================================================================
--- zf.zscp/trunk/src/zf/zscp/DEPENDENCIES.cfg 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/DEPENDENCIES.cfg 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,3 @@
+pysvn
+zope.interface
+zope.schema
Added: zf.zscp/trunk/src/zf/zscp/README.txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/README.txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/README.txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,21 @@
+===================
+ZSCP Implementation
+===================
+
+This package implements the process and Web site of the ZSCP as described in
+``ProcessAndRepository.txt``.
+
+Contents
+--------
+
+``release.txt``
+ Describes the parsing and writing of release data files.
+
+``certification.txt``
+ Describes the parsing and writing of certification data files.
+
+``publication.txt``
+ Describes the parsing and writing of publication data files.
+
+``zscp.txt``
+ The actual implementation of the ZSCP for an SVN repository.
Property changes on: zf.zscp/trunk/src/zf/zscp/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zf.zscp/trunk/src/zf/zscp/TASKS,txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/TASKS,txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/TASKS,txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,16 @@
+================
+Tasks to be done
+================
+
+
+
+Automation Tools
+----------------
+
+- test coverage
+- package meta-information verification (provide hook in parser)
+- Zope coding guidline verifier
+ * check package structure
+ * check class names
+ * check metod and function names
+ * check for method documentation strings
Added: zf.zscp/trunk/src/zf/zscp/__init__.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/__init__.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/__init__.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1 @@
+# Make a pacakge.
Property changes on: zf.zscp/trunk/src/zf/zscp/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/certification.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/certification.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/certification.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Certification
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
+
+from zf.zscp import interfaces, fileformat, contact
+
+
+class Certification(object):
+ """Certification Implementation."""
+ zope.interface.implements(interfaces.ICertification)
+
+ action = None
+ sourceLevel = None
+ targetLevel = None
+ date = None
+ certificationManger = None
+ comments = None
+
+ def __repr__(self):
+ return '<%s action=%r, source=%r, target=%r>' % (
+ self.__class__.__name__, self.action,
+ self.sourceLevel, self.targetLevel)
+
+
+_rootField = zope.schema.List(
+ value_type=zope.schema.Object(schema=interfaces.ICertification))
+
+def processXML(xml):
+ """Process the XML to create a list of certifications."""
+ handler = fileformat.XMLHandler(
+ 'certifications', _rootField,
+ {interfaces.IContact: contact.Contact,
+ interfaces.ICertification: Certification})
+ return fileformat.processXML(xml, handler)
+
+def produceXML(certifications):
+ """Convert the list of certifications to XML."""
+ producer = fileformat.InfoProducer(certifications, _rootField)
+ return fileformat.produceXML(producer, 'certification.xmlt')
Property changes on: zf.zscp/trunk/src/zf/zscp/certification.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/certification.txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/certification.txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/certification.txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,217 @@
+===================================
+Handling Package Certification Data
+===================================
+
+The package certification data is stored in an XML file. This document
+describes how to parse and generate this file.
+
+Parsing Package Certification Data
+----------------------------------
+
+The certification file contains several certifications. The simplest case is
+that there are no certifications.
+
+ >>> import StringIO
+ >>> xml = StringIO.StringIO(u'<certifications />')
+
+ >>> from zf.zscp import certification
+ >>> certifications = certification.processXML(xml)
+ >>> certifications
+ []
+
+Note that you cannot just have any root element:
+
+ >>> xml = StringIO.StringIO(u'<faux-certifications />')
+
+ >>> from zf.zscp import certification
+ >>> certification.processXML(xml)
+ Traceback (most recent call last):
+ ...
+ ValueError: The root element must be named `certifications`
+
+When a certification is added, all required fields must be specified:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <certifications>
+ ... <certification>
+ ... <action>grant</action>
+ ... <source-level>none</source-level>
+ ... <target-level>listed</target-level>
+ ... <date>2006-01-01</date>
+ ... <certification-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </certification-manager>
+ ... </certification>
+ ... </certifications>
+ ... ''')
+
+ >>> certifications = certification.processXML(xml)
+ >>> certifications
+ [<Certification action=u'grant', source=u'none', target=u'listed'>]
+
+All data should be available via the attributes:
+
+ >>> grant = certifications[0]
+ >>> grant.action
+ u'grant'
+ >>> grant.sourceLevel
+ u'none'
+ >>> grant.targetLevel
+ u'listed'
+ >>> grant.date
+ datetime.date(2006, 1, 1)
+ >>> grant.certificationManager
+ <Contact 'John Doe <john at doe.com>'>
+
+If a required field is not specified, then an error is raised. In the example
+below, two required fields are missing:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <certifications>
+ ... <certification>
+ ... <source-level>none</source-level>
+ ... <target-level>listed</target-level>
+ ... <certification-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </certification-manager>
+ ... </certification>
+ ... </certifications>
+ ... ''')
+
+ >>> certification.processXML(xml)
+ Traceback (most recent call last):
+ ...
+ RequiredElementsMissing: Required field(s) 'action', 'date' missing in
+ `certification` (file "<string>", line 10, column 17)
+
+And finally a full file of content:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <certifications>
+ ... <certification>
+ ... <action>warn</action>
+ ... <source-level>level1</source-level>
+ ... <target-level>level1</target-level>
+ ... <date>2006-03-15</date>
+ ... <certification-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </certification-manager>
+ ... <comments>
+ ... The test coverage has been around 50% for over two months.
+ ... </comments>
+ ... </certification>
+ ... <certification>
+ ... <action>grant</action>
+ ... <source-level>none</source-level>
+ ... <target-level>level1</target-level>
+ ... <date>2006-01-01</date>
+ ... <certification-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </certification-manager>
+ ... </certification>
+ ... </certifications>
+ ... ''')
+
+ >>> certifications = certification.processXML(xml)
+ >>> warn = certifications[0]
+ >>> warn.action
+ u'warn'
+ >>> warn.sourceLevel
+ u'level1'
+ >>> warn.targetLevel
+ u'level1'
+ >>> warn.date
+ datetime.date(2006, 3, 15)
+ >>> warn.certificationManager
+ <Contact 'John Doe <john at doe.com>'>
+ >>> warn.comments
+ u'The test coverage has been around 50% for over two months.'
+
+ >>> grant = certifications[1]
+ >>> grant.action
+ u'grant'
+ >>> grant.sourceLevel
+ u'none'
+ >>> grant.targetLevel
+ u'level1'
+ >>> grant.date
+ datetime.date(2006, 1, 1)
+ >>> grant.certificationManager
+ <Contact 'John Doe <john at doe.com>'>
+
+
+Writing Package Certification Data
+----------------------------------
+
+The simplest step is to create a totally empty file.
+
+ >>> certifications = []
+ >>> print certification.produceXML(certifications)
+ <certifications>
+ </certifications>
+
+Now let's add a certification to the certifications having the minimum data ...
+
+ >>> import datetime
+ >>> from zf.zscp import contact
+ >>> grant = certification.Certification()
+ >>> grant.action = u'grant'
+ >>> grant.sourceLevel = u'none'
+ >>> grant.targetLevel = u'level1'
+ >>> grant.date = datetime.date(2006, 1, 1)
+ >>> grant.certificationManager = contact.Contact()
+ >>> grant.certificationManager.name = u'John Doe'
+ >>> grant.certificationManager.email = u'john at doe.com'
+
+and add it to the certifications:
+
+ >>> certifications.append(grant)
+
+We can now render the structure:
+
+ >>> print certification.produceXML(certifications)
+ <certifications>
+ <certification>
+ <action>grant</action>
+ <source-level>none</source-level>
+ <target-level>level1</target-level>
+ <date>2006-01-01</date>
+ <certification-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </certification-manager>
+ </certification>
+ </certifications>
+
+Let's now also add the complete warning (``warn``) certification action in the
+first position to ensure correct output.
+
+ >>> certifications.insert(0, warn)
+ >>> print certification.produceXML(certifications)
+ <certifications>
+ <certification>
+ <action>warn</action>
+ <source-level>level1</source-level>
+ <target-level>level1</target-level>
+ <date>2006-03-15</date>
+ <certification-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </certification-manager>
+ <comments>The test coverage has been around 50% for over two months.</comments>
+ </certification>
+ <certification>
+ <action>grant</action>
+ <source-level>none</source-level>
+ <target-level>level1</target-level>
+ <date>2006-01-01</date>
+ <certification-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </certification-manager>
+ </certification>
+ </certifications>
Property changes on: zf.zscp/trunk/src/zf/zscp/certification.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zf.zscp/trunk/src/zf/zscp/certification.xmlt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/certification.xmlt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/certification.xmlt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,15 @@
+<certifications>
+ <certification tal:repeat="certification options/root">
+ <action tal:content="certification/action" />
+ <source-level tal:content="certification/sourceLevel" />
+ <target-level tal:content="certification/targetLevel" />
+ <date tal:content="certification/date" />
+ <certification-manager>
+ <name tal:content="certification/certificationManager/name" />
+ <email tal:content="certification/certificationManager/email" />
+ </certification-manager>
+ <comments
+ tal:condition="certification/comments"
+ tal:content="certification/comments" />
+ </certification>
+</certifications>
Added: zf.zscp/trunk/src/zf/zscp/contact.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/contact.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/contact.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Contact
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+
+from zf.zscp import interfaces
+
+class Contact(object):
+ """Contact Implementation"""
+ zope.interface.implements(interfaces.IContact)
+
+ name = None
+ email = None
+
+ def __repr__(self):
+ return "<%s '%s <%s>'>" % (
+ self.__class__.__name__, self.name, self.email)
Property changes on: zf.zscp/trunk/src/zf/zscp/contact.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/fields.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/fields.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/fields.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""XXX short summary goes here.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import datetime
+import zope.schema
+
+class Date(zope.schema.Date):
+ """Custom date field."""
+
+ def fromUnicode(self, str):
+ year, month, day = [int(field) for field in str.split('-')]
+ return datetime.date(year, month, day)
Property changes on: zf.zscp/trunk/src/zf/zscp/fields.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/fileformat.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/fileformat.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/fileformat.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,306 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""A collection of tools to support the ZSCP file formats.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import re
+from distutils.util import rfc822_escape
+from email.Parser import HeaderParser
+from xml.sax import make_parser
+from xml.sax.xmlreader import InputSource
+from xml.sax.handler import ContentHandler
+from cStringIO import StringIO
+
+import zope.schema
+from zope.pagetemplate import pagetemplatefile
+
+def getHeaderName(name):
+ """Convert field names to RFC 2822 header names."""
+ parts = re.findall('^[a-z]+|[A-Z][a-z0-9]+', name)
+ headerName = '-'.join(part.lower() for part in parts)
+ return headerName.capitalize()
+
+def getFieldName(name):
+ """Convert XML element names to field names."""
+ name = name.lower()
+ parts = name.split('-')
+ name = ''.join([part.capitalize() for part in parts])
+ return name[0].lower() + name[1:]
+
+def rfc822_unescape(value):
+ return value.replace('\n ', '\n')
+
+
+class XMLStructuralError(Exception):
+ """An exception that represents an error in the XML structure."""
+
+ def __init__(self, locator):
+ self.locator = locator
+
+ def __str__(self):
+ return '(file "%s", line %s, column %s)'% (
+ self.locator.getSystemId(),
+ self.locator.getLineNumber(),
+ self.locator.getColumnNumber())
+
+
+class RequiredElementsMissing(XMLStructuralError):
+ """A required field is missing."""
+
+ def __init__(self, elementName, names, locator):
+ XMLStructuralError.__init__(self, locator)
+ self.elementName = elementName
+ self.names = names
+
+ def __str__(self):
+ info = XMLStructuralError.__str__(self)
+ return 'Required field(s) %s missing in `%s` %s' % (
+ ', '.join([name.__repr__() for name in self.names]),
+ self.elementName,
+ info)
+
+
+class InvalidSubElement(XMLStructuralError):
+ """An unsuspected sub-element was found."""
+
+ def __init__(self, fieldName, locator):
+ XMLStructuralError.__init__(self, locator)
+ self.fieldName = fieldName
+
+ def __str__(self):
+ info = XMLStructuralError.__str__(self)
+ return '`%s` cannot have sub-elements %s' %(self.fieldName, info)
+
+
+class XMLHandler(ContentHandler):
+ """A SAX event handler that creates a Python object from the XML events
+ using a schema.
+ """
+
+ def __init__(self, rootElementName, rootElementField, factories):
+ self.stack = []
+ self.root = None
+ self.rootElementName = rootElementName
+ self.rootElementField = rootElementField
+ self.factories = factories
+
+ @property
+ def current(self):
+ return self.stack[-1]
+
+ def _verifyComplexElement(self, obj, iface, elemName):
+ missingFields = []
+ for name, field in zope.schema.getFields(iface).items():
+ if field.required and getattr(obj, name) == field.missing_value:
+ if field.default is not field.missing_value:
+ setattr(obj, name, field.default)
+ else:
+ missingFields.append(name)
+ if missingFields:
+ raise RequiredElementsMissing(
+ elemName, sorted(missingFields), self.locator)
+
+ def setDocumentLocator(self, locator):
+ self.locator = locator
+
+ def characters(self, text):
+ if isinstance(self.current[0], unicode):
+ self.current[0] += text
+
+ def startElement(self, name, attrs):
+ attrName = getFieldName(name)
+
+ if not self.stack:
+ if name != self.rootElementName:
+ raise ValueError(
+ 'The root element must be named `%s`' %self.rootElementName)
+ field = self.rootElementField
+ else:
+ currentField = self.current[1]
+ if zope.schema.interfaces.IObject.providedBy(currentField):
+ field = currentField.schema.getDescriptionFor(attrName)
+ elif zope.schema.interfaces.IList.providedBy(currentField):
+ field = currentField.value_type
+ else:
+ raise InvalidSubElement(currentField.__name__, self.locator)
+
+ if zope.schema.interfaces.IObject.providedBy(field):
+ obj = self.factories[field.schema](**dict(attrs))
+ elif zope.schema.interfaces.IList.providedBy(field):
+ obj = []
+ else:
+ obj = u''
+
+ self.stack.append([obj, field])
+
+
+ def endElement(self, name):
+ attrName = getFieldName(name)
+
+ obj, field = self.stack.pop()
+ if zope.schema.interfaces.IObject.providedBy(field):
+ self._verifyComplexElement(obj, field.schema, name)
+ elif zope.schema.interfaces.IList.providedBy(field):
+ pass
+ else:
+ obj = field.fromUnicode(obj.strip())
+
+ if not self.stack:
+ self.root = obj
+ elif zope.schema.interfaces.IList.providedBy(self.current[1]):
+ self.current[0].append(obj)
+ else:
+ setattr(self.current[0], attrName, obj)
+
+
+def processXML(file, handler):
+ """Read the releases XML string."""
+ src = InputSource(getattr(file, 'name', '<string>'))
+ src.setByteStream(file)
+ parser = make_parser()
+ parser.setContentHandler(handler)
+ parser.parse(src)
+ return handler.root
+
+
+def _listHandler(producer, obj, field):
+ return [producer.produce(entry, field.value_type)
+ for entry in obj]
+
+def _objectHandler(producer, obj, field):
+ info = {}
+ for name, field in zope.schema.getFieldsInOrder(field.schema):
+ info[name] = producer.produce(getattr(obj, name), field)
+ return info
+
+
+class InfoProducer(object):
+ """An object that produces a TAL-friendly info object from an obejct using
+ a schema.
+ """
+
+ handlers = {zope.schema.interfaces.IList: _listHandler,
+ zope.schema.interfaces.IObject: _objectHandler}
+
+ def __init__(self, rootObject, rootField):
+ self.rootObject = rootObject
+ self.rootField = rootField
+
+ def produce(self, obj, field):
+ if obj is None:
+ return None
+
+ for fieldIface, handler in self.handlers.items():
+ if fieldIface.providedBy(field):
+ return handler(self, obj, field)
+
+ return str(obj)
+
+ def __call__(self):
+ return self.produce(self.rootObject, self.rootField)
+
+
+def produceXML(producer, template):
+ return pagetemplatefile.PageTemplateFile(template)(root=producer())
+
+
+class HeaderProcessor(object):
+ """Parses RFC 2822 style headers into a Python object using a schema."""
+
+ def __init__(self, root, schema):
+ self.root = root
+ self.schema = schema
+ self.missingRequired = []
+
+ def _getHeaderName(self, name):
+ parts = re.findall('^[a-z]+|[A-Z][a-z0-9]+', name)
+ headerName = '-'.join(part.lower() for part in parts)
+ return headerName.capitalize()
+
+ def _processSingleHeader(self, name, field):
+ headerName = getHeaderName(name)
+ headers = self.msg.get_all(headerName)
+ if headers and len(headers) > 1:
+ raise ValueError("header %r can only be given once" % headerName)
+ if headers:
+ value = unicode(headers[0])
+ setattr(self.root, name,
+ field.fromUnicode(rfc822_unescape(value)))
+ else:
+ if field.missing_value != field.default:
+ setattr(self.root, name, field.default)
+ elif field.required:
+ self.missingRequired.append(headerName)
+
+ def _processMultiHeader(self, name, field):
+ headerName = getHeaderName(name)
+ headers = self.msg.get_all(headerName)
+ if headers:
+ values = [
+ field.value_type.fromUnicode(rfc822_unescape(unicode(value)))
+ for value in headers]
+ setattr(self.root, name, values)
+ else:
+ if field.missing_value != field.default:
+ setattr(self.root, name, field.default)
+ elif field.required:
+ self.missingRequired.append(headerName)
+
+ def __call__(self, file):
+ parser = HeaderParser()
+ self.msg = parser.parse(file)
+ for name, field in zope.schema.getFieldsInOrder(self.schema):
+ if zope.schema.interfaces.IList.providedBy(field):
+ self._processMultiHeader(name, field)
+ else:
+ self._processSingleHeader(name, field)
+
+ if self.missingRequired:
+ raise ValueError('Required headers missing: %s' %
+ ', '.join(self.missingRequired))
+
+
+class HeaderProducer(object):
+ """Produces a RFC 2822 style header string from a Python object using a
+ schema.
+ """
+
+ def __init__(self, root, schema):
+ self.root = root
+ self.schema = schema
+
+ def _produceMultiHeader(self, name, value, field):
+ for subvalue in value:
+ self._produceSingleHeader(name, subvalue, field.value_type)
+
+ def _produceSingleHeader(self, name, value, field):
+ headerName = getHeaderName(name)
+ if isinstance(value, unicode):
+ value = value.encode('utf8')
+ value = rfc822_escape(str(value))
+ self.output.write('%s: %s\n' %(headerName, value))
+
+ def __call__(self):
+ self.output = StringIO()
+ for name, field in zope.schema.getFieldsInOrder(self.schema):
+ value = getattr(self.root, name)
+ if value == field.missing_value:
+ continue
+ if zope.schema.interfaces.IList.providedBy(field):
+ self._produceMultiHeader(name, value, field)
+ else:
+ self._produceSingleHeader(name, value, field)
+ return self.output.getvalue()
Property changes on: zf.zscp/trunk/src/zf/zscp/fileformat.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/interfaces.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/interfaces.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/interfaces.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,382 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""ZSCP Interfaces
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.interface.common.mapping
+import zope.schema
+import zope.schema.vocabulary
+
+import zf.zscp.fields
+
+CERTIFICATION_LEVELS = zope.schema.vocabulary.SimpleVocabulary([
+ zope.schema.vocabulary.SimpleTerm(u'none', title=u'None'),
+ zope.schema.vocabulary.SimpleTerm(u'listed', title=u'Listed'),
+ zope.schema.vocabulary.SimpleTerm(u'level1', title=u'Level 1 Certified'),
+ zope.schema.vocabulary.SimpleTerm(u'level2', title=u'Level 2 Certified'),
+ zope.schema.vocabulary.SimpleTerm(u'level3', title=u'Level 3 Certified')
+ ])
+
+CERTIFICATION_ACTIONS = zope.schema.vocabulary.SimpleVocabulary([
+ zope.schema.vocabulary.SimpleTerm(u'grant', title=u'Grant'),
+ zope.schema.vocabulary.SimpleTerm(u'revoke', title=u'Revoke'),
+ zope.schema.vocabulary.SimpleTerm(u'warn', title=u'Warn'),
+ ])
+
+class IRepositoryInitializedEvent(zope.interface.Interface):
+ """Event fired after a repository was initialized."""
+ repository = Attribute('The initialized repository')
+
+class RepositoryInitializedEvent(object):
+ zope.interface.implements(IRepositoryInitializedEvent)
+
+ def __init__(self, repository):
+ self.repository = repository
+
+
+class IPackageEvent(zope.interface.Interface):
+ """An event that involves a package."""
+ package = Attribute('The package that was acted upon')
+
+class PackageEvent(object):
+ zope.interface.implements(IPackageEvent)
+ def __init__(self, pkg):
+ self.package = pkg
+
+
+class IPackageRegisteredEvent(IPackageEvent):
+ """An event fired after the package has been registered with the ZSCP."""
+
+class PackageRegisteredEvent(PackageEvent):
+ pass
+
+
+class IPackageUnregisteredEvent(IPackageEvent):
+ """An event fired after the package has been unregistered with the ZSCP."""
+
+class PackageUnregisteredEvent(PackageEvent):
+ pass
+
+
+class IPackageUpdateddEvent(IPackageEvent):
+ """An event fired after the package has been updated."""
+
+class PackageUpdatedEvent(PackageEvent):
+ pass
+
+
+class IContact(zope.interface.Interface):
+ """A contact"""
+
+ name = zope.schema.TextLine(
+ title=u"Contact Name",
+ description=u"The full name of the contact person.",
+ required=True)
+
+ email = zope.schema.TextLine(
+ title=u"Contact E-mail",
+ description=u"The E-mail address of the contact person.",
+ required=False)
+
+
+class IRelease(zope.interface.Interface):
+ """A release of a package."""
+
+ name = zope.schema.TextLine(
+ title=u"Name",
+ description=u"The name under which the package will be known for this "
+ u"release.",
+ required=True)
+
+ version = zope.schema.TextLine(
+ title=u"Version",
+ description=u"This field describes the version number of the release.",
+ required=True)
+
+ codename = zope.schema.TextLine(
+ title=u"Codename",
+ description=u"The code name of the release.",
+ required=False)
+
+ date = zf.zscp.fields.Date(
+ title=u"Date",
+ description=u"The date on which the release was made.",
+ required=True)
+
+ certification = zope.schema.Choice(
+ title=u"Certification",
+ description=u"The certification level of the package at the date "
+ u"of the release.",
+ vocabulary=CERTIFICATION_LEVELS,
+ required=True,
+ default=u'none')
+
+ package = zope.schema.URI(
+ title=u"Package",
+ description=u"The URL to the installation package file.",
+ required=True)
+
+ source = zope.schema.URI(
+ title=u"Source",
+ description=u"The URL to the repository location. It should be "
+ u"possible to use this URL to make a checkout.",
+ required=False)
+
+ dependencies = zope.schema.List(
+ title=u"Dependencies",
+ description=u"A set of dependencies to other packages. Each "
+ u"dependency must contain the full name of the package "
+ u"and the version number.",
+ value_type=zope.schema.TextLine(title=u'Dependency'),
+ required=False)
+
+ announcement = zope.schema.URI(
+ title=u"Announcement",
+ description=u"A link to the announcement of the release.",
+ required=False)
+
+ releaseManager = zope.schema.Object(
+ title=u"Release Manager",
+ description=u"The release manager of the release.",
+ schema=IContact,
+ required=False)
+
+ pressContact = zope.schema.Object(
+ title=u"Press Contact",
+ description=u"The press contact of the release.",
+ schema=IContact,
+ required=False)
+
+
+class ICertification(zope.interface.Interface):
+ """A certification."""
+
+ action = zope.schema.Choice(
+ title=u"Action",
+ description=u"The action describes whether a certification was "
+ u"granted or revoked. Upon violations (as defined "
+ u"in section 2.8 of the ZSCP proposal), a certification "
+ u"manager can also issue a warning.",
+ vocabulary=CERTIFICATION_ACTIONS,
+ required=True)
+
+ sourceLevel = zope.schema.Choice(
+ title=u"Source Level",
+ description=u"The original certification level before this "
+ u"certification action was executed.",
+ vocabulary=CERTIFICATION_LEVELS,
+ required=True)
+
+ targetLevel = zope.schema.Choice(
+ title=u"Target Level",
+ description=u"The final certification level after this "
+ u"certification action was executed.",
+ vocabulary=CERTIFICATION_LEVELS,
+ required=True)
+
+ date = zf.zscp.fields.Date(
+ title=u"Date",
+ description=u"The date on which the certification action was executed.",
+ required=True)
+
+ certificationManager = zope.schema.Object(
+ title=u"Certification Manager",
+ description=u"The certification manager that executed the "
+ u"certification action.",
+ schema=IContact,
+ required=True)
+
+ comments = zope.schema.Text(
+ title=u"Codename",
+ description=u"This field can contain arbitrary comments about the "
+ u"certification action.",
+ required=False)
+
+
+class IPublication(zope.interface.Interface):
+ """Publication data."""
+
+ packageName = zope.schema.Id(
+ title=u"Package Name",
+ description=u"The dotted Python path of the package.",
+ required=True)
+
+ name = zope.schema.TextLine(
+ title=u"Name",
+ description=u"The commonly used name of the package.",
+ required=True)
+
+ summary = zope.schema.TextLine(
+ title=u"Summary",
+ description=u"A short description or summary of the package. It is "
+ u"also often interpreted as the title.",
+ required=True)
+
+ description = zope.schema.Text(
+ title=u"Description",
+ description=u"A detailed description of the package's functionality. "
+ u"While it should contain some detail, it should not "
+ u"duplicate the documentation of the README.txt file.",
+ required=False)
+
+ homePage = zope.schema.URI(
+ title=u"Homepage",
+ description=u"A URL to the homepage of the package.",
+ required=False)
+
+ author = zope.schema.List(
+ title=u"Author Names",
+ description=u"The names of the authors of the package.",
+ value_type=zope.schema.TextLine(title=u'Name'),
+ required=True)
+
+ authorEmail = zope.schema.List(
+ title=u"Author Emails",
+ description=u"The E-mails of the authors of the package.",
+ value_type=zope.schema.TextLine(title=u'E-mail'),
+ required=True)
+
+ license = zope.schema.List(
+ title=u"Licenses",
+ description=u"The software license(s) of the package.",
+ value_type=zope.schema.TextLine(title=u'License'),
+ required=True)
+
+ platform = zope.schema.List(
+ title=u"Supported Platforms",
+ description=u"The operating system/platform the package is known to "
+ u"run on. This field can be specified multiple times. "
+ u"``All`` may be used, if the package is available "
+ u"on all platforms Python is running on, i.e. the "
+ u"package is pure Python code.",
+ value_type=zope.schema.TextLine(title=u'Platform'),
+ required=True,
+ default=[u'All'])
+
+ classifier = zope.schema.List(
+ title=u"Classifiers",
+ description=u"A classification entry identifying the package.",
+ value_type=zope.schema.TextLine(title=u'Classifier'),
+ required=False)
+
+ developersMailinglist = zope.schema.TextLine(
+ title=u"Developers Mailinglist",
+ description=u"The E-mail address of the developers mailing list.",
+ required=False)
+
+ usersMailinglist = zope.schema.TextLine(
+ title=u"Users Mailinglist",
+ description=u"The E-mail address of the users mailing list.",
+ required=False)
+
+ issueTracker = zope.schema.URI(
+ title=u"Issue Tracker",
+ description=u"A URL to the issue tracker of the package, where new "
+ u"issues/bugs/requests can be reported.",
+ required=False)
+
+ repositoryLocation = zope.schema.URI(
+ title=u"Source Repository",
+ description=u"The URL to the repository. The URL should be usable "
+ u"to actually check out the package.",
+ required=False)
+
+ repositoryWebLocation = zope.schema.URI(
+ title=u"Repository Web Location",
+ description=u"The URL to the repository's browsable HTML UI.",
+ required=False)
+
+ certificationLevel = zope.schema.Choice(
+ title=u"Certification Level",
+ description=u"Describes the certification level of the package.",
+ vocabulary=CERTIFICATION_LEVELS,
+ required=False)
+
+ certificationDate = zf.zscp.fields.Date(
+ title=u"Certification Date",
+ description=u"The date at which the certification was received.",
+ required=False)
+
+ metadataVersion = zope.schema.TextLine(
+ title=u"Metadata Version",
+ description=u"This is the version number of this package meta-data.",
+ required=True)
+
+
+class IPackage(zope.interface.Interface):
+ """Package"""
+
+ name = zope.schema.Id(
+ title=u"Package Name",
+ description=u"The dotted Python path of the package.",
+ required=True)
+
+ publication = zope.schema.Object(
+ title=u"Publication Information",
+ schema=IPublication,
+ required=True)
+
+ releases = zope.schema.List(
+ title=u"Releases",
+ value_type=zope.schema.Object(schema=IRelease),
+ required=True)
+
+ certifications = zope.schema.List(
+ title=u"Certifications",
+ value_type=zope.schema.Object(schema=ICertification),
+ required=True)
+
+
+class IZSCPRepository(zope.interface.common.mapping.IEnumerableMapping):
+ """ZSCP Repository."""
+
+ svnRoot = zope.schema.URI(
+ title=u"SVN Repository Root",
+ description=u"A SVN URI that can be used to checkout the package data.",
+ required=True)
+
+ localRoot = zope.schema.BytesLine(
+ title=u"Local Repository Root",
+ description=u"The directory on the server in to which the packages "
+ u"are checked out.",
+ required=True)
+
+ password = zope.schema.TextLine(
+ title=u"SSH Password",
+ description=u"The directory on the server in to which the packages "
+ u"are checked out.",
+ required=False)
+
+ def initialize():
+ """Initialize the ZSCP repository."""
+
+ def register(pkg):
+ """Register a package with ZSCP."""
+
+ def unregister(pkg):
+ """Unregister a package from ZSCP"""
+
+ def update(pkg):
+ """Update the local copy of the ZSCP data."""
+
+ def fetch(all=False):
+ """Fetch all package names from the SVN repository.
+
+ If ``all`` is true, then return all packages of the repository,
+ regardless of their ZSCP status.
+ """
Property changes on: zf.zscp/trunk/src/zf/zscp/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/package.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/package.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/package.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,36 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Package Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+
+from zf.zscp import interfaces
+
+class Package(object):
+ """Package Implementation."""
+ zope.interface.implements(interfaces.IPackage)
+
+ name = None
+ publication = None
+ releases = None
+ certifications = None
+
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return '<%s %r>' %(self.__class__.__name__, self.name)
Property changes on: zf.zscp/trunk/src/zf/zscp/package.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/publication.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/publication.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/publication.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Publication
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
+
+from zf.zscp import interfaces, fileformat
+
+
+class Publication(object):
+ """Publication"""
+ zope.interface.implements(interfaces.IPublication)
+
+ packageName = None
+ name = None
+ summary = None
+ description = None
+ homePage = None
+ author = None
+ authorEmail = None
+ license = None
+ platform = None
+ classifier = None
+ developersMailinglist = None
+ usersMailinglist = None
+ issueTracker = None
+ repositoryLocation = None
+ repositoryWebLocation = None
+ certificationLevel = None
+ certificationDate = None
+ metadataVersion = None
+
+ def __repr__(self):
+ return "<%s for '%s' (meta-data version %s)>" % (
+ self.__class__.__name__, self.packageName, self.metadataVersion)
+
+def process(file):
+ publication = Publication()
+ processor = fileformat.HeaderProcessor(publication, interfaces.IPublication)
+ processor(file)
+ return publication
+
+def produce(publication):
+ producer = fileformat.HeaderProducer(publication, interfaces.IPublication)
+ return producer()
Property changes on: zf.zscp/trunk/src/zf/zscp/publication.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/publication.txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/publication.txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/publication.txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,245 @@
+=================================
+Handling Package Publication Data
+=================================
+
+The package publication data is stored in an RFC 2822 compatible format. This
+document describes how to parse and generate this file.
+
+Parsing Package Publication Data
+--------------------------------
+
+The publication data file is a collection of simple meta-data entries. The
+file has required and optional fields. So when we simply want to process an
+emoty file, an exception is raised that lists all (missing) required fields:
+
+ >>> import StringIO
+ >>> file = StringIO.StringIO(u'')
+
+ >>> from zf.zscp import publication
+ >>> publication.process(file)
+ Traceback (most recent call last):
+ ...
+ ValueError: Required headers missing: Package-name, Name, Summary, Author,
+ Author-email, License, Metadata-version
+
+So let's now create a file that has at least those required headers:
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... License: ZPL 2.1
+ ... Metadata-version: 1.0
+ ... ''')
+
+The result of the processing step should be a publication object with all
+those attributes set.
+
+ >>> metadata = publication.process(file)
+ >>> metadata
+ <Publication for 'zope.sample' (meta-data version 1.0)>
+
+ >>> metadata.packageName
+ 'zope.sample'
+ >>> metadata.name
+ u'Sample Package'
+ >>> metadata.summary
+ u'This is the Sample Package.'
+ >>> metadata.author
+ [u'John Doe']
+ >>> metadata.authorEmail
+ [u'john at doe.com']
+ >>> metadata.license
+ [u'ZPL 2.1']
+ >>> metadata.metadataVersion
+ u'1.0'
+
+As you can see, some fields are lists, so multiple headers can be specified:
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... Author: Jane Doe
+ ... Author-email: jane at doe.com
+ ... License: ZPL 2.1
+ ... Metadata-version: 1.0
+ ... ''')
+
+ >>> metadata = publication.process(file)
+ >>> metadata.author
+ [u'John Doe', u'Jane Doe']
+ >>> metadata.authorEmail
+ [u'john at doe.com', u'jane at doe.com']
+
+However, for single fields, you can only provide one value:
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Package-name: zope.sample2
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... License: ZPL 2.1
+ ... Metadata-version: 1.0
+ ... ''')
+
+ >>> publication.process(file)
+ Traceback (most recent call last):
+ ...
+ ValueError: header 'Package-name' can only be given once
+
+Also, additional headers are simply ignored.
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Faux: Faux Data
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... License: ZPL 2.1
+ ... Metadata-version: 1.0
+ ... ''')
+
+ >>> metadata = publication.process(file)
+ >>> metadata.faux
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Publication' object has no attribute 'faux'
+
+We also need to ensure that the values are correctly unescaped. Multi-line
+value blocks are indented for the RFC 822 header format. The unescaping
+should remove that indentation:
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Description:
+ ... This is a really great Sample Package. It provides several
+ ... sample features and is still very sample simple.
+ ... I hope you enjoy it.
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... License: ZPL 2.1
+ ... Metadata-version: 1.0
+ ... ''')
+
+ >>> metadata = publication.process(file)
+ >>> print metadata.description
+ This is a really great Sample Package. It provides several
+ sample features and is still very sample simple.
+ I hope you enjoy it.
+
+Finally, let's have a full example:
+
+ >>> file = StringIO.StringIO(u'''\
+ ... Package-name: zope.sample
+ ... Name: Sample Package
+ ... Summary: This is the Sample Package.
+ ... Description:
+ ... This is a really great Sample Package. It provides several
+ ... sample features and is still very sample simple.
+ ... I hope you enjoy it.
+ ... Home-page: http://www.zope.org/Products/SamplePackage
+ ... Author: John Doe
+ ... Author-email: john at doe.com
+ ... License: GPL 2.0
+ ... License: ZPL 2.1
+ ... Platform: Windows
+ ... Platform: Unix
+ ... Classifier: Programming Language :: Python
+ ... Classifier: Topic :: Internet :: WWW/HTTP
+ ... Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+ ... Classifier: Topic :: Software Development :: Libraries :: Python Modules
+ ... Developers-mailinglist: sample-dev at doe.com
+ ... Users-mailinglist: sample-users at doe.com
+ ... Issue-tracker: http://www.zope.org/trackers/sample/
+ ... Repository-location: svn://svn.zope.org/repos/main/sample
+ ... Repository-web-location: http://svn.zope.org/sample
+ ... Certification-level: level1
+ ... Certification-date: 2006-02-28
+ ... Metadata-version: 1.0
+ ... ''')
+
+ >>> metadata = publication.process(file)
+ >>> metadata.packageName
+ 'zope.sample'
+ >>> metadata.name
+ u'Sample Package'
+ >>> metadata.summary
+ u'This is the Sample Package.'
+ >>> print metadata.description
+ This is a really great Sample Package. It provides several
+ sample features and is still very sample simple.
+ I hope you enjoy it.
+ >>> metadata.author
+ [u'John Doe']
+ >>> metadata.authorEmail
+ [u'john at doe.com']
+ >>> metadata.license
+ [u'GPL 2.0', u'ZPL 2.1']
+ >>> metadata.platform
+ [u'Windows', u'Unix']
+ >>> metadata.classifier
+ [u'Programming Language :: Python',
+ u'Topic :: Internet :: WWW/HTTP',
+ u'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ u'Topic :: Software Development :: Libraries :: Python Modules']
+ >>> metadata.developersMailinglist
+ u'sample-dev at doe.com'
+ >>> metadata.usersMailinglist
+ u'sample-users at doe.com'
+ >>> metadata.issueTracker
+ 'http://www.zope.org/trackers/sample/'
+ >>> metadata.repositoryLocation
+ 'svn://svn.zope.org/repos/main/sample'
+ >>> metadata.repositoryWebLocation
+ 'http://svn.zope.org/sample'
+ >>> metadata.certificationLevel
+ u'level1'
+ >>> metadata.certificationDate
+ datetime.date(2006, 2, 28)
+ >>> metadata.metadataVersion
+ u'1.0'
+
+
+Writing Package Publication Data
+--------------------------------
+
+The writing of the publication data is pretty straight forward. Thus, let's
+just write out the complete publication data above again:
+
+ >>> print publication.produce(metadata)
+ Package-name: zope.sample
+ Name: Sample Package
+ Summary: This is the Sample Package.
+ Description:
+ This is a really great Sample Package. It provides several
+ sample features and is still very sample simple.
+ I hope you enjoy it.
+ Home-page: http://www.zope.org/Products/SamplePackage
+ Author: John Doe
+ Author-email: john at doe.com
+ License: GPL 2.0
+ License: ZPL 2.1
+ Platform: Windows
+ Platform: Unix
+ Classifier: Programming Language :: Python
+ Classifier: Topic :: Internet :: WWW/HTTP
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
+ Developers-mailinglist: sample-dev at doe.com
+ Users-mailinglist: sample-users at doe.com
+ Issue-tracker: http://www.zope.org/trackers/sample/
+ Repository-location: svn://svn.zope.org/repos/main/sample
+ Repository-web-location: http://svn.zope.org/sample
+ Certification-level: level1
+ Certification-date: 2006-02-28
+ Metadata-version: 1.0
Property changes on: zf.zscp/trunk/src/zf/zscp/publication.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zf.zscp/trunk/src/zf/zscp/release.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/release.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/release.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,58 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Release
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
+
+from zf.zscp import interfaces, fileformat, contact
+
+class Release(object):
+ """Release Implementation."""
+ zope.interface.implements(interfaces.IRelease)
+
+ name = None
+ version = None
+ codename = None
+ date = None
+ certification = None
+ package = None
+ source = None
+ dependencies = None
+ announcement = None
+ releaseManager = None
+ pressContact = None
+
+ def __repr__(self):
+ return '<%s name=%r, version=%r, codename=%r>' % (
+ self.__class__.__name__, self.name, self.version, self.codename)
+
+
+_rootField = zope.schema.List(
+ value_type=zope.schema.Object(schema=interfaces.IRelease))
+
+def processXML(xml):
+ """Process the XML to create a list of releases."""
+ handler = fileformat.XMLHandler(
+ 'releases', _rootField,
+ {interfaces.IContact: contact.Contact, interfaces.IRelease: Release})
+ return fileformat.processXML(xml, handler)
+
+def produceXML(releases):
+ """Convert the list of releases to XML."""
+ producer = fileformat.InfoProducer(releases, _rootField)
+ return fileformat.produceXML(producer, 'release.xmlt')
Property changes on: zf.zscp/trunk/src/zf/zscp/release.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/release.txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/release.txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/release.txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,319 @@
+=============================
+Handling Package Release Data
+=============================
+
+The package release data is stored in an XML file. This document describes how
+to parse and generate this file.
+
+Parsing Package Release Data
+----------------------------
+
+The release file contains several releases. The simplest case is that there
+are no releases.
+
+ >>> import StringIO
+ >>> xml = StringIO.StringIO(u'<releases />')
+
+ >>> from zf.zscp import release
+ >>> releases = release.processXML(xml)
+ >>> releases
+ []
+
+Note that you cannot just have any root element:
+
+ >>> xml = StringIO.StringIO(u'<faux-releases />')
+
+ >>> from zf.zscp import release
+ >>> release.processXML(xml)
+ Traceback (most recent call last):
+ ...
+ ValueError: The root element must be named `releases`
+
+When a release is added, all required fields must be specified:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>0.9.0</version>
+ ... <date>2006-02-03</date>
+ ... <certification>level1</certification>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> releases = release.processXML(xml)
+ >>> releases
+ [<Release name=u'Sample Package', version=u'0.9.0', codename=None>]
+
+All data should be available via the attributes:
+
+ >>> pointNine = releases[0]
+ >>> pointNine.name
+ u'Sample Package'
+ >>> pointNine.version
+ u'0.9.0'
+ >>> pointNine.date
+ datetime.date(2006, 2, 3)
+ >>> pointNine.certification
+ u'level1'
+ >>> pointNine.package
+ 'http://www.zope.org/SamplePackage/Sample-0.9.0.tgz'
+
+If a required field is not specified, then an error is raised. In the example
+below, two required fields are missing:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <certification>level1</certification>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> release.processXML(xml)
+ Traceback (most recent call last):
+ ...
+ RequiredElementsMissing: Required field(s) 'date', 'version' missing in
+ `release` (file "<string>", line 7, column 11)
+
+If a required field is missing that has a default value, the default is simply
+used:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>0.9.0</version>
+ ... <date>2006-02-03</date>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> pointNine = release.processXML(xml)[0]
+ >>> pointNine.certification
+ u'none'
+
+Finally, you cannot just add sub-elements randomly:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>
+ ... Sample Package
+ ... <subname>Subname</subname>
+ ... </name>
+ ... <version>0.9.0</version>
+ ... <date>2006-02-03</date>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> release.processXML(xml)
+ Traceback (most recent call last):
+ ...
+ InvalidSubElement: `name` cannot have sub-elements
+ (file "<string>", line 6, column 14)
+
+
+Let's now have a look at a complex element within the release. The release
+manager and press contact should be converted to contact objects and then
+being added.
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>0.9.0</version>
+ ... <date>2006-02-03</date>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... <release-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </release-manager>
+ ... <press-contact>
+ ... <name>Jane Doe</name>
+ ... <email>jane at doe.com</email>
+ ... </press-contact>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> pointNine = release.processXML(xml)[0]
+ >>> pointNine.releaseManager
+ <Contact 'John Doe <john at doe.com>'>
+ >>> pointNine.pressContact
+ <Contact 'Jane Doe <jane at doe.com>'>
+
+Another complex element case are the dependencies. Dependencies are a list of
+strings, but are represented structurally in XML. Thus they need to be
+converted properly:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>0.9.0</version>
+ ... <date>2006-02-03</date>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... <dependencies>
+ ... <dependency>Zope 3.3</dependency>
+ ... <dependency>Other Software 1.1</dependency>
+ ... </dependencies>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> pointNine = release.processXML(xml)[0]
+ >>> pointNine.dependencies
+ [u'Zope 3.3', u'Other Software 1.1']
+
+And finally a full file of content:
+
+ >>> xml = StringIO.StringIO(u'''
+ ... <releases>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>1.0.0</version>
+ ... <codename>CoolName</codename>
+ ... <date>2006-02-03</date>
+ ... <certification>listed</certification>
+ ... <package>http://www.zope.org/SamplePackage/Sample-1.0.0.tgz</package>
+ ... <source>svn://svn.zope.org/zf.sample/tags/1.0.0</source>
+ ... <announcement>http://www.zope.org/SamplePackage1Released</announcement>
+ ... <dependencies>
+ ... <dependency>Zope 3.3</dependency>
+ ... </dependencies>
+ ... <release-manager>
+ ... <name>John Doe</name>
+ ... <email>john at doe.com</email>
+ ... </release-manager>
+ ... <press-contact>
+ ... <name>Jane Doe</name>
+ ... <email>jane at doe.com</email>
+ ... </press-contact>
+ ... </release>
+ ... <release>
+ ... <name>Sample Package</name>
+ ... <version>0.9.0</version>
+ ... <date>2006-01-01</date>
+ ... <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ ... </release>
+ ... </releases>
+ ... ''')
+
+ >>> releases = release.processXML(xml)
+ >>> one = releases[0]
+ >>> one.name
+ u'Sample Package'
+ >>> one.version
+ u'1.0.0'
+ >>> one.codename
+ u'CoolName'
+ >>> one.date
+ datetime.date(2006, 2, 3)
+ >>> one.certification
+ u'listed'
+ >>> one.package
+ 'http://www.zope.org/SamplePackage/Sample-1.0.0.tgz'
+ >>> one.source
+ 'svn://svn.zope.org/zf.sample/tags/1.0.0'
+ >>> one.announcement
+ 'http://www.zope.org/SamplePackage1Released'
+ >>> one.dependencies
+ [u'Zope 3.3']
+ >>> one.releaseManager
+ <Contact 'John Doe <john at doe.com>'>
+ >>> one.pressContact
+ <Contact 'Jane Doe <jane at doe.com>'>
+
+ >>> pointNine = releases[1]
+ >>> pointNine.name
+ u'Sample Package'
+ >>> pointNine.version
+ u'0.9.0'
+ >>> pointNine.date
+ datetime.date(2006, 1, 1)
+ >>> pointNine.certification
+ u'none'
+ >>> pointNine.package
+ 'http://www.zope.org/SamplePackage/Sample-0.9.0.tgz'
+
+
+Writing Package Release Data
+----------------------------
+
+The simplest step is to create a totally empty file.
+
+ >>> releases = []
+ >>> print release.produceXML(releases)
+ <releases>
+ </releases>
+
+Now let's add a release to the releases having the minimum data ...
+
+ >>> import datetime
+ >>> pointNine = release.Release()
+ >>> pointNine.name = u'Sample Package'
+ >>> pointNine.version = u'0.9.0'
+ >>> pointNine.date = datetime.date(2006, 2, 3)
+ >>> pointNine.certification = u'level1'
+ >>> pointNine.package = 'http://www.zope.org/SamplePackage/Sample-0.9.0.tgz'
+
+and add it to the releases:
+
+ >>> releases.append(pointNine)
+
+We can now render the structure:
+
+ >>> print release.produceXML(releases)
+ <releases>
+ <release>
+ <name>Sample Package</name>
+ <version>0.9.0</version>
+ <date>2006-02-03</date>
+ <certification>level1</certification>
+ <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ </release>
+ </releases>
+
+Let's now also add the complete 1.0.0 (``one``) release in the first position
+to ensure correct output.
+
+ >>> releases.insert(0, one)
+ >>> print release.produceXML(releases)
+ <releases>
+ <release>
+ <name>Sample Package</name>
+ <version>1.0.0</version>
+ <codename>CoolName</codename>
+ <date>2006-02-03</date>
+ <certification>listed</certification>
+ <package>http://www.zope.org/SamplePackage/Sample-1.0.0.tgz</package>
+ <source>svn://svn.zope.org/zf.sample/tags/1.0.0</source>
+ <announcement>http://www.zope.org/SamplePackage1Released</announcement>
+ <dependencies>
+ <dependency>Zope 3.3</dependency>
+ </dependencies>
+ <release-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </release-manager>
+ <press-contact>
+ <name>Jane Doe</name>
+ <email>jane at doe.com</email>
+ </press-contact>
+ </release>
+ <release>
+ <name>Sample Package</name>
+ <version>0.9.0</version>
+ <date>2006-02-03</date>
+ <certification>level1</certification>
+ <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ </release>
+ </releases>
Property changes on: zf.zscp/trunk/src/zf/zscp/release.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zf.zscp/trunk/src/zf/zscp/release.xmlt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/release.xmlt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/release.xmlt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,33 @@
+<releases>
+ <release tal:repeat="release options/root">
+ <name tal:content="release/name" />
+ <version tal:content="release/version" />
+ <codename
+ tal:condition="release/codename"
+ tal:content="release/codename" />
+ <date tal:content="release/date" />
+ <certification tal:content="release/certification" />
+ <package tal:content="release/package" />
+ <source
+ tal:condition="release/source"
+ tal:content="release/source" />
+ <announcement
+ tal:condition="release/announcement"
+ tal:content="release/announcement" />
+ <dependencies
+ tal:condition="release/dependencies"
+ tal:repeat="dependency release/dependencies">
+ <dependency tal:content="dependency" />
+ </dependencies>
+ <release-manager
+ tal:condition="release/releaseManager">
+ <name tal:content="release/releaseManager/name" />
+ <email tal:content="release/releaseManager/email" />
+ </release-manager>
+ <press-contact
+ tal:condition="release/pressContact">
+ <name tal:content="release/pressContact/name" />
+ <email tal:content="release/pressContact/email" />
+ </press-contact>
+ </release>
+</releases>
Added: zf.zscp/trunk/src/zf/zscp/tests.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/tests.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/tests.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,208 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Viewlet tests
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+import os
+import unittest
+import StringIO
+from zope.testing import doctest
+from zope.testing.doctestunit import DocFileSuite, pprint
+
+class Directory(dict):
+
+ def checkout(self, target, svnPath):
+ os.mkdir(target)
+ # Write a local .svn file that stores the target data.
+ svnFile = file(os.path.join(target, '.svn'), 'w')
+ svnFile.write(svnPath)
+ svnFile.close()
+ # Now recurse through the sub directories
+ for name, value in self.items():
+ path = os.path.join(target, name)
+ value.checkout(path, os.path.join(svnPath, name))
+
+ def checkin(self, localPath):
+ for name, value in self.items():
+ path = os.path.join(localPath, name)
+ value.checkin(path)
+
+ def update(self, localPath):
+ if not os.path.exists(localPath):
+ os.mkdir(localPath)
+ for name, value in self.items():
+ fullPath = os.path.join(localPath, name)
+ value.update(fullPath)
+
+
+class File(StringIO.StringIO):
+
+ def checkout(self, target, svnPath):
+ f = file(target, 'w')
+ f.write(self.getvalue())
+ f.close()
+
+ def checkin(self, localPath):
+ f = file(localPath, 'r')
+ self.__init__(f.read())
+ f.close()
+
+ def update(self, localPath):
+ f = file(localPath, 'w')
+ f.write(self.getvalue())
+ f.close()
+
+
+class SVNTestClient(object):
+
+ root = None
+ dir = None
+ adds = []
+
+ def _getSVNPath(self, svnDir):
+ local_path = svnDir.replace(self.root, '')
+ obj = self.dir
+ for segment in local_path.split('/'):
+ if segment == '':
+ continue
+ obj = obj[segment]
+ return obj
+
+ def checkout(self, svnDir, localDir):
+ obj = self._getSVNPath(svnDir)
+ obj.checkout(localDir, svnDir)
+
+ def checkin(self, localDir, message):
+ while self.adds:
+ dir = self.adds.pop()
+ baseDir, name = os.path.split(dir)
+ svnPath = file(os.path.join(baseDir, '.svn'), 'r').read()
+ obj = self._getSVNPath(svnPath)
+ if os.path.isdir(dir):
+ obj[name] = Directory()
+ else:
+ f = file(dir)
+ obj[name] = File(f.read())
+ f.close()
+
+ svnPath = file(os.path.join(localDir, '.svn'), 'r').read()
+ self._getSVNPath(svnPath).checkin(localDir)
+
+ def update(self, localDir):
+ svnPath = file(os.path.join(localDir, '.svn'), 'r').read()
+ obj = self._getSVNPath(svnPath)
+ obj.update(localDir)
+
+ def ls(self, svnDir):
+ obj = self._getSVNPath(svnDir)
+ return [{'name': svnDir + '/' + name} for name in obj]
+
+ def mkdir(self, svnDir, message):
+ svnDir, name = os.path.split(svnDir)
+ obj = self._getSVNPath(svnDir)
+ obj[name] = Directory()
+
+ def add(self, files):
+ if isinstance(files, list):
+ self.adds += files
+ else:
+ self.adds.append(files)
+
+ def remove(self, svnPath):
+ svnDir, name = os.path.split(svnPath)
+ obj = self._getSVNPath(svnDir)
+ del obj[name]
+
+
+ZSCP_cfg = '''\
+publication PUBLICATION.cfg
+certifications CERTIFICATIONS.xml
+releases RELEASES.xml
+'''
+
+PUBLICATION_cfg = '''\
+Package-name: zope.sample
+Name: Sample Package
+Summary: This is the Sample Package.
+Author: John Doe
+Author-email: john at doe.com
+License: ZPL 2.1
+Metadata-version: 1.0
+'''
+
+RELEASES_xml = '''\
+<releases>
+ <release>
+ <name>Sample Package</name>
+ <version>0.9.0</version>
+ <date>2006-02-03</date>
+ <certification>level1</certification>
+ <package>http://www.zope.org/SamplePackage/Sample-0.9.0.tgz</package>
+ </release>
+</releases>
+'''
+
+CERTIFICATIONS_xml = '''\
+<certifications>
+ <certification>
+ <action>grant</action>
+ <source-level>none</source-level>
+ <target-level>listed</target-level>
+ <date>2006-01-01</date>
+ <certification-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </certification-manager>
+ </certification>
+</certifications>
+'''
+
+def zscpSetUp(test):
+ client = SVNTestClient()
+
+ client.dir = Directory()
+ client.dir['zope.sample'] = Directory()
+ client.dir['zope.sample']['zscp'] = Directory()
+ client.dir['zope.sample']['zscp']['ZSCP.cfg'] = File(ZSCP_cfg)
+ client.dir['zope.sample']['zscp']['PUBLICATION.cfg'] = File(PUBLICATION_cfg)
+ client.dir['zope.sample']['zscp']['RELEASES.xml'] = File(RELEASES_xml)
+ client.dir['zope.sample']['zscp']['CERTIFICATIONS.xml'] = File(
+ CERTIFICATIONS_xml)
+ client.dir['zope.sample1'] = Directory()
+ client.dir['zope.sample2'] = Directory()
+
+ test.globs['svnClient'] = client
+
+def test_suite():
+ return unittest.TestSuite((
+ DocFileSuite('release.txt',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('certification.txt',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('publication.txt',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('zscp.txt',
+ setUp=zscpSetUp,
+ globs={'pprint': pprint},
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: zf.zscp/trunk/src/zf/zscp/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/website/__init__.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/website/__init__.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/website/__init__.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1 @@
+# Make a pacakge.
Property changes on: zf.zscp/trunk/src/zf/zscp/website/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/website/interfaces.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/website/interfaces.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/website/interfaces.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""ZSCP Web Site Interfaces
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.app import folder
+
+class IZSCPSite(folder.interfaces.IFolder):
+ """The root object for the ZSCP site.
+
+ The site mainly contains ZSCP repository objects.
+ """
Property changes on: zf.zscp/trunk/src/zf/zscp/website/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/website/zscp.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/website/zscp.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/website/zscp.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""ZSCP Web Site
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+from zope.app import folder
+
+from zf.zscp.website import interfaces
+
+
+class ZSCPSite(folder.folder.Folder):
+ zope.interface.implements(interface.IZSCPSite)
+
+
+
Property changes on: zf.zscp/trunk/src/zf/zscp/website/zscp.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/zscp.py
===================================================================
--- zf.zscp/trunk/src/zf/zscp/zscp.py 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/zscp.py 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,216 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""ZSCP Data Management
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import os
+import os.path
+import pysvn
+import zope.event
+import zope.interface
+
+from zf.zscp import interfaces, package, publication, release, certification
+
+
+class ZSCPRepository(object):
+ """A ZSCP-compliant repository."""
+ zope.interface.implements(interfaces.IZSCPRepository)
+
+ def __init__(self, svnRoot, localRoot, password):
+ self.svnRoot = svnRoot
+ self.localRoot = localRoot
+ self.password = password
+
+ def _getClient(self):
+ """Get an SVN client."""
+ client = pysvn.Client()
+
+ def ssl_password(realm, may_save):
+ return True, self.password, True
+ client.callback_ssl_client_cert_password_prompt = ssl_password
+
+ return client
+
+ def initialize(self):
+ """See interfaces.IZSCPRepository"""
+ client = self._getClient()
+ # for each package that is already part of the ZSCP, make sure to
+ # check out the ZSCP data.
+ for name in self.fetch():
+ full_url = self.svnRoot + '/' + name + '/zscp'
+ local_path = os.path.join(self.localRoot, name)
+ client.checkout(full_url, local_path)
+ # Send out an event notification
+ zope.event.notify(interfaces.RepositoryInitializedEvent(self))
+
+ def register(self, pkg):
+ """See interfaces.IZSCPRepository"""
+ client = self._getClient()
+ full_url = self.svnRoot + '/' + pkg.name + '/zscp'
+ local_path = os.path.join(self.localRoot, pkg.name)
+ # Create a directory for zscp remotely
+ client.mkdir(full_url, 'Create a directory for the ZSCP process.')
+ # Check out the directory
+ client.checkout(full_url, local_path)
+ # Create a ZSCP.cfg file
+ zscp = {'publication': 'PUBLICATION.cfg',
+ 'releases': 'RELEASES.xml',
+ 'certifications': 'CERTIFICATIONS.xml'}
+ zscp_file = file(os.path.join(local_path, 'ZSCP.cfg'), 'w')
+ zscp_file.write(produce(zscp))
+ zscp_file.close()
+ # Now update that data
+ self.update(pkg)
+ # Add all files to the repository and check them in
+ client.add([os.path.join(local_path, 'ZSCP.cfg'),
+ os.path.join(local_path, 'PUBLICATION.cfg'),
+ os.path.join(local_path, 'RELEASES.xml'),
+ os.path.join(local_path, 'CERTIFICATIONS.xml')])
+ client.checkin(local_path, 'Initial addition of package data.')
+ # Send out an event notification
+ zope.event.notify(interfaces.PackageRegisteredEvent(pkg))
+
+ def unregister(self, pkg):
+ """See interfaces.IZSCPRepository"""
+ client = self._getClient()
+ full_url = self.svnRoot + '/' + pkg.name + '/zscp'
+ # Remove the directory
+ client.remove(full_url)
+ # Remove local checkout
+ # LAME! Simulate recursive delete!
+ def remove(arg, dirname, fnames):
+ for fname in fnames:
+ path = os.path.join(dirname, fname)
+ if os.path.isfile(path):
+ os.remove(path)
+
+ os.path.walk(os.path.join(self.localRoot, pkg.name), remove, None)
+ os.removedirs(os.path.join(self.localRoot, pkg.name))
+ # Send out an event notification
+ zope.event.notify(interfaces.PackageUnregisteredEvent(pkg))
+
+ def update(self, pkg):
+ """See interfaces.IZSCPRepository"""
+ client = self._getClient()
+ local_path = os.path.join(self.localRoot, pkg.name)
+ # Do checkout update
+ client.update(local_path)
+ # Load the ZSCP configuration
+ zscp_path = os.path.join(local_path, 'ZSCP.cfg')
+ zscp = process(file(zscp_path))
+ # Update publication
+ pub_file = file(os.path.join(local_path, zscp['publication']), 'w')
+ pub_file.write(publication.produce(pkg.publication))
+ pub_file.close()
+ # Update releases
+ rel_file = file(os.path.join(local_path, zscp['releases']), 'w')
+ rel_file.write(release.produceXML(pkg.releases))
+ rel_file.close()
+ # Update certifications
+ cert_file = file(os.path.join(local_path, zscp['certifications']), 'w')
+ cert_file.write(certification.produceXML(pkg.certifications))
+ cert_file.close()
+ # Commit the changes
+ client.checkin(local_path, 'Update of package data.')
+ # Send out an event notification
+ zope.event.notify(interfaces.PackageUpdatedEvent(pkg))
+
+ def fetch(self, all=False):
+ """See interfaces.IZSCPRepository"""
+ names = []
+ client = self._getClient()
+ for entry in client.ls(self.svnRoot):
+ name = os.path.split(entry['name'])[-1]
+ if all is True:
+ names.append(name)
+ else:
+ path = entry['name'] + '/zscp'
+ if path in [e['name'] for e in client.ls(entry['name'])]:
+ names.append(name)
+ return names
+
+ def __getitem__(self, key):
+ """See zope.interface.common.mapping.IItemMapping"""
+ if key not in os.listdir(self.localRoot):
+ raise KeyError, key
+
+ local_path = os.path.join(self.localRoot, key)
+ # Load the ZSCP configuration
+ zscp_path = os.path.join(local_path, 'ZSCP.cfg')
+ zscp = process(file(zscp_path))
+ # Create the package
+ pkg = package.Package(key)
+ # Add publication
+ pub_file = file(os.path.join(local_path, zscp['publication']), 'r')
+ pkg.publication = publication.process(pub_file)
+ pub_file.close()
+ # Add releases
+ rel_file = file(os.path.join(local_path, zscp['releases']), 'r')
+ pkg.releases = release.processXML(rel_file)
+ rel_file.close()
+ # Add certifications
+ cert_file = file(os.path.join(local_path, zscp['certifications']), 'r')
+ pkg.certifications = certification.processXML(cert_file)
+ cert_file.close()
+
+ return pkg
+
+ def keys(self):
+ """See zope.interface.common.mapping.IEnumerableMapping"""
+ return os.listdir(self.localRoot)
+
+ def get(self, key, default=None):
+ """See zope.interface.common.mapping.IReadMapping"""
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def __contains__(self, key):
+ """See zope.interface.common.mapping.IReadMapping"""
+ return key in self.keys()
+
+ def __iter__(self):
+ """See zope.interface.common.mapping.IEnumerableMapping"""
+ return iter(keys)
+
+ def values(self):
+ """See zope.interface.common.mapping.IEnumerableMapping"""
+ return [self[name] for name in self.keys()]
+
+ def items(self):
+ """See zope.interface.common.mapping.IEnumerableMapping"""
+ return [(name, self[name]) for name in self.keys()]
+
+ def __len__(self):
+ """See zope.interface.common.mapping.IEnumerableMapping"""
+ return len(self.keys())
+
+
+def process(file):
+ """Process the ZSCP.cfg file content."""
+ config = {}
+ for line in file.readlines():
+ line = line.strip()
+ if line.startswith('#') or not line:
+ continue
+ key, value = line.split(' ')
+ config[key] = value
+ return config
+
+def produce(zscp):
+ """Produce the ZSCP.cfg file content."""
+ return '\n'.join(['%s %s' %(key, value) for key, value in zscp.items()])
Property changes on: zf.zscp/trunk/src/zf/zscp/zscp.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: zf.zscp/trunk/src/zf/zscp/zscp.txt
===================================================================
--- zf.zscp/trunk/src/zf/zscp/zscp.txt 2006-03-02 00:34:56 UTC (rev 65706)
+++ zf.zscp/trunk/src/zf/zscp/zscp.txt 2006-03-02 00:35:40 UTC (rev 65707)
@@ -0,0 +1,203 @@
+=====================
+A ZSCP Implementation
+=====================
+
+The zscp module,
+
+ >>> from zf.zscp import zscp
+
+implements the process for an SVN repository. It is controoled by the ZSCP
+repository object:
+
+ >>> import tempfile
+ >>> localRoot = tempfile.mkdtemp()
+ >>> repos = zscp.ZSCPRepository(
+ ... 'svn+ssh://svn.zope.org/repos/main', localRoot, 'mypass')
+
+The first argument of the constructor is the SVN URL to the repository, the
+second is the directory that will be used to checkout the ``zscp`` directories
+and the final argument is the passphrase for the SSL authentication.
+
+For the purpose of this test, we register a stub pysvn client:
+
+ >>> svnClient.root = 'svn+ssh://svn.zope.org/repos/main'
+ >>> repos._getClient = lambda : svnClient
+
+The initial test SVN repository looks as follows::
+
+ root/
+ zope.sample/
+ zscp/
+ ZSCP.cfg
+ PUBLICATION.cfg
+ RELEASES.xml
+ CERTIFICATIONS.xml
+ zope.sample1/
+ zope.sample2/
+
+With the setup all done, let's see how the ZSCP is realized. The first step is
+to initialize the repository, which means that all available package ``zscp``
+directories are checked out from the repository. In the local copy, those
+``zscp`` directories will be known by their package name:
+
+ >>> repos.initialize()
+
+ >>> import os
+ >>> os.listdir(localRoot)
+ ['zope.sample']
+
+Since only the ``zope.sample`` package has a ``zscp`` directory, it is the
+only one checked out. You can also use the ``fetch()`` method to get a list of
+all ZSCP packages in the repository:
+
+ >>> repos.fetch()
+ ['zope.sample']
+
+When you pass ``all=True`` to the method, then all packages in the repository
+will be returned:
+
+ >>> sorted(repos.fetch(all=True))
+ ['zope.sample', 'zope.sample1', 'zope.sample2']
+
+Once the repository is initialized, you can use the mapping interface to
+discover the content:
+
+ >>> len(repos)
+ 1
+
+ >>> 'zope.sample' in repos
+ True
+ >>> 'zope.sample1' in repos
+ False
+
+ >>> repos['zope.sample']
+ <Package 'zope.sample'>
+ >>> repos.get('zope.sample')
+ <Package 'zope.sample'>
+ >>> repos.get('zope.sample1') is None
+ True
+
+ >>> repos.keys()
+ ['zope.sample']
+ >>> repos.items()
+ [('zope.sample', <Package 'zope.sample'>)]
+ >>> repos.values()
+ [<Package 'zope.sample'>]
+
+Now we would like to register a new package with the ZSCP process. This is
+done by first creating a package ...
+
+ >>> from zf.zscp import package, publication
+ >>> sample1 = package.Package('zope.sample1')
+
+ >>> sample1.publication = publication.Publication()
+ >>> sample1.publication.packageName = 'zope.sample1'
+ >>> sample1.publication.name = u'Sample Package 1'
+ >>> sample1.publication.summary = u'This is the Sample Package 1.'
+ >>> sample1.publication.author = [u'Jane Doe']
+ >>> sample1.publication.authorEmail = [u'jane at doe.com']
+ >>> sample1.publication.license = [u'GPL 2.0']
+ >>> sample1.publication.metadataVersion = u'1.0'
+
+ >>> sample1.certifications = []
+ >>> sample1.releases = []
+
+and then registering it:
+
+ >>> repos.register(sample1)
+
+Let's make sure all the data was really stored in the SVN repository:
+
+ >>> sorted(repos.items())
+ [('zope.sample', <Package 'zope.sample'>),
+ ('zope.sample1', <Package 'zope.sample1'>)]
+
+ >>> svnClient.dir['zope.sample1']['zscp'].keys()
+ ['PUBLICATION.cfg', 'CERTIFICATIONS.xml', 'RELEASES.xml', 'ZSCP.cfg']
+
+At the beginning there are no certifications:
+
+ >>> sample1.certifications
+ []
+ >>> print svnClient.dir['zope.sample1']['zscp']['CERTIFICATIONS.xml'].read()
+ <certifications>
+ </certifications>
+
+Let's now add a certification:
+
+ >>> import datetime
+ >>> from zf.zscp import certification, contact
+ >>> listed = certification.Certification()
+ >>> listed.action = u'grant'
+ >>> listed.sourceLevel = u'none'
+ >>> listed.targetLevel = u'listed'
+ >>> listed.date = datetime.date(2006, 1, 1)
+ >>> listed.certificationManager = contact.Contact()
+ >>> listed.certificationManager.name = 'John Doe'
+ >>> listed.certificationManager.email = 'john at doe.com'
+ >>> sample1.certifications.append(listed)
+
+To update the checkout and the repository, we can do the following:
+
+ >>> repos.update(sample1)
+
+So now we should have an entry:
+
+ >>> sample1.certifications
+ [<Certification action=u'grant', source=u'none', target=u'listed'>]
+ >>> svnClient.dir['zope.sample1']['zscp']['CERTIFICATIONS.xml'].seek(0)
+ >>> print svnClient.dir['zope.sample1']['zscp']['CERTIFICATIONS.xml'].read()
+ <certifications>
+ <certification>
+ <action>grant</action>
+ <source-level>none</source-level>
+ <target-level>listed</target-level>
+ <date>2006-01-01</date>
+ <certification-manager>
+ <name>John Doe</name>
+ <email>john at doe.com</email>
+ </certification-manager>
+ </certification>
+ </certifications>
+
+In case a project wants to be removed from the ZSCP process, you simply
+unregister it:
+
+ >>> repos.unregister(sample1)
+
+It should be gone in the SVN repository and the local checkout:
+
+
+ >>> print svnClient.dir['zope.sample1'].keys()
+ []
+ >>> os.listdir(localRoot)
+ ['zope.sample']
+
+And that's it.
+
+
+Parsing and writing the ``ZSCP.cfg`` file
+-----------------------------------------
+
+It is necessary to parse the ``ZSCP.cfg`` file in order to determine the
+locations of the other data files.
+
+ >>> import StringIO
+ >>> config = StringIO.StringIO(u'''
+ ... publication PUBLICATION.cfg
+ ... releases RELEASES.xml
+ ... certifications CERTIFICATIONS.xml
+ ... ''')
+
+ >>> zscp_data = zscp.process(config)
+ >>> pprint(zscp_data)
+ {u'certifications': u'CERTIFICATIONS.xml',
+ u'publication': u'PUBLICATION.cfg',
+ u'releases': u'RELEASES.xml'}
+
+On the other hand, we also need to be able create the contents of the file:
+
+ >>> print zscp.produce(zscp_data)
+ certifications CERTIFICATIONS.xml
+ publication PUBLICATION.cfg
+ releases RELEASES.xml
Property changes on: zf.zscp/trunk/src/zf/zscp/zscp.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Zope-CVS
mailing list