[Zope3-checkins] SVN: Zope3/trunk/ Added the z3checkins product to
the Zope 3 tree.
Gintautas Miliauskas
gintas at pov.lt
Wed May 19 06:25:42 EDT 2004
Log message for revision 24818:
Added the z3checkins product to the Zope 3 tree.
-=-
Added: Zope3/trunk/package-includes/z3checkins-configure.zcml
===================================================================
--- Zope3/trunk/package-includes/z3checkins-configure.zcml 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/package-includes/z3checkins-configure.zcml 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1 @@
+<include package="z3checkins" />
Property changes on: Zope3/trunk/package-includes/z3checkins-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/README
===================================================================
--- Zope3/trunk/src/z3checkins/README 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/README 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,104 @@
+Zope 3 Checkins
+===============
+
+This is a Zope 3 product for keeping track of <zope3-checkins at zope.org> mailing
+list. It adds a new content object type: CheckinMessage. You can upload all
+messages from the mailing list to your Zope 3 instance with a simple procmail
+rule, and then view the latest checkins in your Mozilla sidebar or any news
+aggregator that supports RSS, e.g. Nautilus.
+
+It is also quite usable for other checkin-tracking mailing lists. However
+since there is no single standard on how the messages should be formatted,
+your mileage may vary.
+
+
+Installation
+------------
+
+Start up Zope 3 and add a Checkin Folder. You will be presented with a form.
+
+ - the first field is a description used for checkins.rss view
+ - the second field is a URL to the traditional mailing list archive.
+ - the third field is a list of icon definitions used for checkins to
+ different parts of the source trees. Each line should contain four
+ fields:
+
+ prefix icon-name alt-text title
+
+ prefix is matched against the beginning of the checkin directory (put
+ longer, more specific prefixes first. * is a catch-all prefix, and
+ should be placed last).
+
+ icon-name is a Zope 3 resource name. z3checkins comes with the following
+ icons: zope3.png, product.png, message.png
+
+ alt-text is a short alternate text
+
+ title is a longer description, usually shown in a tooltip
+
+You would use the following configuration for zope3-checkins at zope.org:
+
+ RSS view description:
+ Latest Zope 3 Checkins
+
+ URL of mailing list:
+ http://mail.zope.org/pipermail/zope3-checkins/
+
+ Icon definitions:
+ Zope3 zope3.png Z3 Zope 3 core
+ * product.png Product Zope 3 product
+
+Then, go to the Metadata tab and set the title.
+
+Now you can create Checkin Messages in that folder. In the rest of this
+document I assume Zope 3 is accessible at http://localhost:8080/ and that
+the checkin folder is called 'zope3-checkins'.
+
+
+Upload script
+-------------
+
+Here's an example of a shell script that uses curl to upload a file or a list
+of files, each containing a single RFC822 mail message:
+
+ #!/bin/sh
+ for file in "$@"; do
+ curl -F field.data=@- -F UPDATE_SUBMIT=Submit -s -S \
+ -u username:password \
+ http://localhost:8080/zope3-checkins/+/CheckinMessage \
+ < "$file" > /dev/null
+ done
+
+Replace username:password with the username and password of a Zope 3 user that
+has zope.ManageContent permission in the 'zope3-checkins' folder.
+
+This script may be used to import archives in Maildir format. Or you can
+use formail or some other tool to split mbox folders available at
+http://mail.zope.org/pipermail/zope3-checkins/
+
+
+Procmail
+--------
+
+Add this to your .procmailrc to upload new messages automatically:
+
+:0:
+* ^List-Id:.*<zope3-checkins\.zope\.org>
+| curl -F field.data=@- -F UPDATE_SUBMIT=Submit -s -S -u username:password \
+ http://localhost:8080/zope3-checkins/+/CheckinMessage > /dev/null
+
+Replace username:password with the username and password of a Zope 3 user that
+has zope.ManageContent permission in the 'zope3-checkins' folder.
+
+
+RSS feed
+--------
+
+Use the following URL to access last checkins in RSS format:
+
+ http://localhost:8080/zope3-checkins/checkins.rss
+
+
+Good luck,
+Marius Gedminas
+<marius at pov.lt>
Property changes on: Zope3/trunk/src/z3checkins/README
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/TODO
===================================================================
--- Zope3/trunk/src/z3checkins/TODO 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/TODO 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,19 @@
+I definitely want to do the following:
+
+- Make sure non-ASCII chars work both in headers and in bodies
+
+I'm not sure I'll find time for these:
+
+- Is storing all the messages in a single folder scalable enough?
+- Highlight branch tags in message body
+- Highlight quoted text in normal message bodies
+- Show checkin times in the user's timezone instead of server's (this probably
+ needs some configuration page and cookies)
+- Replace newlines with <br/> elements in checkin messages in message_part.pt
+- Shorten descriptions in message_part.pt to first 100 chars (words? sentences?
+ lines?)
+- Add links to the specific versions of the files mentioned in the CVS web
+ interface
+- Filtering (by branch, by author, etc)
+- Threading?
+- Verify how all this works in other browsers beside Mozilla
Property changes on: Zope3/trunk/src/z3checkins/TODO
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/__init__.py
===================================================================
--- Zope3/trunk/src/z3checkins/__init__.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/__init__.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,4 @@
+"""
+Zope 3 product for processing zope3-checkins mailing list messages and
+presenting them in various formats.
+"""
Property changes on: Zope3/trunk/src/z3checkins/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/bookmark.pt
===================================================================
--- Zope3/trunk/src/z3checkins/bookmark.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/bookmark.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1 @@
+<hr />
Property changes on: Zope3/trunk/src/z3checkins/bookmark.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/branch.png
===================================================================
(Binary files differ)
Property changes on: Zope3/trunk/src/z3checkins/branch.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: Zope3/trunk/src/z3checkins/configure.zcml
===================================================================
--- Zope3/trunk/src/z3checkins/configure.zcml 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/configure.zcml 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,256 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain='z3checkins'
+ >
+
+<!-- CheckinMessage content object -->
+
+ <content class=".message.Message">
+
+ <require permission="zope.View"
+ interface=".interfaces.IMessage" />
+
+ <implements
+ interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
+
+ </content>
+
+ <content class=".message.CheckinMessage">
+
+ <require permission="zope.View"
+ interface=".interfaces.ICheckinMessage" />
+
+ <implements
+ interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
+
+ </content>
+
+ <content class=".folder.CheckinFolder">
+
+ <require permission="zope.View"
+ interface="zope.app.container.interfaces.IReadContainer" />
+
+ <require permission="zope.ManageContent"
+ interface="zope.app.container.interfaces.IWriteContainer" />
+
+ <require permission="zope.ManageContent"
+ set_schema=".interfaces.ICheckinFolder" />
+
+ <require permission="zope.View"
+ interface=".interfaces.ICheckinFolderSchema" />
+
+ <factory id="CheckinFolder"
+ title="Checkin Folder"
+ description="A checkin folder" />
+
+ <implements
+ interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
+
+ </content>
+
+<!-- Utilities -->
+
+ <adapter for="zope.app.container.interfaces.IReadContainer"
+ factory=".message.MessageContainerAdapter"
+ permission="zope.View"
+ provides=".interfaces.IMessageArchive" />
+
+ <adapter for=".interfaces.ICheckinFolder"
+ factory=".folder.MessageNameChooser"
+ permission="zope.View"
+ provides="zope.app.container.interfaces.INameChooser" />
+
+ <adapter for=".interfaces.IMessage"
+ factory=".folder.MessageSized"
+ permission="zope.View"
+ provides="zope.app.size.interfaces.ISized" />
+
+
+ <utility factory=".message.CheckinMessageParser"
+ permission="zope.View"
+ provides=".interfaces.IMessageParser" />
+
+<!-- Generic views for date/time formatting -->
+
+ <!-- XXX: there should be an interface that datetime.datetime implements -->
+
+ <view
+ for="*"
+ name="rfc822"
+ factory=".message.RFCDateTimeFormatter"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ permission="zope.Public"
+ />
+
+ <view
+ for="*"
+ name="isodatetime"
+ factory=".message.ISODateTimeFormatter"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ permission="zope.Public"
+ />
+
+<!-- Browser views: adding -->
+
+ <browser:addform
+ name="CheckinMessage"
+ schema="zope.app.file.interfaces.IFile"
+ fields="data"
+ label="Upload a checkin message"
+ permission="zope.ManageContent"
+ class=".message.MessageUpload" />
+
+ <browser:addMenuItem
+ title="Checkin message"
+ class=".message.MessageUpload"
+ permission="zope.ManageContent"
+ view="CheckinMessage" />
+
+ <browser:addform
+ name="CheckinFolder"
+ schema=".interfaces.ICheckinFolderSchema"
+ fields="description archive_url icons"
+ label="Create a checkin message folder"
+ permission="zope.ManageContent"
+ content_factory=".folder.CheckinFolder" />
+
+ <browser:addMenuItem
+ title="Checkin Folder"
+ class=".folder.CheckinFolder"
+ permission="zope.ManageContent"
+ view="CheckinFolder" />
+
+ <browser:view
+ name="+"
+ menu="zmi_actions" title="Add"
+ for=".interfaces.ICheckinFolder"
+ permission="zope.ManageContent"
+ class="zope.app.container.browser.adding.Adding">
+
+ <page name="index.html" attribute="index" />
+ <page name="action.html" attribute="action" />
+
+ </browser:view>
+
+ <browser:editform
+ name="EditFolder"
+ schema=".interfaces.ICheckinFolder"
+ label="Change properties of a checkin message folder"
+ menu="zmi_views" title="Edit"
+ permission="zope.ManageContent" />
+
+<!-- Browser views: email message -->
+
+ <browser:page
+ for=".interfaces.IMessage"
+ name="rss"
+ class=".message.MessageRSSView"
+ attribute="index"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.IMessage"
+ name="html"
+ template="message_part.pt"
+ class=".message.MessageView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.IMessage"
+ name="html-sidebar"
+ template="message_sidebar.pt"
+ class=".message.MessageView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.IMessage"
+ name="index.html"
+ template="message.pt"
+ class=".message.MessageView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.IMessage"
+ name="index.txt"
+ template="message_plain.pt"
+ class=".message.MessageView"
+ permission="zope.View" />
+
+<!-- Browser views: checkin message -->
+
+ <browser:page
+ for=".interfaces.ICheckinMessage"
+ name="rss"
+ class=".message.MessageRSSView"
+ attribute="index"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinMessage"
+ name="html"
+ template="message_part.pt"
+ class=".message.CheckinMessageView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinMessage"
+ name="html-sidebar"
+ template="message_sidebar.pt"
+ class=".message.CheckinMessageView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinMessage"
+ name="index.html"
+ template="message.pt"
+ class=".message.CheckinMessageView"
+ menu="zmi_views" title="Preview"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinMessage"
+ name="index.txt"
+ template="message_plain.pt"
+ class=".message.CheckinMessageView"
+ permission="zope.View" />
+
+<!-- Browser views: bookmark -->
+
+ <browser:page
+ for=".interfaces.IBookmark"
+ name="html"
+ template="bookmark.pt"
+ permission="zope.View" />
+
+<!-- Browser views: containers -->
+
+ <browser:page
+ for=".interfaces.ICheckinFolder"
+ name="checkins.rss"
+ template="rss_container.pt"
+ class=".message.ContainerView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinFolder"
+ name="index.html"
+ template="container.pt"
+ class=".message.ContainerView"
+ permission="zope.View" />
+
+ <browser:page
+ for=".interfaces.ICheckinFolder"
+ name="checkins-sidebar.html"
+ template="container_sidebar.pt"
+ class=".message.ContainerView"
+ permission="zope.View" />
+
+<!-- Resources -->
+
+ <browser:resource name="message.png" file="message.png" />
+ <browser:resource name="zope3.png" file="zope3.png" />
+ <browser:resource name="product.png" file="product.png" />
+ <browser:resource name="branch.png" file="branch.png" />
+
+</configure>
Property changes on: Zope3/trunk/src/z3checkins/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/container.pt
===================================================================
--- Zope3/trunk/src/z3checkins/container.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/container.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,69 @@
+<html>
+<head>
+<title tal:content="view/title">Zope 3 Checkins</title>
+<style type="text/css">
+ * { font-size: small; }
+ h1 { font-size: medium; margin-bottom: 0.5ex; }
+ div.toolbar { font-size: xx-small; margin-left: 1em; margin-bottom: 1.5em; }
+ div.toolbar > a { display: block; width: 100%; }
+ div.navigation { margin-top: 1em; }
+ div.navigation > a { display: block; width: 100%; text-align: right; }
+ .message { margin-top: 1ex; }
+ .description { font-size: small; margin-left: 1em; }
+ .same { color: gray; margin-top: 0.5ex; }
+ .author { font-weight: bold; }
+ a.title { display: block; width: 100%; }
+ a:hover { background: #e0e6ff; }
+ img.icon { float: right; padding: 0ex; margin: 2px; border: none; }
+ hr { width: 60%; border: none; background: gray; height: 1px;
+ margin-top: 1ex; margin-bottom: 0ex; }
+</style>
+<style type="text/css">
+ .description { white-space: pre; }
+</style>
+</head>
+<body tal:define="dummy view/placeBookmark;
+ start python:int(request.get('start', '0'));
+ size python:int(request.get('size', '20'));
+ opt_size python:(size != 20) and '&size=%d' % size or ''">
+<h1 tal:replace="view/title">Zope 3 Checkins</h1>
+
+<div class="toolbar"
+ tal:define="first_batch python:start <= 0">
+<tal:block tal:replace='structure string:
+<script language="JavaScript" type="text/javascript">
+<!--
+if ((typeof window.sidebar == "object") && (typeof window.sidebar.addPanel == "function")) {
+ url = "${context/@@absolute_url}/@@checkins-sidebar.html";
+ document.write("<a href=\"javascript:window.sidebar.addPanel('${view/title}', '" + url + "', '');\">Add to sidebar</a>");
+}
+//-->
+</script>
+'/>
+<a tal:condition="view/archive_url"
+ tal:attributes="href view/archive_url"
+ href="http://mail.zope.org/pipermail/zope3-checkins/">List archives</a>
+<a tal:condition="first_batch"
+ href="javascript:window.location.reload()">Refresh</a>
+<a tal:condition="not: first_batch"
+ tal:attributes="href request/URL">Newest checkins</a>
+</div>
+
+<div class="navigation">
+<a tal:define="prev python:max(0, start - size)"
+ tal:condition="python: start > 0"
+ tal:attributes="href string:${request/URL}?start=${prev}${opt_size}">Previous
+ <span tal:replace="size">20</span></a>
+</div>
+
+<div tal:replace="structure view/renderCheckins" />
+
+<div class="navigation">
+<a tal:define="next python:start + size"
+ tal:condition="python: next < view.count()"
+ tal:attributes="href string:${request/URL}?start=${next}${opt_size}">Next
+ <span tal:replace="size">20</span></a>
+</div>
+
+</body>
+</html>
Property changes on: Zope3/trunk/src/z3checkins/container.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/container_sidebar.pt
===================================================================
--- Zope3/trunk/src/z3checkins/container_sidebar.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/container_sidebar.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,57 @@
+<html>
+<head>
+<title tal:content="view/title">Zope 3 Checkins</title>
+<style type="text/css">
+ * { font-size: small; }
+ h1 { font-size: medium; margin-bottom: 0.5ex; }
+ div.toolbar { font-size: xx-small; margin-left: 1em; margin-bottom: 1.5em; }
+ div.toolbar > a { display: block; width: 100%; }
+ div.navigation { margin-top: 1em; }
+ div.navigation > a { display: block; width: 100%; text-align: right; }
+ .message { margin-top: 1ex; }
+ .description { font-size: small; margin-left: 1em; }
+ .same { color: gray; margin-top: 0.5ex; }
+ .author { font-weight: bold; }
+ a.title { display: block; width: 100%; }
+ a:hover { background: #e0e6ff; }
+ img.icon { float: right; padding: 0ex; margin: 2px; border: none; }
+ hr { width: 60%; border: none; background: gray; height: 1px;
+ margin-top: 1ex; margin-bottom: 0ex; }
+</style>
+</head>
+<body tal:define="dummy view/placeBookmark;
+ start python:int(request.get('start', '0'));
+ size python:int(request.get('size', '20'));
+ opt_size python:(size != 20) and '&size=%d' % size or ''">
+<h1 tal:replace="view/title">Zope 3 Checkins</h1>
+
+<div class="toolbar"
+ tal:define="first_batch python:start <= 0">
+<a tal:condition="view/archive_url"
+ tal:attributes="href view/archive_url"
+ target="_content"
+ href="http://mail.zope.org/pipermail/zope3-checkins/">List archives</a>
+<a tal:condition="first_batch"
+ href="javascript:window.location.reload()">Refresh</a>
+<a tal:condition="not: first_batch"
+ tal:attributes="href request/URL">Newest checkins</a>
+</div>
+
+<div class="navigation">
+<a tal:define="prev python:max(0, start - size)"
+ tal:condition="python: start > 0"
+ tal:attributes="href string:${request/URL}?start=${prev}${opt_size}">Previous
+ <span tal:replace="size">20</span></a>
+</div>
+
+<div tal:replace="structure view/renderCheckins" />
+
+<div class="navigation">
+<a tal:define="next python:start + size"
+ tal:condition="python: next < view.count()"
+ tal:attributes="href string:${request/URL}?start=${next}${opt_size}">Next
+ <span tal:replace="size">20</span></a>
+</div>
+
+</body>
+</html>
Property changes on: Zope3/trunk/src/z3checkins/container_sidebar.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/folder.py
===================================================================
--- Zope3/trunk/src/z3checkins/folder.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/folder.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,52 @@
+"""
+Python code for z3checkins product.
+
+Checkin message folder handling.
+
+$Id: folder.py,v 1.5 2004/03/14 10:56:48 gintautasm Exp $
+"""
+
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.interfaces import INameChooser
+from zope.app.container.interfaces import IContainerNamesContainer
+from zope.app.size.interfaces import ISized
+from interfaces import ICheckinFolder
+
+class CheckinFolder(BTreeContainer):
+ """A message folder."""
+
+ implements(ICheckinFolder, IContainerNamesContainer)
+
+
+class MessageNameChooser:
+ """An adapter to choose names for messages."""
+
+ implements(INameChooser)
+
+ def __init__(self, context):
+ pass
+
+ def chooseName(self, name, message):
+ return message.message_id
+
+ def checkName(self, name, message):
+ return name == message.message_id
+
+
+class MessageSized:
+ """An adapter to calculate size of a message."""
+ implements(ISized)
+
+ def __init__(self, message):
+ self._message = message
+
+ def sizeForSorting(self):
+ return len(self._message.full_text)
+
+ def sizeForDisplay(self):
+ bytes = len(self._message.full_text)
+ if bytes < 1024:
+ return u'%d bytes' % bytes
+ else:
+ return u'%d KB' % (bytes / 1024)
Property changes on: Zope3/trunk/src/z3checkins/folder.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/ftests/__init__.py
===================================================================
--- Zope3/trunk/src/z3checkins/ftests/__init__.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/ftests/__init__.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,5 @@
+"""
+Functional tests for z3checkins.
+
+$Id: __init__.py,v 1.1 2003/08/01 09:43:20 mgedmin Exp $
+"""
Property changes on: Zope3/trunk/src/z3checkins/ftests/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/ftests/msg1.txt
===================================================================
--- Zope3/trunk/src/z3checkins/ftests/msg1.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/ftests/msg1.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,34 @@
+From: Jim =?ISO-8859-13?Q?=CD?= <jim at example.org>
+To: zope3-checkins at example.com
+Date: Wed, 30 Jul 2003 23:40:11 +0100
+Subject: [Zope3-checkins] CVS: Zope3/src/app/frobulator - frobulator.py:1.5
+Message-Id: <msg1 at example.org>
+
+Update of /cvs-repository/Zope3/src/app/frobulator
+In directory cvs.zope.org:/tmp/cvs-serv12345
+
+Modified files:
+ frobulator.py
+Log message:
+Update the frobulator time conductor API implementation to match the new
+specification.
+
+
+=== Zope3/src/app/frobulator/frobulator.py 1.4 => 1.5 ===
+--- Zope3/src/app/frobulator/frobulator.py:1.4 Tue Mar 25 15:21:29 2003
++++ Zope3/src/app/frobulator/frobulator.py Fri Mar 28 11:57:34 2003
+@@@ -123,7 +123,7 @@
+ z = self._transponder_matrix[0][3] ** 0.5 + epsilon
+ return self.postprocessCochraneCoefficients(x, y, z)
+
+- def performTimeTravel(self, duration):
++ def performTimeTravel(self, duration, avoidParadoxes=True):
+ """
+ The basic time travel function.
+
+
+
+_______________________________________________
+Zope3-Checkins mailing list
+Zope3-Checkins at zope.org
+http://mail.zope.org/mailman/listinfo/zope3-checkins
Property changes on: Zope3/trunk/src/z3checkins/ftests/msg1.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/ftests/msg2.txt
===================================================================
--- Zope3/trunk/src/z3checkins/ftests/msg2.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/ftests/msg2.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,13 @@
+From: Fred <fred at example.org>
+To: zope3-checkins at example.com
+Date: Thu, 31 Jul 2003 17:00:23 +0300
+Subject: CVS: your checkin on Wednesday
+Message-Id: <msg2 at example.org>
+In-Reply-To: <msg1 at example.org>
+
+I did not particuarily understand the change to frobulator.py regarding the
+superconducting time traveller specification. It looks like you omitted at
+least a part of the implementation. Could you double check?
+
+--
+Fred
Property changes on: Zope3/trunk/src/z3checkins/ftests/msg2.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/ftests/test_z3checkins.py
===================================================================
--- Zope3/trunk/src/z3checkins/ftests/test_z3checkins.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/ftests/test_z3checkins.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+"""
+Functional tests for z3checkins.
+
+$Id: test_z3checkins.py,v 1.9 2004/05/15 13:23:59 gintautasm Exp $
+"""
+
+import unittest
+import os
+from zope.app.tests.functional import BrowserTestCase
+
+
+class TestCheckins(BrowserTestCase):
+
+ container_views = ('index.html', 'checkins-sidebar.html', 'checkins.rss')
+ message_views = ('index.html', 'index.txt')
+ resources = ('zope3.png', 'product.png', 'branch.png', 'message.png')
+
+ def open(self, filename):
+ """Open a file relative to the location of this module."""
+ base = os.path.dirname(__file__)
+ return open(os.path.join(base, filename))
+
+ def setUp(self):
+ BrowserTestCase.setUp(self)
+ response = self.publish('/+/action.html', basic='mgr:mgrpw',
+ form={'type_name': u'CheckinFolder', 'id': u'z3c'})
+ self.assertEqual(response.getStatus(), 302)
+
+ response = self.publish(
+ '/+/CheckinFolder=z3c',
+ basic='mgr:mgrpw',
+ form={'field.description': u'Some description',
+ 'field.archive_url': u'http://void',
+ 'field.icons': u'icon\nanother one',
+ 'UPDATE_SUBMIT': 'Add'})
+ self.assertEqual(response.getStatus(), 302)
+ z3c = self.getRootFolder()['z3c']
+ self.assertEqual(z3c.description, u'Some description')
+ self.assertEqual(z3c.archive_url, u'http://void')
+ self.assertEqual(z3c.icons, u'icon\nanother one')
+
+ def test_empty(self):
+ for view in self.container_views:
+ response = self.publish('/z3c/@@%s' % view)
+ self.assertEqual(response.getStatus(), 200)
+
+ def test_resources(self):
+ for resource in self.resources:
+ response = self.publish('/z3c/++resource++%s' % resource)
+ self.assertEqual(response.getStatus(), 200)
+
+ def test_add_checkin_message(self):
+ response = self.publish('/z3c/@@+',
+ basic='mgr:mgrpw',
+ form={'field.data': self.open('msg1.txt'),
+ 'UPDATE_SUBMIT': u'Submit'})
+ self.assertEqual(response.getStatus(), 200)
+ self.assertEqual(response.getBody().count("Checkin message"), 1)
+
+ response = self.publish('/z3c/+/CheckinMessage',
+ basic='mgr:mgrpw',
+ form={'field.data': self.open('msg1.txt'),
+ 'UPDATE_SUBMIT': u'Submit'})
+ self.assertEqual(response.getStatus(), 302)
+
+ for view in self.container_views:
+ response = self.publish('/z3c/@@%s' % view)
+ self.assertEqual(response.getStatus(), 200)
+ for view in self.message_views:
+ response = self.publish('/z3c/msg1 at example.org/@@%s' % view)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish('/z3c/@@checkins.rss')
+ self.assertEqual(response.getStatus(), 200)
+ body = response.getBody()
+ xml_directive = '<?xml '
+ self.assert_(body.startswith(xml_directive),
+ 'checkins.rss has no XML directive:\n%s...' % body[:70])
+ # Make sure the XML directive is not repeated
+ self.assert_(body[len(xml_directive):].find(xml_directive) == -1,
+ '%s appears more than once in checkins.rss' % xml_directive)
+
+ def test_add_simple_message(self):
+ response = self.publish('/z3c/+/CheckinMessage',
+ basic='mgr:mgrpw',
+ form={'field.data': self.open('msg2.txt'),
+ 'UPDATE_SUBMIT': u'Submit'})
+ self.assertEqual(response.getStatus(), 302)
+
+ for view in self.container_views:
+ response = self.publish('/z3c/@@%s' % view)
+ self.assertEqual(response.getStatus(), 200)
+ for view in self.message_views:
+ response = self.publish('/z3c/msg2 at example.org/@@%s' % view)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish('/z3c/@@checkins.rss')
+ self.assertEqual(response.getStatus(), 200)
+ body = response.getBody()
+ xml_directive = '<?xml '
+ self.assert_(body.startswith(xml_directive),
+ 'checkins.rss has no XML directive:\n%s...' % body[:70])
+ # Make sure the XML directive is not repeated
+ self.assert_(body[len(xml_directive):].find(xml_directive) == -1,
+ '%s appears more than once in checkins.rss' % xml_directive)
+
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestCheckins))
+ return suite
+
+
+if __name__ == '__main__':
+ unittest.main()
Property changes on: Zope3/trunk/src/z3checkins/ftests/test_z3checkins.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/interfaces.py
===================================================================
--- Zope3/trunk/src/z3checkins/interfaces.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/interfaces.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,110 @@
+"""
+Interfaces for the z3checkins product.
+
+$Id: interfaces.py,v 1.14 2004/05/15 13:23:57 gintautasm Exp $
+"""
+
+from zope.interface import Interface, Attribute
+from zope.app.folder.interfaces import IFolder
+from zope.app.container.interfaces import IContainer, IContained
+from zope.app.container.constraints import ContainerTypesConstraint
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.schema import Field, Text, TextLine
+
+
+class IMessageUpload(Interface):
+ pass
+
+
+class IMessage(IMessageUpload):
+ """Mail message."""
+
+ message_id = Attribute("Unique message ID")
+ author_name = Attribute("Author's real name")
+ author_email = Attribute("Author's email address")
+ subject = Attribute("Subject line of the message")
+ date = Attribute("Date and time of the message")
+ body = Attribute("Body of the message")
+ full_text = Attribute("Full message text (headers and body)")
+
+
+class ICheckinMessage(IMessage):
+ """Checkin message."""
+
+ directory = Attribute("Directory that was updated")
+ branch = Attribute("Branch tag if this was commited to a branch")
+ log_message = Attribute("Checkin log message")
+ # Maybe added_files, modified_files, removed_files listing files and their
+ # revisions
+
+
+class IBookmark(Interface):
+ """Bookmark placed between messages."""
+
+
+class FormatError(Exception):
+ """Ill-formed message exception"""
+
+
+class IMessageParser(Interface):
+ """Parser for RFC-822 checkin messages"""
+
+ def parse(input):
+ """Parses an RFC-822 format message from a 'input' (which can be a
+ string or a file-like object) and returns an IMessage.
+
+ If the message is a checkin message, returns an ICheckinMessage.
+
+ May raise a FormatError if the message is ill-formed.
+ """
+
+
+class ICheckinFolderSchema(Interface):
+ """Checkin folder properties"""
+
+ description = TextLine(title=u"RSS view description",
+ required=False)
+ archive_url = TextLine(title=u"URL of mailing list archive",
+ required=False)
+ icons = Text(title=u"Icon definitions", required=False)
+
+
+class ICheckinFolder(IFolder, ICheckinFolderSchema):
+ """A marker interface for the checkins folder."""
+
+ def __setitem__(name, object):
+ """Add a message"""
+
+ __setitem__.precondition = ItemTypePrecondition(IMessageUpload)
+
+
+class IMessageContained(IContained):
+ """A contained message."""
+
+ __parent__ = Field(constraint=ContainerTypesConstraint(ICheckinFolder))
+
+
+class IMessageArchive(Interface):
+ """A chronologically ordered sequence of messages.
+
+ Implements the Python sequence procotol.
+ """
+
+ def __len__():
+ """Returns the number of messages in the archive."""
+
+ def __getitem__(index):
+ """Returns a given message."""
+
+ def __getslice__(start, stop):
+ """Returns a range of messages."""
+
+ def __iter__():
+ """Returns an iterator."""
+
+ def index(message):
+ """Returns the index of a given message.
+
+ Raises ValueError if message is not in the archive.
+ """
+
Property changes on: Zope3/trunk/src/z3checkins/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/message.png
===================================================================
(Binary files differ)
Property changes on: Zope3/trunk/src/z3checkins/message.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: Zope3/trunk/src/z3checkins/message.pt
===================================================================
--- Zope3/trunk/src/z3checkins/message.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/message.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,52 @@
+<html>
+<head tal:define="next view/next; prev view/previous; first view/first; last view/last">
+ <title tal:content="string: ${context/author_name} - ${context/subject}" />
+ <link rel="next" tal:condition="next"
+ tal:attributes="href next/@@absolute_url" />
+ <link rel="previous" tal:condition="prev"
+ tal:attributes="href prev/@@absolute_url" />
+ <link rel="first" tal:condition="first"
+ tal:attributes="href first/@@absolute_url" />
+ <link rel="last" tal:condition="last"
+ tal:attributes="href last/@@absolute_url" />
+ <style type="text/css">
+ body { color: black; background: white; }
+ .headers { margin-bottom: 2em; }
+ .headers p { margin: 0ex; text-indent: -6em; padding-left: 6em;
+ font-family: monospace; }
+ .header { font-weight: bold; color: blue; }
+ div.body > pre { margin: 0ex; }
+ div.log { border: 1px solid gray; padding: 0.5ex;
+ margin-top: 0.5ex; margin-bottom: 2em; }
+ div.log p { margin: 0ex; text-indent: -4em; padding-left: 4em;
+ font-family: monospace; }
+ .file { background: #ddd; }
+ .oldfile { background: #ddd; color: #65c; }
+ .newfile { background: #ddd; color: #182; }
+ .chunk { background: #eee; color: #a22; }
+ .old { background: #e3e0ff; color: red; }
+ .new { background: #e0ffe6; color: green; }
+ .signature { color: gray; }
+ .trail { color: gray; }
+ .tab { color: gray; }
+ img.icon { float: right; padding: 0ex; margin: 2px; border: none; }
+ </style>
+</head>
+<body>
+<img class="icon"
+ tal:define="icon view/icon"
+ tal:attributes="src icon/src; alt icon/alt; title icon/title" />
+<img tal:condition="context/branch | nothing"
+ class="icon" src="++resource++branch.png" alt="Branch"
+ tal:attributes="title string:Branch: ${context/branch}"/>
+<div class="headers">
+<p><span class="header">From:</span>
+ <span class="value" tal:content="context/author_name" />
+ <<span class="value" tal:content="context/author_email" />>
+</p>
+<p><span class="header">Date:</span> <span class="value" tal:content="context/date/@@rfc822" /></p>
+<p><span class="header">Subject:</span> <span class="value" tal:content="context/subject" /></p>
+</div>
+<div class="body" tal:content="structure view/body" />
+</body>
+</html>
Property changes on: Zope3/trunk/src/z3checkins/message.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/message.py
===================================================================
--- Zope3/trunk/src/z3checkins/message.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/message.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,747 @@
+"""
+Python code for z3checkins product.
+
+# This module could be split into three: timeutils.py, message.py and views.py
+# but it is small enough IMHO.
+
+$Id: message.py,v 1.40 2004/05/14 19:56:05 gintautasm Exp $
+"""
+
+import re
+import email
+import email.Utils
+import mailbox
+import time
+from StringIO import StringIO
+from datetime import datetime, tzinfo, timedelta
+
+from persistence import Persistent
+from zope.app.form import CustomWidgetFactory
+from zope.app.form.browser import FileWidget
+from zope.app.container.interfaces import IReadContainer
+from zope.app.datetimeutils import parseDatetimetz, DateTimeError
+from zope.app.dublincore.interfaces import IZopeDublinCore
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.component import getUtility, getAdapter, queryAdapter
+from zope.component import getView
+from zope.exceptions import DuplicationError
+from zope.interface import implements
+from zope.proxy import removeAllProxies
+from zope.app.publisher.browser import BrowserView
+
+from interfaces import IMessage, ICheckinMessage, IMessageContained
+from interfaces import IMessageUpload, IBookmark
+from interfaces import IMessageParser, IMessageArchive
+from interfaces import FormatError
+
+__metaclass__ = type
+
+#
+# Date/time utils
+#
+
+class FixedTimezone(tzinfo):
+ """Timezone with a fixed UTC offset"""
+
+ def __init__(self, offset=None):
+ """Creates a timezone with a given UTC offset (minutes east of UTC)."""
+ self._offset = offset
+
+ def tzname(self, dt):
+ if self._offset >= 0:
+ sign = '+'
+ h, m = divmod(self._offset, 60)
+ else:
+ sign = '-'
+ h, m = divmod(-self._offset, 60)
+ return '%c%02d%02d' % (sign, h, m)
+
+ def utcoffset(self, dt):
+ return timedelta(minutes=self._offset)
+
+ def dst(self, dt):
+ return timedelta(0)
+
+
+class RFCDateTimeFormatter:
+ """RFC822 view for datetime objects."""
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __str__(self):
+ """Renders datetime objects in RFC822 format."""
+ return self.context.strftime("%a, %d %b %Y %H:%M:%S %z")
+
+ __call__ = __str__
+
+
+class ISODateTimeFormatter:
+ """ISO 8601 view for datetime objects."""
+
+ if time.localtime()[-1]:
+ userstz = FixedTimezone(-time.altzone / 60)
+ else:
+ userstz = FixedTimezone(-time.timezone / 60)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __str__(self):
+ """Renders datetime objects as "YYYY-MM-DD hh:mm" in the local time
+ zone."""
+ return self.context.astimezone(self.userstz).strftime("%Y-%m-%d %H:%M")
+
+ __call__ = __str__
+
+
+#
+# Checkin message content object
+#
+
+def find_body_start(full_text):
+ """Find the body of an RFC-822 message and return its index in full_text."""
+ pos1 = full_text.find('\n\n')
+ pos2 = full_text.find('\r\n\r\n')
+ if pos1 == -1:
+ pos1 = len(full_text)
+ else:
+ pos1 += 2
+ if pos2 == -1:
+ pos2 = len(full_text)
+ else:
+ pos2 += 4
+ return min(pos1, pos2)
+
+
+class Message(Persistent):
+ """Persistent email message."""
+
+ implements(IMessage, IMessageContained)
+
+ __parent__ = __name__ = None
+
+ def __init__(self, message_id=None, author_name=None,
+ author_email=None, subject=None, date=None,
+ full_text=None):
+ self.message_id = message_id
+ self.author_name = author_name
+ self.author_email = author_email
+ self.subject = subject
+ self.date = date
+ self.full_text = full_text
+
+ def _getBody(self):
+ if self.full_text is None:
+ return None
+ else:
+ return self.full_text[find_body_start(self.full_text):]
+
+ body = property(_getBody)
+
+ def __eq__(self, other):
+ """Messages with the same message_id compare identical."""
+ if not IMessage.providedBy(other):
+ return False
+ return self.message_id == other.message_id
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+class CheckinMessage(Message):
+ """Persistent checkin message."""
+
+ implements(ICheckinMessage)
+
+ def __init__(self, message_id=None, author_name=None,
+ author_email=None, subject=None, date=None, full_text=None,
+ directory=None, log_message=None, branch=None):
+ super(CheckinMessage, self).__init__(message_id=message_id,
+ author_name=author_name, author_email=author_email,
+ subject=subject, date=date, full_text=full_text)
+ self.directory = directory
+ self.log_message = log_message
+ self.branch = branch
+
+
+class CheckinMessageParser:
+ """Parser for RFC822 mail messages."""
+
+ implements(IMessageParser)
+
+ def parse(self, input):
+ """See IMessageParser."""
+
+ if not hasattr(input, 'readline'):
+ full_text = str(input)
+ elif hasattr(input, 'seek') and hasattr(input, 'tell'):
+ old_pos = input.tell()
+ full_text = input.read()
+ input.seek(old_pos)
+ else:
+ full_text = input.read()
+
+ m = email.message_from_string(full_text)
+ subject = m.get('Subject', '').replace('\n', '')
+ message_id = m.get('Message-Id', None)
+ if message_id is None:
+ raise FormatError("Message does not have a message id")
+ if message_id[0] == "<" and message_id[-1] == ">":
+ message_id = message_id[1:-1] # strip angle brackets
+ author = m.get('From', '')
+ author_name, author_email = email.Utils.parseaddr(author)
+ date = m.get('Date', '')
+
+ # Fix incorrect timezones (+XX:XX instead of RFC-822 mandated +XXXX)
+ if date[-3] == ':':
+ date = date[:-3] + date[-2:]
+
+ (year, month, day, hours, minutes, seconds,
+ weekday, yearday, dst, tzoffset) = email.Utils.parsedate_tz(date)
+
+ # XXX a workaround to deal with messages that don't specify a timezone
+ if tzoffset is None:
+ tzoffset = 0
+
+ date = datetime(year, month, day, hours, minutes, seconds,
+ tzinfo=FixedTimezone(tzoffset / 60))
+
+ checkin_info = self.tryToParseCheckinMessage(subject, m)
+ if checkin_info is not None:
+ directory, log_message, branch = checkin_info
+ return CheckinMessage(message_id=message_id,
+ author_name=author_name,
+ author_email=author_email, subject=subject,
+ date=date, full_text=full_text,
+ directory=directory, log_message=log_message,
+ branch=branch)
+
+ return Message(message_id=message_id,
+ author_name=author_name,
+ author_email=author_email, subject=subject,
+ date=date, full_text=full_text)
+
+ def tryToParseCheckinMessage(self, subject, msg):
+ """Detect and parse CVS/Subversion checkin messages.
+
+ Returns a tuple (directory, log_message, branch) for checkin
+ messages, and None if the message is not a checkin message.
+ """
+
+ if subject.startswith("Re:"):
+ return None
+
+ if "CVS:" in subject:
+ parts = subject.split("CVS: ", 1)
+ if len(parts) < 2:
+ return None
+ subject = parts[1]
+ directory = subject.split(' - ')[0]
+ elif "rev " in subject:
+ parts = subject.split(' - ')
+ if len(parts) < 2:
+ return None
+ directory = parts[1]
+ else:
+ return None
+
+ body_lines = msg.get_payload().splitlines()
+ try:
+ log_message, branch = self.extract_log(body_lines)
+ except FormatError:
+ return None
+
+ return directory, log_message, branch
+
+ def extract_log(self, lines):
+ log_message = []
+ branch = None
+ in_log_msg = False
+ for line in lines:
+ if in_log_msg:
+ if (line.startswith('=== ')
+ or line.startswith("Added:")
+ or line.startswith("Modified:")
+ or line.startswith("Removed:")
+ or line.startswith("Deleted:")
+ or line.startswith("Property changes on:")
+ or line == "Status:"
+ ):
+ break
+ else:
+ log_message.append(line)
+ else:
+ if (line.lower().startswith('log message:') or
+ line.startswith("Log:")):
+ in_log_msg = True
+ elif line.startswith(' Tag: '):
+ branch = line[len(' Tag: '):].strip()
+ if not in_log_msg:
+ raise FormatError("Could not find log message")
+ return "\n".join(log_message).strip(), branch
+
+
+class MessageContainerAdapter:
+ """Adapts a container to a message archive."""
+
+ implements(IMessageArchive)
+ __used_for__ = IReadContainer
+
+ def __init__(self, context):
+ self.context = context
+ items = []
+ for key, item in self.context.items():
+ if IMessage.providedBy(item):
+ items.append((item.date, key, item))
+ items.sort()
+ self.messages = []
+ for date, key, item in items:
+ # XXX is this nice?
+ #item.__parent__ = self.context
+ #item.__name__ = key
+ self.messages.append(item)
+
+ def __len__(self):
+ return len(self.messages)
+
+ def __getitem__(self, index):
+ return self.messages[index]
+
+ def __getslice__(self, start, stop):
+ return self.messages[start:stop]
+
+ def __iter__(self):
+ return iter(self.messages)
+
+ def index(self, message):
+ return self.messages.index(message)
+
+
+class Bookmark:
+
+ implements(IBookmark)
+
+
+#
+# Browser views
+#
+
+class MessageUpload:
+ """Adding view mixin for uploading checkin messages."""
+
+ implements(IMessageUpload, IMessageContained)
+ data_widget = CustomWidgetFactory(FileWidget)
+
+ def createAndAdd(self, data):
+ if data.has_key('data'): # XXX should we bark if no data is given?
+ msg_raw = data['data']
+ parser = getUtility(self.context, IMessageParser)
+ if msg_raw.startswith("From "):
+ # detected an mbox file
+ mbox = StringIO(msg_raw)
+ messages = mailbox.PortableUnixMailbox(mbox,
+ factory=parser.parse)
+ for message in messages:
+ try:
+ self.add(message)
+ dc = queryAdapter(message, IZopeDublinCore)
+ if dc is not None:
+ # XXX should handle RFC-2047
+ dc.title = unicode(message.subject)
+ dc.created = message.date
+ except DuplicationError:
+ pass # leave the old mesage unchanged
+ else:
+ message = parser.parse(msg_raw)
+ self.add(message)
+
+
+class ContainerView:
+ """View mixin for locating checkin messages in a container."""
+
+ max_bookmarks = 5
+
+ def title(self):
+ """Returns the title of this archive.
+
+ Title is obtained from Dublin Core metadata of the folder. If it is
+ empty, "Zope 3 Checkins" is used.
+ """
+ dc = queryAdapter(self.context, IZopeDublinCore)
+ if dc is not None:
+ title = dc.title
+ else:
+ title = ''
+ return title or "Zope 3 Checkins"
+
+ def description(self):
+ """Returns the description of this archive.
+ """
+ return self.context.description
+
+ def archive_url(self):
+ """Returns the URL for mailing list archives.
+ """
+ return self.context.archive_url
+
+ def bookmarks(self):
+ """Returns a list of bookmarks from a cookie. Each bookmark is
+ expressed as a datetime object.
+ """
+ bookmarks = []
+ cookie = self.request.get('bookmarks', '')
+ for item in cookie.split():
+ try:
+ bookmarks.append(parseDatetimetz(item))
+ except (DateTimeError, IndexError):
+ pass
+ return bookmarks
+
+ def placeBookmark(self):
+ """Place a new bookmark after the latest checkin message in a
+ cookie."""
+ if int(self.request.get('start', 0)) > 0:
+ return # The user can't see the newest checkins
+ if not hasattr(self, '_archive'):
+ self._archive = getAdapter(self.context, IMessageArchive)
+ if not self._archive:
+ return # No messages -- no bookmarks
+ bookmarks = self.bookmarks()
+ bookmarks.sort()
+ # Do not insert a bookmark if there were no checkins since the last
+ # bookmark
+ if (bookmarks and bookmarks[-1] >= self._archive[-1].date):
+ return
+ bookmarks.append(self._archive[-1].date)
+ if len(bookmarks) > self.max_bookmarks:
+ del bookmarks[:-self.max_bookmarks]
+ cookie = " ".join([dt.isoformat() for dt in bookmarks])
+ self.request.response.setCookie('bookmarks', cookie,
+ max_age=365*24*60*60) # 1 year
+
+ def checkins(self, start=None, size=None):
+ """Returns a list of the last 'size' checkin messages in
+ self.context, newest first, skipping the first 'start' messages.
+ """
+ if start is None: start = int(self.request.get('start', 0))
+ if size is None: size = int(self.request.get('size', 20))
+ if not hasattr(self, '_archive'):
+ self._archive = getAdapter(self.context, IMessageArchive)
+ idx = len(self._archive) - start
+ items = self._archive[max(0, idx-size):idx]
+ items = removeAllProxies(items)
+ # insert bookmarks
+ def bookmarkBetween(msg1, msg2, bookmarks=self.bookmarks()):
+ for b in bookmarks:
+ if msg1.date <= b < msg2.date:
+ return True
+ return False
+ n = 1
+ while n < len(items):
+ if bookmarkBetween(items[n-1], items[n]):
+ items.insert(n, Bookmark())
+ n += 2
+ else:
+ n += 1
+ # insert bookmarks before the first/after the last batch item
+ if items:
+ before = self._archive[max(0, idx-size-1):max(0, idx-size)]
+ if before and bookmarkBetween(before[0], items[0]):
+ items.insert(0, Bookmark())
+ after = self._archive[idx:idx+1]
+ if after and bookmarkBetween(items[-1], after[0]):
+ items.insert(len(items), Bookmark())
+ # reverse order to present newest checkins first
+ items.reverse()
+ return items
+
+ def renderCheckins(self, start=None, size=None):
+ """Returns a list of checkins rendered into HTML. See `checkins` for
+ description of parameters."""
+ html = []
+ previous_message = None
+ for item in self.checkins(start=start, size=size):
+ if ICheckinMessage.providedBy(item):
+ same_as_previous = item.log_message == previous_message
+ previous_message = item.log_message
+ else:
+ same_as_previous = None
+ view = getView(item, 'html', self.request)
+ output = view(same_as_previous=same_as_previous)
+ html.append(output)
+ return "".join(html)
+
+ def count(self):
+ """Returns the number of checkin messages in the archive."""
+ if not hasattr(self, '_archive'):
+ self._archive = getAdapter(self.context, IMessageArchive)
+ return len(self._archive)
+
+
+class MessageRSSView(BrowserView):
+ """View for messages.
+
+ Makes sure the page template is treated as XML.
+ """
+
+ index = ViewPageTemplateFile('rss_message.pt', content_type='text/xml')
+
+
+class MessageView:
+ """View mixin for messages."""
+
+ def _calc_index(self):
+ if not hasattr(self, '_archive'):
+ container = self.context.__parent__
+ self._archive = container and queryAdapter(container,
+ IMessageArchive)
+ if not self._archive:
+ self._index = None
+ elif not hasattr(self, '_index'):
+ self._index = self._archive.index(self.context)
+
+ def next(self):
+ """Returns the next message in archive."""
+ self._calc_index()
+ if self._index is not None and self._index < len(self._archive) - 1:
+ return self._archive[self._index + 1]
+ else:
+ return None
+
+ def previous(self):
+ """Returns the previous message in archive."""
+ self._calc_index()
+ if self._index is not None and self._index > 0:
+ return self._archive[self._index - 1]
+ else:
+ return None
+
+ def first(self):
+ """Returns the first message in archive."""
+ self._calc_index()
+ if self._archive:
+ return self._archive[0]
+ else:
+ return None
+
+ def last(self):
+ """Returns the last message in archive."""
+ self._calc_index()
+ if self._archive:
+ return self._archive[-1]
+ else:
+ return None
+
+ def icon(self):
+ """Returns a mapping describing an icon for this checkin. The mapping
+ contains 'src', 'alt' and 'title' attributes."""
+ return {'src': '++resource++message.png',
+ 'alt': 'Message',
+ 'title': 'Email message'}
+
+ def body(self):
+ """Colorizes message body."""
+
+ text = self.context.body.replace('\r', '')\
+ .replace('&', '&') \
+ .replace('<', '<') \
+ .replace('>', '>') \
+ .replace('"', '"')
+ # It would be nice to highlight quoted text here
+ return '<pre>%s</pre>' % text
+
+
+class CheckinMessageView(MessageView):
+ """View mixin for checkin messages."""
+
+ _subtrees = None
+ def subtrees(self):
+ """Returns a sequence of tuples (prefix, icon, alt, title).
+
+ (icon, alt, title) are the resource name, alt text and tooltip used
+ for any checkin messages that have directory starting with prefix.
+
+ This information is currently taken from Dublin Core metadata
+ description field, third paragraph. Every line in that paragraph
+ defines a subtree, with all fields separated by spaces or tabs.
+ """
+ if self._subtrees is not None:
+ return self._subtrees
+ self._subtrees = []
+ container = self.context.__parent__
+ description = container.description
+ if not description:
+ return self._subtrees
+ for line in description.splitlines():
+ items = line.split(None, 3)
+ if len(items) < 4:
+ continue
+ if items[0] == '*': # catch-all
+ items[0] = ''
+ self._subtrees.append(items)
+ return self._subtrees
+
+ def icon(self):
+ """Returns a mapping describing an icon for this checkin. The mapping
+ contains 'src', 'alt' and 'title' attributes."""
+ for prefix, icon, alt, title in self.subtrees():
+ if self.context.directory.startswith(prefix):
+ return {'src': '++resource++%s' % icon,
+ 'alt': alt,
+ 'title': title}
+ return {'src': '++resource++product.png',
+ 'alt': 'Checkin',
+ 'title': 'Checkin'}
+
+ def body(self):
+ """Colorizes checkin message body."""
+
+ text = self.context.body.replace('\r', '')\
+ .replace('&', '&') \
+ .replace('<', '<') \
+ .replace('>', '>') \
+ .replace('"', '"')
+
+ text = re.sub(r'(https?://.+?)'
+ r'($|[ \t\r\n)]|>|"|[.,](?:$|[ \t\r\n]))',
+ r'<a href="\1">\1</a>\2', text)
+
+ log_idx = text.find('\nLog message:\n')
+ if log_idx == -1:
+ log_idx = text.find('\nLog Message:\n')
+ if log_idx != -1:
+ log_idx += len('\nLog message:\n')
+ if log_idx == -1:
+ log_idx = text.find('\nLog:\n')
+ if log_idx != -1:
+ log_idx += len('\nLog:\n')
+ if log_idx == -1:
+ return '<pre>%s</pre>' % text
+
+ sig_idx = text.rfind(
+ '\n_______________________________________________')
+ if sig_idx == -1:
+ sig_idx = len(text)
+
+ diff_idx = text.find('\n===')
+ if diff_idx == -1:
+ diff_idx = sig_idx
+
+ status_idx = text.find('\nStatus:\n')
+ if status_idx == -1:
+ if text[diff_idx:diff_idx+5] == "\n====":
+ # Subversion
+ status_idx = text.rfind('\n', 0, diff_idx)
+ else:
+ status_idx = diff_idx
+
+ propchange_idx = text.find('\nProperty changes on:')
+ if propchange_idx != -1 and propchange_idx < status_idx:
+ status_idx = propchange_idx
+
+ assert log_idx <= status_idx <= diff_idx <= sig_idx
+
+ intro = text[:log_idx]
+ log = text[log_idx:status_idx].strip()
+ import_status = text[status_idx:diff_idx]
+ diff = text[diff_idx:sig_idx]
+ sig = text[sig_idx:]
+
+ def empty2nbsp(s):
+ if not s:
+ return ' '
+ n = 0
+ while n < len(s) and s[n] == ' ':
+ n += 1
+ if n:
+ return ' ' * n + s[n:]
+ else:
+ return s
+ log = '<p>%s</p>' % '</p>\n<p>'.join(map(empty2nbsp, log.splitlines()))
+
+ if import_status is None:
+ import_status = ''
+
+ if diff is None:
+ diff = ''
+
+ diff = "\n".join(map(self.mark_whitespace, diff.splitlines()))
+
+ def colorize(style):
+ return r'<div class="%s">\1</div>' % style
+
+ # Unified diff
+ diff = re.sub(r'(?m)^(===.*)$', colorize("file"), diff)
+ diff = re.sub(r'(?m)^(---.*)$', colorize("oldfile"), diff)
+ diff = re.sub(r'(?m)^(\+\+\+.*)$', colorize("newfile"), diff)
+ diff = re.sub(r'(?m)^(@@.*)$', colorize("chunk"), diff)
+ diff = re.sub(r'(?m)^(-.*)$', colorize("old"), diff)
+ diff = re.sub(r'(?m)^(\+.*)$', colorize("new"), diff)
+
+ # Postprocess for Mozilla
+ diff = re.sub('</div>\n', '\n</div>', diff)
+
+ if sig:
+ sig = '<div class="signature">%s</div>' % sig
+
+ text = '<pre>%s</pre><div class="log">%s</div><pre>%s%s%s</pre>' \
+ % (intro, log, import_status, diff, sig)
+ # XXX: find out the actual encoding instead of assuming UTF-8
+ return unicode(text, 'UTF-8', 'replace')
+
+ def mark_whitespace(self, line, tab=('>', '-'), trail='.'):
+ """Mark whitespace in diff lines.
+
+ Suggested values for tab: ('>', '-'), ('»', ' '),
+ ('»', '‐')
+
+ Suggested values for trail: '.', '␣'
+ """
+ if line == ' ' or (not line.endswith(' ') and '\t' not in line):
+ return line
+ m = re.search('\s+\Z', line)
+ if m:
+ n = m.start()
+ if n == 0:
+ n = 1 # don't highlight the first space in a diff
+ line = '%s<span class="trail">%s</span>' % (line[:n],
+ line[n:].replace(' ', trail))
+ if '\t' in line:
+ NORMAL, TAG, ENTITY = 0, 1, 2
+ idx = col = 0
+ mode = NORMAL
+ tabs = []
+ for c in line[1:]: # ignore first space in a diff
+ idx += 1
+ if mode == TAG:
+ if c == '>':
+ mode = NORMAL
+ elif mode == ENTITY:
+ if c == ';':
+ col += 1
+ mode = NORMAL
+ else:
+ if c == '<':
+ mode = TAG
+ elif c == '&':
+ mode = ENTITY
+ elif c == '\t':
+ width = 8 - (col % 8)
+ tabs.append((idx, width))
+ col += width
+ else:
+ col += 1
+ if tabs:
+ parts = []
+ last = 0
+ for idx, width in tabs:
+ parts.append(line[last:idx])
+ parts.append('<span class="tab">%s%s</span>'
+ % (tab[0], tab[1] * (width - 1)))
+ last = idx + 1
+ parts.append(line[last:])
+ line = "".join(parts)
+ return line
Property changes on: Zope3/trunk/src/z3checkins/message.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/message_part.pt
===================================================================
--- Zope3/trunk/src/z3checkins/message_part.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/message_part.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,20 @@
+<div class="message">
+<a class="title" tal:attributes="href context/@@absolute_url">
+ <img class="icon"
+ tal:define="icon view/icon"
+ tal:attributes="src icon/src; alt icon/alt; title icon/title" />
+ <img tal:condition="context/branch | nothing"
+ class="icon" src="++resource++branch.png" alt="Branch"
+ tal:attributes="title string:Branch: ${context/branch}"/>
+ <span class="date" tal:content="context/date/@@isodatetime" />:
+ <span class="author" tal:content="context/author_name" />
+ - <span class="subject" tal:content="context/subject" />
+</a>
+<tal:if condition="context/log_message | nothing">
+<div class="same description" tal:condition="options/same_as_previous">
+(Same as above)
+</div>
+<div class="description" tal:condition="not:options/same_as_previous"
+ tal:content="context/log_message" />
+</tal:if>
+</div>
Property changes on: Zope3/trunk/src/z3checkins/message_part.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/message_plain.pt
===================================================================
--- Zope3/trunk/src/z3checkins/message_plain.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/message_plain.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1 @@
+<tal:block tal:replace="structure context/full_text" />
Property changes on: Zope3/trunk/src/z3checkins/message_plain.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/message_sidebar.pt
===================================================================
--- Zope3/trunk/src/z3checkins/message_sidebar.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/message_sidebar.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,20 @@
+<div class="message">
+<a class="title" tal:attributes="href context/@@absolute_url" target="_content">
+ <img class="icon"
+ tal:define="icon view/icon"
+ tal:attributes="src icon/src; alt icon/alt; title icon/title" />
+ <img tal:condition="context/branch | nothing"
+ class="icon" src="++resource++branch.png" alt="Branch"
+ tal:attributes="title string:Branch: ${context/branch}"/>
+ <span class="date" tal:content="context/date/@@isodatetime" />:
+ <span class="author" tal:content="context/author_name" />
+ - <span class="subject" tal:content="context/subject" />
+</a>
+<tal:if condition="context/log_message | nothing">
+<div class="same description" tal:condition="options/same_as_previous">
+(Same as above)
+</div>
+<div class="description" tal:condition="not:options/same_as_previous"
+ tal:content="context/log_message" />
+</tal:if>
+</div>
Property changes on: Zope3/trunk/src/z3checkins/message_sidebar.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/product.png
===================================================================
(Binary files differ)
Property changes on: Zope3/trunk/src/z3checkins/product.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: Zope3/trunk/src/z3checkins/rss_container.pt
===================================================================
--- Zope3/trunk/src/z3checkins/rss_container.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/rss_container.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<rss version="2.0" xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <channel tal:define="webmaster context/webmaster_email | nothing">
+ <title tal:content="view/title">Zope 3 Checkins</title>
+ <link tal:content="string:${context/@@absolute_url}"></link>
+ <description tal:content="view/description" tal:condition="view/description">Latest Zope 3 Checkins</description>
+ <language>en-us</language>
+ <docs>http://backend.userland.com/rss</docs>
+ <generator>z3checkins</generator>
+ <webMaster tal:condition="webmaster" tal:content="webmaster" />
+<item tal:repeat="item view/checkins" tal:replace="structure item/@@rss|nothing" />
+ </channel>
+</rss>
Property changes on: Zope3/trunk/src/z3checkins/rss_container.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/rss_message.pt
===================================================================
--- Zope3/trunk/src/z3checkins/rss_message.pt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/rss_message.pt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,8 @@
+<item xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <title tal:content="string:${context/author_name} - ${context/subject}" />
+ <link tal:content="context/@@absolute_url" />
+ <description tal:content="context/log_message | nothing" />
+ <guid tal:content="context/@@absolute_url" />
+ <author><tal:block tal:replace="context/author_name" /> <<tal:block tal:replace="context/author_email" />></author>
+ <pubDate tal:content="context/date/@@rfc822" />
+</item>
Property changes on: Zope3/trunk/src/z3checkins/rss_message.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/__init__.py
===================================================================
--- Zope3/trunk/src/z3checkins/tests/__init__.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/__init__.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,5 @@
+"""
+Unit tests for z3checkins.
+
+$Id: __init__.py,v 1.2 2003/08/01 09:43:23 mgedmin Exp $
+"""
Property changes on: Zope3/trunk/src/z3checkins/tests/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/mbox.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/mbox.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/mbox.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,193 @@
+From steve at cat-box.net Sat Jun 1 00:31:53 2002
+From: steve at cat-box.net (Steve Alexander)
+Date: Fri, 31 May 2002 19:31:53 -0400
+Subject: [Zope-Checkins] CVS: Zope3/lib/python/Zope/App/Security - PermissionRegistry.py:1.1.2.16.14.1
+Message-ID: <200205312331.g4VNVr927566 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope3/lib/python/Zope/App/Security
+In directory cvs.zope.org:/tmp/cvs-serv27552
+
+Modified Files:
+ Tag: Zope3InWonderland-branch
+ PermissionRegistry.py
+Log Message:
+Permission ids must not start with a dot.
+
+
+=== Zope3/lib/python/Zope/App/Security/PermissionRegistry.py 1.1.2.16 => 1.1.2.16.14.1 ===
+ """Define a new permission object, register, and return it.
+
+- name is the permission name, must be globally unique
++ permission is the permission name, must be globally unique
+
+ title is the permission title, human readable.
+
+ description (optional) is human readable
+ """
++ if permission.startswith('.'):
++ raise ValueError("permissions must not start with a '.'")
+ return self.register(permission, title, description)
+
+ def definedPermission(self, permission_id):
+
+
+
+
+From steve at cat-box.net Sat Jun 1 00:31:53 2002
+From: steve at cat-box.net (Steve Alexander)
+Date: Fri, 31 May 2002 19:31:53 -0400
+Subject: [Zope-Checkins] CVS: Zope3/lib/python/Zope/App/Security/tests - testPermissionRegistry.py:1.1.2.13.14.1
+Message-ID: <200205312331.g4VNVr027568 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope3/lib/python/Zope/App/Security/tests
+In directory cvs.zope.org:/tmp/cvs-serv27552/tests
+
+Modified Files:
+ Tag: Zope3InWonderland-branch
+ testPermissionRegistry.py
+Log Message:
+Permission ids must not start with a dot.
+
+
+=== Zope3/lib/python/Zope/App/Security/tests/testPermissionRegistry.py 1.1.2.13 => 1.1.2.13.14.1 ===
+ self.assertEqual(None, permissionRegistry.getPermission('Foo'))
+ self.failIf(permissionRegistry.definedPermission('Foo'))
++
++ def testPermissionStartsWithDot(self):
++ self.assertRaises(ValueError, permissionRegistry.definePermission,
++ '.Foo', 'dot foo title')
+
+ def testPermissionIsAnIPermission(self):
+ permissionRegistry.definePermission('Foo', 'foo title')
+@@ -56,7 +60,7 @@
+ permission = permissionRegistry.getPermission('Foo')
+ eq(permission.getTitle(), 'Foo-able')
+ eq(permission.getDescription(), 'A foo-worthy permission')
+-
++
+
+ def test_suite():
+ loader=unittest.TestLoader()
+
+
+
+
+From tim.one at comcast.net Sat Jun 1 00:46:53 2002
+From: tim.one at comcast.net (Tim Peters)
+Date: Fri, 31 May 2002 19:46:53 -0400
+Subject: [Zope-Checkins] CVS: Zope/lib/python/BTrees/tests - testSetOps.py:1.3
+Message-ID: <200205312346.g4VNkr731412 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope/lib/python/BTrees/tests
+In directory cvs.zope.org:/tmp/cvs-serv30763/tests
+
+Modified Files:
+ testSetOps.py
+Log Message:
+testBigInput(): This spent almost all of its time building an IISet
+from a sequence of ints in reverse-sorted order (a quadratic-time
+proposition). That doesn't test anything interesting in context, though.
+So fiddled it to do a larger input, but it runs much faster now.
+
+
+=== Zope/lib/python/BTrees/tests/testSetOps.py 1.2 => 1.3 ===
+
+ def testBigInput(self):
+- input = IISet(range(50000))
+- reversed = range(50000)
+- reversed.reverse()
+- reversed = IISet(reversed)
+- output = multiunion([input, reversed] * 5)
+- self.assertEqual(len(output), 50000)
+- self.assertEqual(list(output), range(50000))
++ N = 100000
++ input = IISet(range(N))
++ output = multiunion([input] * 10)
++ self.assertEqual(len(output), N)
++ self.assertEqual(output.minKey(), 0)
++ self.assertEqual(output.maxKey(), N-1)
++ self.assertEqual(list(output), range(N))
+
+ def testLotsOfLittleOnes(self):
+ from random import shuffle
+
+
+
+
+From tim.one at comcast.net Sat Jun 1 01:49:19 2002
+From: tim.one at comcast.net (Tim Peters)
+Date: Fri, 31 May 2002 20:49:19 -0400
+Subject: [Zope-Checkins] CVS: Zope/lib/python/BTrees - SetOpTemplate.c:1.16
+Message-ID: <200206010049.g510nJR14723 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope/lib/python/BTrees
+In directory cvs.zope.org:/tmp/cvs-serv14639
+
+Modified Files:
+ SetOpTemplate.c
+Log Message:
+multiunion(): For an input that's IIBucket-based (IIBucket and IISet),
+this now copies the keys into the work area in one gulp via memcpy,
+instead of iterating over them one at a time. Yields a nice speedup when
+it applies (and it usually should apply!).
+
+
+=== Zope/lib/python/BTrees/SetOpTemplate.c 1.15 => 1.16 ===
+ set. At this point, we ignore the possibility of duplicates. */
+ for (i = 0; i < n; ++i) {
+- SetIteration setiter = {0, 0, 0};
+- int merge; /* dummy needed for initSetIteration */
+-
+ set = PySequence_GetItem(seq, i);
+ if (set == NULL)
+ goto Error;
+
+- /* XXX TODO: If set is a bucket, do a straight resize+memcpy instead.
+- */
+- if (initSetIteration(&setiter, set, 1, &merge) < 0)
+- goto Error;
+- if (setiter.next(&setiter) < 0)
+- goto Error;
+- while (setiter.position >= 0) {
+- if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0)
++ /* If set is a bucket, do a straight resize + memcpy. */
++ if (set->ob_type == (PyTypeObject*)&SetType ||
++ set->ob_type == (PyTypeObject*)&BucketType) {
++ const int setsize = SIZED(set)->len;
++ int size_desired = result->len + setsize;
++ /* If there are more to come, overallocate by 25% (arbitrary). */
++ if (i < n-1)
++ size_desired += size_desired >> 2;
++ if (size_desired && size_desired > result->size) {
++ if (Bucket_grow(result, size_desired, 1) < 0)
++ goto Error;
++ }
++ memcpy(result->keys + result->len,
++ BUCKET(set)->keys,
++ setsize * sizeof(KEY_TYPE));
++ result->len += setsize;
++ }
++ else {
++ /* No cheap way: iterate over set's elements one at a time. */
++ SetIteration setiter = {0, 0, 0};
++ int merge; /* dummy needed for initSetIteration */
++
++ if (initSetIteration(&setiter, set, 1, &merge) < 0)
+ goto Error;
+- COPY_KEY(result->keys[result->len], setiter.key);
+- ++result->len;
+- /* We know the key is an int, so no need to incref it. */
+ if (setiter.next(&setiter) < 0)
+ goto Error;
++ while (setiter.position >= 0) {
++ if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0)
++ goto Error;
++ COPY_KEY(result->keys[result->len], setiter.key);
++ ++result->len;
++ /* We know the key is an int, so no need to incref it. */
++ if (setiter.next(&setiter) < 0)
++ goto Error;
++ }
+ }
+ Py_DECREF(set);
+ set = NULL;
Property changes on: Zope3/trunk/src/z3checkins/tests/mbox.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/mbox_with_dupes.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/mbox_with_dupes.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/mbox_with_dupes.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,193 @@
+From steve at cat-box.net Sat Jun 1 00:31:53 2002
+From: steve at cat-box.net (Steve Alexander)
+Date: Fri, 31 May 2002 19:31:53 -0400
+Subject: [Zope-Checkins] CVS: Zope3/lib/python/Zope/App/Security - PermissionRegistry.py:1.1.2.16.14.1
+Message-ID: <200205312331.g4VNVr927566 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope3/lib/python/Zope/App/Security
+In directory cvs.zope.org:/tmp/cvs-serv27552
+
+Modified Files:
+ Tag: Zope3InWonderland-branch
+ PermissionRegistry.py
+Log Message:
+Permission ids must not start with a dot.
+
+
+=== Zope3/lib/python/Zope/App/Security/PermissionRegistry.py 1.1.2.16 => 1.1.2.16.14.1 ===
+ """Define a new permission object, register, and return it.
+
+- name is the permission name, must be globally unique
++ permission is the permission name, must be globally unique
+
+ title is the permission title, human readable.
+
+ description (optional) is human readable
+ """
++ if permission.startswith('.'):
++ raise ValueError("permissions must not start with a '.'")
+ return self.register(permission, title, description)
+
+ def definedPermission(self, permission_id):
+
+
+
+
+From steve at cat-box.net Sat Jun 1 00:31:53 2002
+From: steve at cat-box.net (Steve Alexander)
+Date: Fri, 31 May 2002 19:31:53 -0400
+Subject: [Zope-Checkins] CVS: Zope3/lib/python/Zope/App/Security/tests - testPermissionRegistry.py:1.1.2.13.14.1
+Message-ID: <200205312331.g4VNVr927566 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope3/lib/python/Zope/App/Security/tests
+In directory cvs.zope.org:/tmp/cvs-serv27552/tests
+
+Modified Files:
+ Tag: Zope3InWonderland-branch
+ testPermissionRegistry.py
+Log Message:
+Permission ids must not start with a dot.
+
+
+=== Zope3/lib/python/Zope/App/Security/tests/testPermissionRegistry.py 1.1.2.13 => 1.1.2.13.14.1 ===
+ self.assertEqual(None, permissionRegistry.getPermission('Foo'))
+ self.failIf(permissionRegistry.definedPermission('Foo'))
++
++ def testPermissionStartsWithDot(self):
++ self.assertRaises(ValueError, permissionRegistry.definePermission,
++ '.Foo', 'dot foo title')
+
+ def testPermissionIsAnIPermission(self):
+ permissionRegistry.definePermission('Foo', 'foo title')
+@@ -56,7 +60,7 @@
+ permission = permissionRegistry.getPermission('Foo')
+ eq(permission.getTitle(), 'Foo-able')
+ eq(permission.getDescription(), 'A foo-worthy permission')
+-
++
+
+ def test_suite():
+ loader=unittest.TestLoader()
+
+
+
+
+From tim.one at comcast.net Sat Jun 1 00:46:53 2002
+From: tim.one at comcast.net (Tim Peters)
+Date: Fri, 31 May 2002 19:46:53 -0400
+Subject: [Zope-Checkins] CVS: Zope/lib/python/BTrees/tests - testSetOps.py:1.3
+Message-ID: <200205312331.g4VNVr927566 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope/lib/python/BTrees/tests
+In directory cvs.zope.org:/tmp/cvs-serv30763/tests
+
+Modified Files:
+ testSetOps.py
+Log Message:
+testBigInput(): This spent almost all of its time building an IISet
+from a sequence of ints in reverse-sorted order (a quadratic-time
+proposition). That doesn't test anything interesting in context, though.
+So fiddled it to do a larger input, but it runs much faster now.
+
+
+=== Zope/lib/python/BTrees/tests/testSetOps.py 1.2 => 1.3 ===
+
+ def testBigInput(self):
+- input = IISet(range(50000))
+- reversed = range(50000)
+- reversed.reverse()
+- reversed = IISet(reversed)
+- output = multiunion([input, reversed] * 5)
+- self.assertEqual(len(output), 50000)
+- self.assertEqual(list(output), range(50000))
++ N = 100000
++ input = IISet(range(N))
++ output = multiunion([input] * 10)
++ self.assertEqual(len(output), N)
++ self.assertEqual(output.minKey(), 0)
++ self.assertEqual(output.maxKey(), N-1)
++ self.assertEqual(list(output), range(N))
+
+ def testLotsOfLittleOnes(self):
+ from random import shuffle
+
+
+
+
+From tim.one at comcast.net Sat Jun 1 01:49:19 2002
+From: tim.one at comcast.net (Tim Peters)
+Date: Fri, 31 May 2002 20:49:19 -0400
+Subject: [Zope-Checkins] CVS: Zope/lib/python/BTrees - SetOpTemplate.c:1.16
+Message-ID: <200206010049.g510nJR14723 at cvs.baymountain.com>
+
+Update of /cvs-repository/Zope/lib/python/BTrees
+In directory cvs.zope.org:/tmp/cvs-serv14639
+
+Modified Files:
+ SetOpTemplate.c
+Log Message:
+multiunion(): For an input that's IIBucket-based (IIBucket and IISet),
+this now copies the keys into the work area in one gulp via memcpy,
+instead of iterating over them one at a time. Yields a nice speedup when
+it applies (and it usually should apply!).
+
+
+=== Zope/lib/python/BTrees/SetOpTemplate.c 1.15 => 1.16 ===
+ set. At this point, we ignore the possibility of duplicates. */
+ for (i = 0; i < n; ++i) {
+- SetIteration setiter = {0, 0, 0};
+- int merge; /* dummy needed for initSetIteration */
+-
+ set = PySequence_GetItem(seq, i);
+ if (set == NULL)
+ goto Error;
+
+- /* XXX TODO: If set is a bucket, do a straight resize+memcpy instead.
+- */
+- if (initSetIteration(&setiter, set, 1, &merge) < 0)
+- goto Error;
+- if (setiter.next(&setiter) < 0)
+- goto Error;
+- while (setiter.position >= 0) {
+- if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0)
++ /* If set is a bucket, do a straight resize + memcpy. */
++ if (set->ob_type == (PyTypeObject*)&SetType ||
++ set->ob_type == (PyTypeObject*)&BucketType) {
++ const int setsize = SIZED(set)->len;
++ int size_desired = result->len + setsize;
++ /* If there are more to come, overallocate by 25% (arbitrary). */
++ if (i < n-1)
++ size_desired += size_desired >> 2;
++ if (size_desired && size_desired > result->size) {
++ if (Bucket_grow(result, size_desired, 1) < 0)
++ goto Error;
++ }
++ memcpy(result->keys + result->len,
++ BUCKET(set)->keys,
++ setsize * sizeof(KEY_TYPE));
++ result->len += setsize;
++ }
++ else {
++ /* No cheap way: iterate over set's elements one at a time. */
++ SetIteration setiter = {0, 0, 0};
++ int merge; /* dummy needed for initSetIteration */
++
++ if (initSetIteration(&setiter, set, 1, &merge) < 0)
+ goto Error;
+- COPY_KEY(result->keys[result->len], setiter.key);
+- ++result->len;
+- /* We know the key is an int, so no need to incref it. */
+ if (setiter.next(&setiter) < 0)
+ goto Error;
++ while (setiter.position >= 0) {
++ if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0)
++ goto Error;
++ COPY_KEY(result->keys[result->len], setiter.key);
++ ++result->len;
++ /* We know the key is an int, so no need to incref it. */
++ if (setiter.next(&setiter) < 0)
++ goto Error;
++ }
+ }
+ Py_DECREF(set);
+ set = NULL;
Property changes on: Zope3/trunk/src/z3checkins/tests/mbox_with_dupes.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/sample_import_msg.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/sample_import_msg.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/sample_import_msg.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,22 @@
+From: Foo Bar <foo.bar at bar.com>
+Subject: [Zope3-checkins] CVS: Zope3/src/foo/bar - Imported sources
+Date: Fri, 28 Mar 2003 11:58:05 +03:00
+Message-Id: <42 at bar.com>
+
+Update of /cvs-repository/Zope3/src/foo/bar
+In directory cvs.zope.org:/tmp/cvs-serv12345
+
+Log message:
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.
+
+Status:
+
+Vendor Tag:\tbarfulator
+Release Tags:\tyo-yo
+
+N Zope3/src/foo/bar/baz.py
+N Zope3/src/foo/bar/bar.pt
+
+No conflicts created by this import
+
Property changes on: Zope3/trunk/src/z3checkins/tests/sample_import_msg.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/sample_msg1.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/sample_msg1.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/sample_msg1.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,33 @@
+From: Foo Bar <foo.bar at bar.com>
+Subject: [Zope3-checkins] CVS: Zope3/src/foo/bar - baz.py:1.2
+Date: Fri, 28 Mar 2003 11:58:05 +03:00
+Message-Id: <42 at bar.com>
+
+Update of /cvs-repository/Zope3/src/foo/bar
+In directory cvs.zope.org:/tmp/cvs-serv12345
+
+Modified files:
+ baz.py
+Log message:
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.
+
+
+=== Zope3/src/foo/bar/baz.py 1.1 => 1.2 ===
+--- Zope3/src/foo/bar/baz.py:1.1 Tue Mar 25 15:21:29 2003
++++ Zope3/src/foo/bar/baz.py Fri Mar 28 11:57:34 2003
+@@@ -123,7 +123,7 @@
+ lalala
+ burbur
+ barbar
+-xxx
++yyy
+ www
+ quux
+ furumburum
+
+
+_______________________________________________
+Zope3-Checkins mailing list
+Zope3-Checkins at zope.org
+http://mail.zope.org/mailman/listinfo/zope3-checkins
Property changes on: Zope3/trunk/src/z3checkins/tests/sample_msg1.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/sample_msg2.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/sample_msg2.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/sample_msg2.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,21 @@
+From: Foo Bar <foo.bar at bar.com>
+Subject: [Zope3-checkins] CVS: Zope3/src/foo/bar - baz.py:1.2
+Date: Fri, 28 Mar 2003 11:58:05 +03:00
+Message-Id: <42 at bar.com>
+
+Update of /cvs-repository/Zope3/src/foo/bar
+In directory cvs.zope.org:/tmp/cvs-serv12345
+
+Added files:
+ Tag: foo-branch
+ baz.py
+Log Message:
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.
+
+
+=== Added File Zope3/src/foo/bar/baz.py 1.1 ===
+lalala
+burbur
+barbar
+
Property changes on: Zope3/trunk/src/z3checkins/tests/sample_msg2.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/simple_msg.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/simple_msg.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/simple_msg.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,7 @@
+From: John Doe <john at example.com>
+Subject: Something happened!
+Date: Wed, 29 Jul 2003 14:42:11 +0200
+Message-Id: <q$w$e$r$t$y at example.com>
+
+This is just a simple message.
+
Property changes on: Zope3/trunk/src/z3checkins/tests/simple_msg.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/svn_msg.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/svn_msg.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/svn_msg.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,37 @@
+To: checkins at lists.schooltool.org
+From: Albertas Agejevas <alga at pov.lt>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Message-Id: <20030908101551.6F900C32F at mail.pov.lt>
+Date: Mon, 8 Sep 2003 13:15:51 +0300 (EEST)
+Subject: [schooltool-checkins] rev 10 - trunk/schooltool
+
+Author: alga
+Date: 2003-09-08 13:15:50 +0300 (Mon, 08 Sep 2003)
+New Revision: 10
+
+Modified:
+ trunk/schooltool/README
+Log:
+Added a period.
+
+
+Modified: trunk/schooltool/README
+===================================================================
+--- trunk/schooltool/README 2003-09-05 16:54:07 UTC (rev 9)
++++ trunk/schooltool/README 2003-09-08 10:15:50 UTC (rev 10)
+@@ -1,7 +1,7 @@
+ SchoolTool
+ ==========
+
+-SchoolTool - common information systems platform for school administration
++SchoolTool - common information systems platform for school administration.
+
+ Website: http://www.schooltool.org/
+
+
+_______________________________________________
+Checkins mailing list
+Checkins at lists.schooltool.org
+http://lists.schooltool.org/mailman/listinfo/checkins
+
Property changes on: Zope3/trunk/src/z3checkins/tests/svn_msg.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/svn_msg2.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/svn_msg2.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/svn_msg2.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,38 @@
+To: checkins at lists.schooltool.org
+From: Albertas Agejevas <alga at pov.lt>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Message-Id: <20030908101551.6F900C32F at mail.pov.lt>
+Date: Mon, 8 Sep 2003 13:15:51 +0300 (EEST)
+Subject: [schooltool-checkins] rev 10 -
+ trunk/schooltool
+
+Author: alga
+Date: 2003-09-08 13:15:50 +0300 (Mon, 08 Sep 2003)
+New Revision: 10
+
+Modified:
+ trunk/schooltool/README
+Log:
+Added a period.
+
+
+Modified: trunk/schooltool/README
+===================================================================
+--- trunk/schooltool/README 2003-09-05 16:54:07 UTC (rev 9)
++++ trunk/schooltool/README 2003-09-08 10:15:50 UTC (rev 10)
+@@ -1,7 +1,7 @@
+ SchoolTool
+ ==========
+
+-SchoolTool - common information systems platform for school administration
++SchoolTool - common information systems platform for school administration.
+
+ Website: http://www.schooltool.org/
+
+
+_______________________________________________
+Checkins mailing list
+Checkins at lists.schooltool.org
+http://lists.schooltool.org/mailman/listinfo/checkins
+
Property changes on: Zope3/trunk/src/z3checkins/tests/svn_msg2.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/svn_msg3.txt
===================================================================
--- Zope3/trunk/src/z3checkins/tests/svn_msg3.txt 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/svn_msg3.txt 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,15 @@
+To: checkins at lists.schooltool.org
+From: Albertas Agejevas <alga at pov.lt>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Message-Id: <20030909101551.6F900C32F at mail.pov.lt>
+Date: Mon, 8 Sep 2003 13:15:51 +0300 (EEST)
+Subject: [schooltool-checkins] rev 10 had a bug in it
+
+Blah blah blah
+
+_______________________________________________
+Checkins mailing list
+Checkins at lists.schooltool.org
+http://lists.schooltool.org/mailman/listinfo/checkins
+
Property changes on: Zope3/trunk/src/z3checkins/tests/svn_msg3.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/tests/test_message.py
===================================================================
--- Zope3/trunk/src/z3checkins/tests/test_message.py 2004-05-19 09:45:58 UTC (rev 24817)
+++ Zope3/trunk/src/z3checkins/tests/test_message.py 2004-05-19 10:25:41 UTC (rev 24818)
@@ -0,0 +1,1124 @@
+#!/usr/bin/python
+"""
+Unit tests for message.py
+
+$Id: test_message.py,v 1.26 2004/03/26 22:18:08 gintautasm Exp $
+"""
+
+import unittest
+import os
+import sys
+import time
+from difflib import SequenceMatcher
+from datetime import datetime, timedelta
+from zope.app.tests.placelesssetup import PlacelessSetup
+from zope.component import getService, servicenames
+from zope.interface import Interface, implements
+from zope.interface.verify import verifyObject
+from zope.exceptions import DuplicationError
+
+from z3checkins.interfaces import IMessage, IMessageContained, \
+ ICheckinMessage, IBookmark, IMessageParser, IMessageArchive
+
+
+class TestFixedTimezone(unittest.TestCase):
+
+ def test_timezone(self):
+ from z3checkins.message import FixedTimezone
+ for tzoff, name in ((30, "+0030"), (-300, "-0500")):
+ tz = FixedTimezone(tzoff)
+ self.assertEquals(tz.tzname(None), name)
+ self.assertEquals(tz.utcoffset(None), timedelta(minutes=tzoff))
+ self.assertEquals(tz.dst(None), timedelta(0))
+
+
+class TestRFCDateTimeFormatter(unittest.TestCase):
+
+ times = ((2003, 4, 2, 12, 33, 41, 3*60, "Wed, 02 Apr 2003 12:33:41 +0300"),
+ (2000, 1, 2, 17, 41, 33, -5*60, "Sun, 02 Jan 2000 17:41:33 -0500"))
+
+ def test_rfctime(self):
+ from z3checkins.message import FixedTimezone, RFCDateTimeFormatter
+ for Y, M, D, h, m, s, tz, res in self.times:
+ dt = datetime(Y, M, D, h, m, s, tzinfo=FixedTimezone(tz))
+ view = RFCDateTimeFormatter(dt, None)
+ self.assertEquals(str(view), res)
+ self.assertEquals(view(), res)
+
+
+class TestISODateTimeFormatter(unittest.TestCase):
+
+ times = ((2003, 4, 2, 12, 33, 41, 3*60, "2003-04-02 09:33"),
+ (2000, 1, 2, 17, 41, 33, -5*60, "2000-01-02 22:41"))
+
+ def test_usertz(self):
+ from z3checkins.message import ISODateTimeFormatter
+ t = time.time()
+ delta = ISODateTimeFormatter.userstz._offset * 60
+ self.assertEquals(time.gmtime(t)[:8], time.localtime(t - delta)[:8])
+
+ def test_isotime(self):
+ from z3checkins.message import FixedTimezone, ISODateTimeFormatter
+ for Y, M, D, h, m, s, tz, res in self.times:
+ dt = datetime(Y, M, D, h, m, s, tzinfo=FixedTimezone(tz))
+ dt -= ISODateTimeFormatter.userstz.utcoffset(None)
+ view = ISODateTimeFormatter(dt, None)
+ self.assertEquals(str(view), res)
+ self.assertEquals(view(), res)
+
+
+class TestCheckinMessage(unittest.TestCase):
+
+ def test_find_body_start(self):
+ from z3checkins.message import find_body_start
+ self.assertEquals(find_body_start("Foo: X\nBar: Y\n Z\n\nQQQ"), 19)
+ self.assertEquals(find_body_start("Foo: X\r\nBar: Y\r\n Z\r\n\r\nQQQ"),
+ 23)
+ self.assertEquals(find_body_start("Foo: X\n\nQQQ\n\nWWW"), 8)
+ self.assertEquals(find_body_start("Foo: X\n\n\nQQQ\n\nWWW"), 8)
+ self.assertEquals(find_body_start("Foo: X\n\nQQQ\r\n\r\nWWW"), 8)
+ self.assertEquals(find_body_start("Foo: X\r\n\r\nQQQ\n\nWWW"), 10)
+ self.assertEquals(find_body_start("Foo: X\n\n"), 8)
+ self.assertEquals(find_body_start("\r\n\r\n"), 4)
+ self.assertEquals(find_body_start("xyzzy"), 5)
+
+ def test_interface(self):
+ from z3checkins.message import Message
+ from z3checkins.message import CheckinMessage
+ verifyObject(IMessage, Message())
+ verifyObject(ICheckinMessage, CheckinMessage())
+
+ def test_body(self):
+ from z3checkins.message import Message
+ m = Message(full_text="Subject: foo\n\nBody text\n")
+ self.assertEquals(m.body, "Body text\n")
+
+ def test_equality(self):
+ from z3checkins.message import Message
+ a = Message(message_id="abc")
+ b = Message(message_id="abc")
+ c = Message(message_id="xyz")
+ self.assertEquals(a, b)
+ self.assertNotEquals(a, c)
+ self.assertNotEquals(b, c)
+
+
+class TestCheckinMessageParser(unittest.TestCase):
+
+ def test_interface(self):
+ from z3checkins.message import CheckinMessageParser
+ verifyObject(IMessageParser, CheckinMessageParser())
+
+ def test_parser1(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ sample_msg1 = open_test_data("sample_msg1.txt")
+ sample_msg1_text = sample_msg1.read()
+ sample_msg1.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(sample_msg1)
+ self.assert_(ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "42 at bar.com")
+ self.assertEquals(msg.author_name, "Foo Bar")
+ self.assertEquals(msg.author_email, "foo.bar at bar.com")
+ self.assertEquals(msg.date, datetime(2003, 03, 28, 11, 58, 05,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.directory, "Zope3/src/foo/bar")
+ self.assertEquals(msg.branch, None)
+ self.assertEquals(msg.log_message, """\
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.""")
+ self.assertEquals(msg.body,
+ sample_msg1_text.split("\n\n", 1)[1])
+
+ def test_parser2(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ sample_msg2 = open_test_data("sample_msg2.txt")
+ sample_msg2_text = sample_msg2.read()
+ sample_msg2.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(sample_msg2)
+ self.assert_(ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "42 at bar.com")
+ self.assertEquals(msg.author_name, "Foo Bar")
+ self.assertEquals(msg.author_email, "foo.bar at bar.com")
+ self.assertEquals(msg.date, datetime(2003, 03, 28, 11, 58, 05,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.directory, "Zope3/src/foo/bar")
+ self.assertEquals(msg.branch, "foo-branch")
+ self.assertEquals(msg.log_message, """\
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.""")
+ self.assertEquals(msg.body,
+ sample_msg2_text.split("\n\n", 1)[1])
+
+ def test_parser_empty(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.interfaces import FormatError
+ parser = CheckinMessageParser()
+ self.assertRaises(FormatError, parser.parse, '')
+
+ def test_parser_importmsg(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ sample_import_msg = open_test_data("sample_import_msg.txt")
+ sample_import_msg_text = sample_import_msg.read()
+ sample_import_msg.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(sample_import_msg)
+ self.assert_(ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "42 at bar.com")
+ self.assertEquals(msg.author_name, "Foo Bar")
+ self.assertEquals(msg.author_email, "foo.bar at bar.com")
+ self.assertEquals(msg.date, datetime(2003, 03, 28, 11, 58, 05,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.directory, "Zope3/src/foo/bar")
+ self.assertEquals(msg.branch, None)
+ self.assertEquals(msg.log_message, """\
+Ipsum suum dolores quantum est er nonsensicum textum writum esmum inum tuum
+lineum furum testum logum messageum.""")
+ self.assertEquals(msg.body,
+ sample_import_msg_text.split("\n\n", 1)[1])
+
+ def test_parser_simplemsg(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ simple_msg = open_test_data("simple_msg.txt")
+ simple_msg_text = simple_msg.read()
+ simple_msg.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(simple_msg)
+ self.assert_(IMessage.providedBy(msg))
+ self.assert_(not ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "q$w$e$r$t$y at example.com")
+ self.assertEquals(msg.author_name, "John Doe")
+ self.assertEquals(msg.author_email, "john at example.com")
+ self.assertEquals(msg.date, datetime(2003, 07, 29, 14, 42, 11,
+ tzinfo=FixedTimezone(2*60)))
+ self.assertEquals(msg.body, simple_msg_text.split("\n\n", 1)[1])
+
+ def test_parser_svnmsg(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ svn_msg = open_test_data("svn_msg.txt")
+ svn_msg_text = svn_msg.read()
+ svn_msg.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(svn_msg)
+ self.assert_(ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "20030908101551.6F900C32F at mail.pov.lt")
+ self.assertEquals(msg.author_name, "Albertas Agejevas")
+ self.assertEquals(msg.author_email, "alga at pov.lt")
+ self.assertEquals(msg.date, datetime(2003, 9, 8, 13, 15, 51,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.directory, "trunk/schooltool")
+ self.assertEquals(msg.branch, None)
+ self.assertEquals(msg.log_message, """Added a period.""")
+ self.assertEquals(msg.body,
+ svn_msg_text.split("\n\n", 1)[1])
+
+ def test_parser_svnmsg_with_split_subject(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ svn_msg2 = open_test_data("svn_msg2.txt")
+ svn_msg2_text = svn_msg2.read()
+ svn_msg2.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(svn_msg2)
+ self.assert_(ICheckinMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "20030908101551.6F900C32F at mail.pov.lt")
+ self.assertEquals(msg.author_name, "Albertas Agejevas")
+ self.assertEquals(msg.author_email, "alga at pov.lt")
+ self.assertEquals(msg.date, datetime(2003, 9, 8, 13, 15, 51,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.directory, "trunk/schooltool")
+ self.assertEquals(msg.branch, None)
+ self.assertEquals(msg.log_message, """Added a period.""")
+ self.assertEquals(msg.body,
+ svn_msg2_text.split("\n\n", 1)[1])
+
+ def test_parser_svnmsg_with_rev(self):
+ from z3checkins.message import CheckinMessageParser
+ from z3checkins.message import FixedTimezone
+ svn_msg3 = open_test_data("svn_msg3.txt")
+ svn_msg3_text = svn_msg3.read()
+ svn_msg3.seek(0)
+ parser = CheckinMessageParser()
+ msg = parser.parse(svn_msg3)
+ self.assert_(IMessage.providedBy(msg))
+ self.assertEquals(msg.message_id, "20030909101551.6F900C32F at mail.pov.lt")
+ self.assertEquals(msg.author_name, "Albertas Agejevas")
+ self.assertEquals(msg.author_email, "alga at pov.lt")
+ self.assertEquals(msg.date, datetime(2003, 9, 8, 13, 15, 51,
+ tzinfo=FixedTimezone(3*60)))
+ self.assertEquals(msg.body, svn_msg3_text.split("\n\n", 1)[1])
+
+
+class MessageStub:
+
+ implements(ICheckinMessage, IMessageContained)
+
+ __name__ = __parent__ = None
+
+ def __init__(self, data=None, date=None, body=None, log_message='',
+ subject='', message_id="message at id"):
+ self.data = data
+ self.date = date
+ self.body = body
+ self.subject = subject
+ self.log_message = log_message
+ self.message_id = message_id
+
+
+class TestMessageContainerAdapter(unittest.TestCase):
+
+ def test_interface(self):
+ from z3checkins.message import MessageContainerAdapter
+ verifyObject(IMessageArchive, MessageContainerAdapter({}))
+
+ def test_len(self):
+ from z3checkins.message import MessageContainerAdapter
+ a = MessageContainerAdapter({})
+ self.assertEquals(len(a), 0)
+ a = MessageContainerAdapter({'1': 2, '3': 'abc'})
+ self.assertEquals(len(a), 0)
+ a = MessageContainerAdapter({'1': 2, '3': 'abc', 4: MessageStub()})
+ self.assertEquals(len(a), 1)
+
+ def test_getitem(self):
+ from z3checkins.message import MessageContainerAdapter
+ a = MessageContainerAdapter({'1': 2, '3': 'abc',
+ '4': MessageStub(date=1, message_id='1'),
+ '5': MessageStub(date=4, message_id='2'),
+ '6': MessageStub(date=3, message_id='3'),
+ '7': MessageStub(date=2, message_id='4')})
+ self.assertEquals(a[0].message_id, '1')
+ self.assertEquals(a[1].message_id, '4')
+ self.assertEquals(a[2].message_id, '3')
+ self.assertEquals(a[3].message_id, '2')
+ self.assertEquals(a[-1].message_id, '2')
+ self.assertRaises(IndexError, a.__getitem__, 4)
+ self.assertRaises(IndexError, a.__getitem__, -5)
+ self.assertRaises(TypeError, a.__getitem__, 'xyzzy')
+ self.assertRaises(TypeError, a.__getitem__, None)
+ self.assertEquals(len(a[1:3]), 2)
+ self.assertEquals(len(a[1:-1]), 2)
+ self.assertEquals(len(a[3:1]), 0)
+
+ def test_iter(self):
+ from z3checkins.message import MessageContainerAdapter
+ a = MessageContainerAdapter({'1': 2, '3': 'abc',
+ '4': MessageStub(date=1, message_id='1'),
+ '5': MessageStub(date=4, message_id='2'),
+ '6': MessageStub(date=3, message_id='3'),
+ '7': MessageStub(date=2, message_id='4')})
+ b = [x.message_id for x in a]
+ self.assertEquals(b, ['1', '4', '3', '2'])
+ self.assert_(MessageStub(message_id='5') not in a)
+ self.assert_(a.context['6'] in a)
+
+ def test_index(self):
+ from z3checkins.message import MessageContainerAdapter
+ m1 = MessageStub(date=1, message_id='1')
+ m2 = MessageStub(date=4, message_id='2')
+ m3 = MessageStub(date=3, message_id='3')
+ m4 = MessageStub(date=2, message_id='4')
+ a = MessageContainerAdapter({'1': 2, '3': 'abc',
+ '4': m1, '5': m2, '6': m3, '7': m4})
+ self.assertEquals(a.index(m1), 0)
+ self.assertEquals(a.index(m4), 1)
+ self.assertEquals(a.index(m3), 2)
+ self.assertEquals(a.index(m2), 3)
+ self.assertRaises(ValueError, a.index, MessageStub)
+
+
+class ParserStub:
+
+ implements(IMessageParser)
+
+ def parse(self, data):
+ if hasattr(data, 'read'):
+ full_text = data.read()
+ else:
+ full_text = data
+
+ message_id = "message at id"
+ id_lines = filter(lambda s: s.lower().startswith("message-id: "),
+ full_text.splitlines())
+ if len(id_lines) == 1:
+ message_id = id_lines[0][len("message-id: "):]
+ return MessageStub(data=full_text, message_id=message_id)
+
+class AddingStub:
+
+ def __init__(self):
+ self.added = []
+
+ def add(self, obj):
+ # ignore duplicates happening with default messages
+ if obj.message_id != "message at id":
+ for message in self.added:
+ if message.message_id == obj.message_id:
+ raise DuplicationError()
+ self.added.append(obj)
+
+class TestMessageUpload(PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ PlacelessSetup.setUp(self)
+ getService(None, 'Utilities').provideUtility(IMessageParser,
+ ParserStub())
+
+ def test_createAndAdd(self):
+ from z3checkins.message import MessageUpload
+ view = MessageUpload()
+ view.context = AddingStub()
+ view.add = view.context.add
+ added = view.context.added
+ self.assertEquals(len(added), 0)
+ view.createAndAdd({})
+ self.assertEquals(len(added), 0)
+ view.createAndAdd({'data': 'Ipsum suum'})
+ self.assertEquals(len(added), 1)
+ self.assertEquals(added[0].__class__, MessageStub)
+ self.assertEquals(added[0].message_id, "message at id")
+ self.assertEquals(added[0].data, "Ipsum suum")
+
+ def test_createAndAdd_mbox(self):
+ from z3checkins.message import MessageUpload
+ view = MessageUpload()
+ view.context = AddingStub()
+ view.add = view.context.add
+ added = view.context.added
+ data = open_test_data('mbox.txt').read()
+ self.assertEquals(len(added), 0)
+ view.createAndAdd({'data': data})
+ self.assertEquals(len(added), 4)
+ for message in added:
+ self.assertEquals(message.__class__, MessageStub)
+ self.assertEquals(added[0].data.count("Steve Alexander"), 1)
+ self.assertEquals(added[1].data.count("Steve Alexander"), 1)
+ self.assertEquals(added[2].data.count("Tim Peters"), 1)
+ self.assertEquals(added[3].data.count("Tim Peters"), 1)
+
+ def test_createAndAdd_mbox_with_dupes(self):
+ from z3checkins.message import MessageUpload
+ view = MessageUpload()
+ view.context = AddingStub()
+ view.add = view.context.add
+ added = view.context.added
+ data = open_test_data('mbox_with_dupes.txt').read()
+ self.assertEquals(len(added), 0)
+ view.createAndAdd({'data': data})
+ self.assertEquals(len(added), 2)
+ for message in added:
+ self.assertEquals(message.__class__, MessageStub)
+ self.assertEquals(added[0].data.count("Steve Alexander"), 1)
+ self.assertEquals(added[1].data.count("Tim Peters"), 1)
+
+
+class IUnitTestPresentation(Interface):
+ pass
+
+class IRequest(Interface):
+ pass
+
+class MessageTestView:
+ def __init__(self, context, request):
+ self.context = context
+ def __call__(self, same_as_previous=False):
+ result = 'msg%d' % self.context.date
+ if same_as_previous:
+ result += '*'
+ return result + '\n'
+
+class BookmarkTestView:
+ def __init__(self, context, request):
+ self.context = context
+ def __call__(self, same_as_previous=False):
+ return '-\n'
+
+class RequestStub(dict):
+
+ implements(IRequest)
+
+ _cookies = ()
+
+ def __init__(self, **kw):
+ super(RequestStub, self).__init__()
+ self.update(kw)
+ self.response = self
+
+ def setCookie(self, name, value, **kw):
+ self._cookies += (name, value, kw)
+
+ def getPresentationType(self):
+ return IUnitTestPresentation
+
+ def getPresentationSkin(self):
+ return ''
+
+class TestContainerView(PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ PlacelessSetup.setUp(self)
+ from z3checkins.message import MessageContainerAdapter
+ getService(None, servicenames.Adapters).register(
+ [None], IMessageArchive, "", MessageContainerAdapter)
+
+ def test_checkins(self):
+ from z3checkins.message import ContainerView
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
+ 'a': MessageStub(date=2), 'c': MessageStub(date=3)}
+ view.request = {}
+ res = view.checkins()
+ self.assertEquals(len(res), 3)
+ self.assertEquals(view.count(), 3)
+ self.assertEquals(res[0].date, 3)
+ self.assertEquals(res[1].date, 2)
+ self.assertEquals(res[2].date, 1)
+
+ def test_checkins_limited(self):
+ from z3checkins.message import ContainerView
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
+ 'a': MessageStub(date=2), 'c': MessageStub(date=3)}
+ view.request = {}
+ res = view.checkins(size=2)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(res[0].date, 3)
+ self.assertEquals(res[1].date, 2)
+
+ res = view.checkins(start=1, size=3)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(res[0].date, 2)
+ self.assertEquals(res[1].date, 1)
+
+ def test_checkins_bookmarks(self):
+ from z3checkins.message import ContainerView
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
+ 'a': MessageStub(date=2), 'c': MessageStub(date=4)}
+ view.request = {}
+ view.bookmarks = lambda: [3]
+ res = view.checkins()
+ self.assertEquals(len(res), 4)
+ self.assertEquals(res[0].date, 4)
+ self.assert_(IBookmark.providedBy(res[1]))
+ self.assertEquals(res[2].date, 2)
+ self.assertEquals(res[3].date, 1)
+
+ view.bookmarks = lambda: [2]
+ res = view.checkins()
+ self.assertEquals(len(res), 4)
+ self.assertEquals(res[0].date, 4)
+ self.assert_(IBookmark.providedBy(res[1]))
+ self.assertEquals(res[2].date, 2)
+ self.assertEquals(res[3].date, 1)
+
+ view.bookmarks = lambda: [0, 1, 2, 3, 4, 5, 6, 2, 3, 1]
+ res = view.checkins()
+ self.assertEquals(len(res), 5)
+ self.assertEquals(res[0].date, 4)
+ self.assert_(IBookmark.providedBy(res[1]))
+ self.assertEquals(res[2].date, 2)
+ self.assert_(IBookmark.providedBy(res[3]))
+ self.assertEquals(res[4].date, 1)
+
+ res = view.checkins(start=1, size=1)
+ self.assertEquals(len(res), 3)
+ self.assert_(IBookmark.providedBy(res[0]))
+ self.assertEquals(res[1].date, 2)
+ self.assert_(IBookmark.providedBy(res[2]))
+
+ def test_bookmarks(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.request = {}
+ self.assertEquals(view.bookmarks(), [])
+ view.request = {'bookmarks': '2003-01-04T21:33:04-05:00'}
+ self.assertEquals(view.bookmarks(),
+ [datetime(2003, 01, 04, 21, 33, 04,
+ tzinfo=FixedTimezone(-5*60))])
+ view.request = {'bookmarks': '2003-01-04T21:33:04-05:00 '
+ 'errors are ignored '
+ '2004-05-06T07:08:09+10:00 '
+ '2002-02-02T02:02:02+02:00 '
+ '2005-02-29T07:08:09+10:00'}
+ self.assertEquals(view.bookmarks(),
+ [datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60)),
+ datetime(2004, 5, 6, 7, 8, 9,
+ tzinfo=FixedTimezone(10*60)),
+ datetime(2002, 2, 2, 2, 2, 2,
+ tzinfo=FixedTimezone(2*60))])
+
+ def test_placeBookmark_empty_archive(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {}
+ view.request = RequestStub()
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies, ())
+
+ def test_placeBookmark_empty_bookmarks(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60)))}
+ view.request = RequestStub()
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies,
+ ('bookmarks', '2003-01-04T21:33:04-05:00',
+ {'max_age': 31536000}))
+
+ def test_placeBookmark_not_at_start(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60)))}
+ view.request = RequestStub(start=1)
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies, ())
+
+ def test_placeBookmark_no_new_checkins(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60)))}
+ view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
+ 'errors are ignored '
+ '2002-02-02T02:02:02+02:00')
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies, ())
+
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60)))}
+ view.request = RequestStub(bookmarks='2004-01-04T21:33:04-05:00 '
+ 'errors are ignored '
+ '2002-02-02T02:02:02+02:00')
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies, ())
+
+ def test_placeBookmark_new_checkins(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60))),
+ 'w': MessageStub(date=datetime(2003, 1, 6, 22, 33, 44,
+ tzinfo=FixedTimezone(+3*60)))}
+ view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
+ 'errors are ignored '
+ '2002-02-02T02:02:02+02:00')
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies,
+ ('bookmarks', '2002-02-02T02:02:02+02:00 '
+ '2003-01-04T21:33:04-05:00 '
+ '2003-01-06T22:33:44+03:00',
+ {'max_age': 31536000}))
+
+ def test_placeBookmark_new_checkins_overflow(self):
+ from z3checkins.message import ContainerView
+ from z3checkins.message import FixedTimezone
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
+ tzinfo=FixedTimezone(-5*60))),
+ 'w': MessageStub(date=datetime(2003, 1, 6, 22, 33, 44,
+ tzinfo=FixedTimezone(+3*60)))}
+ view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
+ 'errors are ignored '
+ '2002-01-01T02:02:02+02:00 '
+ '2002-01-02T02:02:02+02:00 '
+ '2002-01-03T02:02:02+02:00 '
+ '2002-01-04T02:02:02+02:00 '
+ '2002-02-02T02:02:02+02:00 ')
+ view.placeBookmark()
+ self.assertEquals(view.request._cookies,
+ ('bookmarks', '2002-01-03T02:02:02+02:00 '
+ '2002-01-04T02:02:02+02:00 '
+ '2002-02-02T02:02:02+02:00 '
+ '2003-01-04T21:33:04-05:00 '
+ '2003-01-06T22:33:44+03:00',
+ {'max_age': 31536000}))
+
+ def test_renderCheckins(self):
+ from z3checkins.message import ContainerView
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=1, log_message='xxx'),
+ 'a': MessageStub(date=2, log_message='xxx'),
+ 'c': MessageStub(date=3, log_message='yyy')}
+ view.request = RequestStub()
+ view.index = view
+ getService(None, servicenames.Presentation).provideView(
+ ICheckinMessage, 'html', IRequest, MessageTestView)
+
+ res = view.renderCheckins()
+ self.assertEquals(res, 'msg3\nmsg2\nmsg1*\n')
+ res = view.renderCheckins(start=1, size=1)
+ self.assertEquals(res, 'msg2\n')
+
+ def test_renderCheckins_with_bookmarks(self):
+ from z3checkins.message import ContainerView
+ view = ContainerView()
+ view.context = {'x': 123, 'y': object(),
+ 'z': MessageStub(date=1, log_message='xxx'),
+ 'a': MessageStub(date=2, log_message='xxx'),
+ 'c': MessageStub(date=3, log_message='yyy')}
+ view.request = RequestStub()
+ view.index = view
+ view.bookmarks = lambda: [1]
+ getService(None, servicenames.Presentation).provideView(
+ ICheckinMessage, 'html', IRequest, MessageTestView)
+ getService(None, servicenames.Presentation).provideView(
+ IBookmark, 'html', IRequest, BookmarkTestView)
+
+ res = view.renderCheckins()
+ self.assertEquals(res, 'msg3\nmsg2\n-\nmsg1*\n')
+
+
+def diff(a, b):
+ "Compare the differences of two sequences of strings"
+
+ if isinstance(a, (str, unicode)): a = a.splitlines()
+ if isinstance(b, (str, unicode)): b = b.splitlines()
+
+ diff = []
+ def dump(tag, x, lo, hi, diff=diff):
+ for i in xrange(lo, hi):
+ diff.append(tag + x[i])
+
+ differ = SequenceMatcher(a=a, b=b)
+ for tag, alo, ahi, blo, bhi in differ.get_opcodes():
+ if tag == 'replace':
+ dump('-', a, alo, ahi)
+ dump('+', b, blo, bhi)
+ elif tag == 'delete':
+ dump('-', a, alo, ahi)
+ elif tag == 'insert':
+ dump('+', b, blo, bhi)
+ elif tag == 'equal':
+ dump(' ', a, alo, ahi)
+ else:
+ raise ValueError, 'unknown tag ' + `tag`
+ return "\n".join(diff)
+
+class TestCheckinMessageView(PlacelessSetup, unittest.TestCase):
+
+ def setUp(self):
+ PlacelessSetup.setUp(self)
+ from z3checkins.message import MessageContainerAdapter
+ getService(None, servicenames.Adapters).register(
+ [None], IMessageArchive, "", MessageContainerAdapter)
+
+ def test_body_strange(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="Something & strange")
+ self.assertEquals(view.body(), "<pre>Something & strange</pre>")
+
+ def test_body(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="Blah blah\n"
+ "blah\n"
+ "Log message:\n"
+ "Blurb blurb\n"
+ "blurb.\n"
+ "\n"
+ "=== foo.py: 1.2 -> 1.3 ===\n"
+ "--- foo.py:1.2\tdatetime\n"
+ "+++ foo.py\tdatetime\n"
+ "@@@ -123,4 +567,8 @@@\n"
+ " fwoosh <>&\"\n"
+ "-fouoww\n"
+ "+fruuuh\n"
+ " fargle\n"
+ "_______________________________________________\n"
+ "signature\n")
+ result = view.body()
+ expected = ('<pre>Blah blah\n'
+ 'blah\n'
+ 'Log message:\n'
+ '</pre>'
+ '<div class="log">'
+ '<p>Blurb blurb</p>\n'
+ '<p>blurb.</p>'
+ '</div>'
+ '<pre>\n'
+ '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
+ '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
+ '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
+ '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
+ ' fwoosh <>&"\n'
+ '<div class="old">-fouoww\n</div>'
+ '<div class="new">+fruuuh\n</div>'
+ ' fargle'
+ '<div class="signature">\n'
+ '_______________________________________________\n'
+ 'signature\n'
+ '</div>'
+ '</pre>')
+ self.assertEquals(result, expected, diff(expected, result))
+
+ def test_body_svn(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="""\
+Author: mg
+Date: 2003-09-09 21:21:09 +0300 (Tue, 09 Sep 2003)
+New Revision: 15
+
+Added:
+ trunk/schooltool/schooltool/ftests/
+ trunk/schooltool/schooltool/ftests/__init__.py
+ trunk/schooltool/schooltool/ftests/test_rest.py
+ trunk/schooltool/schooltool/main.py
+ trunk/schooltool/schooltool/tests/test_main.py
+Removed:
+ trunk/schooltool/schooltool/tests/test_rest.py
+Modified:
+ trunk/schooltool/schooltool/tests/__init__.py
+Log:
+First prototype of SchoolTool HTTP server that serves RESTful pages, complete
+with unit and functional tests.
+
+
+
+Added: trunk/schooltool/schooltool/ftests/__init__.py
+===================================================================
+--- trunk/schooltool/schooltool/ftests/__init__.py 2003-09-09 18:03:43 UTC (rev 14)
++++ trunk/schooltool/schooltool/ftests/__init__.py 2003-09-09 18:21:09 UTC (rev 15)
+@@ -0,0 +1,21 @@
++#
++# SchoolTool - common information systems platform for school administration
+"""
+)
+ result = view.body()
+ expected = ("""\
+<pre>Author: mg
+Date: 2003-09-09 21:21:09 +0300 (Tue, 09 Sep 2003)
+New Revision: 15
+
+Added:
+ trunk/schooltool/schooltool/ftests/
+ trunk/schooltool/schooltool/ftests/__init__.py
+ trunk/schooltool/schooltool/ftests/test_rest.py
+ trunk/schooltool/schooltool/main.py
+ trunk/schooltool/schooltool/tests/test_main.py
+Removed:
+ trunk/schooltool/schooltool/tests/test_rest.py
+Modified:
+ trunk/schooltool/schooltool/tests/__init__.py
+Log:
+</pre><div class="log"><p>First prototype of SchoolTool HTTP server that serves RESTful pages, complete</p>
+<p>with unit and functional tests.</p></div><pre>
+Added: trunk/schooltool/schooltool/ftests/__init__.py
+<div class="file">===================================================================
+</div><div class="oldfile">--- trunk/schooltool/schooltool/ftests/__init__.py<span class="tab">>------</span>2003-09-09 18:03:43 UTC (rev 14)
+</div><div class="newfile">+++ trunk/schooltool/schooltool/ftests/__init__.py<span class="tab">>------</span>2003-09-09 18:21:09 UTC (rev 15)
+</div><div class="chunk">@@ -0,0 +1,21 @@
+</div><div class="new">+#
+</div><div class="new">+# SchoolTool - common information systems platform for school administration</div></pre>"""
+ )
+ self.assertEquals(result, expected, diff(expected, result))
+
+ def test_body_crlf(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="Blah blah\r\n"
+ "blah\r\n"
+ "Log message:\r\n"
+ "Blurb blurb\r\n"
+ "blurb.\r\n"
+ "\r\n"
+ "=== foo.py: 1.2 -> 1.3 ===\r\n"
+ "--- foo.py:1.2\tdatetime\r\n"
+ "+++ foo.py\tdatetime\r\n"
+ "@@@ -123,4 +567,8 @@@\r\n"
+ " fwoosh <>&\"\r\n"
+ "-fouoww \r\n"
+ "+fruuuh\r\n"
+ " fargle\r\n"
+ " \r\n"
+ " \r\n"
+ "_______________________________________________\r\n"
+ "signature\r\n")
+ result = view.body()
+ expected = ('<pre>Blah blah\n'
+ 'blah\n'
+ 'Log message:\n'
+ '</pre>'
+ '<div class="log">'
+ '<p>Blurb blurb</p>\n'
+ '<p>blurb.</p>'
+ '</div>'
+ '<pre>\n'
+ '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
+ '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
+ '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
+ '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
+ ' fwoosh <>&"\n'
+ '<div class="old">-fouoww<span class="trail">..</span>\n</div>'
+ '<div class="new">+fruuuh\n</div>'
+ ' fargle\n'
+ ' <span class="trail">..</span>\n'
+ ' '
+ '<div class="signature">\n'
+ '_______________________________________________\n'
+ 'signature\n'
+ '</div>'
+ '</pre>')
+ self.assertEquals(result, expected, diff(expected, result))
+
+ def test_body_nosig(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="Blah blah\n"
+ "blah\n"
+ "Log message:\n"
+ "Blurb blurb\n"
+ "blurb.\n"
+ "\n"
+ "=== foo.py: 1.2 -> 1.3 ===\n"
+ "--- foo.py:1.2\tdatetime\n"
+ "+++ foo.py\tdatetime\n"
+ "@@@ -123,4 +567,8 @@@\n"
+ " fwoosh <>&\"\n"
+ "-fouoww\n"
+ "+fruuuh\n"
+ " fargle")
+ result = view.body()
+ expected = ('<pre>Blah blah\n'
+ 'blah\n'
+ 'Log message:\n'
+ '</pre>'
+ '<div class="log">'
+ '<p>Blurb blurb</p>\n'
+ '<p>blurb.</p>'
+ '</div>'
+ '<pre>\n'
+ '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
+ '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
+ '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
+ '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
+ ' fwoosh <>&"\n'
+ '<div class="old">-fouoww\n</div>'
+ '<div class="new">+fruuuh\n</div>'
+ ' fargle'
+ '</pre>')
+ self.assertEquals(result, expected, diff(expected, result))
+
+ def test_body_importmsg(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub(body="Blah blah\n"
+ "blah\n"
+ "Log message:\n"
+ "Blurb blurb\n"
+ "blurb.\n"
+ "\n"
+ "Status:\n"
+ "\n"
+ "Vendor Tag:\tnovendor\n"
+ "Release Tags:\tstart\n"
+ "\n"
+ "N foo/bar.py\n"
+ "N foo/baz.pt\n"
+ "\n"
+ "No conflicts created by this import\n")
+ result = view.body()
+ expected = ('<pre>Blah blah\n'
+ 'blah\n'
+ 'Log message:\n'
+ '</pre>'
+ '<div class="log">'
+ '<p>Blurb blurb</p>\n'
+ '<p>blurb.</p>'
+ '</div>'
+ '<pre>\n'
+ 'Status:\n'
+ '\n'
+ 'Vendor Tag:\tnovendor\n'
+ 'Release Tags:\tstart\n'
+ '\n'
+ 'N foo/bar.py\n'
+ 'N foo/baz.pt\n'
+ '\n'
+ 'No conflicts created by this import\n'
+ '</pre>')
+ self.assertEquals(result, expected, diff(expected, result))
+
+ def test_markwitespace(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ m = view.mark_whitespace
+ self.assertEquals(m(''), '')
+ self.assertEquals(m('xyzzy'), 'xyzzy')
+ self.assertEquals(m(' '), ' <span class="trail">.</span>')
+ self.assertEquals(m(' xy z '), ' xy z<span class="trail">..</span>')
+ self.assertEquals(m(' xy z \t '), ' xy z<span class="trail">.<span class="tab">>-</span>.</span>')
+ self.assertEquals(m(' \t|'), ' <span class="tab">>-------</span>|')
+ self.assertEquals(m(' |\t|'), ' |<span class="tab">>------</span>|')
+ self.assertEquals(m(' xxxxxx|\t|'), ' xxxxxx|<span class="tab">></span>|')
+ self.assertEquals(m(' x<tag\t>xxxxx|\t|'), ' x<tag\t>xxxxx|<span class="tab">></span>|')
+ self.assertEquals(m(' x&ent;xxxx|\t|'), ' x&ent;xxxx|<span class="tab">></span>|')
+
+ def test_urls(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ prefixes = ['', 'A link: ', '(', '<']
+ suffixes = ['', ' etc.', ')', '>', '.', ',', '\n']
+ urls = ['http://www.example.com', 'https://www.example.com', 'http://localhost:8080/foo?q=a&w=b']
+
+ def quote(text):
+ return (text.replace('&', '&')
+ .replace('<', '<')
+ .replace('>', '>')
+ .replace('"', '"'))
+
+ for prefix in prefixes:
+ for link in urls:
+ for suffix in suffixes:
+ view.context = MessageStub(body="%s%s%s" % (prefix, link, suffix))
+ prefix = quote(prefix)
+ link = quote(link)
+ suffix = quote(suffix)
+ self.assertEquals(view.body(), '<pre>%s<a href="%s">%s</a>%s</pre>' % (prefix, link, link, suffix))
+
+ def test_navigation_no_archive(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub()
+ self.assertEquals(view.first(), None)
+ self.assertEquals(view.last(), None)
+ self.assertEquals(view.next(), None)
+ self.assertEquals(view.previous(), None)
+
+ def test_navigation_empty_archive(self):
+ from z3checkins.message import CheckinMessageView
+ view = CheckinMessageView()
+ view.context = MessageStub()
+ view.context.__parent__ = {'1': 2}
+ self.assertEquals(view.first(), None)
+ self.assertEquals(view.last(), None)
+ self.assertEquals(view.next(), None)
+ self.assertEquals(view.previous(), None)
+
+ def test_navigation(self):
+ from z3checkins.message import CheckinMessageView
+ m1 = MessageStub(date=1, message_id='1')
+ m2 = MessageStub(date=2, message_id='2')
+ m3 = MessageStub(date=3, message_id='3')
+ m4 = MessageStub(date=4, message_id='4')
+
+ folder = {'1': 2, '3': 'abc', '4': m1, '5': m2, '6': m3, '7': m4}
+ view = CheckinMessageView()
+ view.context = m3
+ m3.__parent__ = folder
+ self.assertEquals(view.first(), m1)
+ self.assertEquals(view.last(), m4)
+ self.assertEquals(view.next(), m4)
+ self.assertEquals(view.previous(), m2)
+
+ view = CheckinMessageView()
+ view.context = m1
+ m1.__parent__ = folder
+ self.assertEquals(view.first(), m1)
+ self.assertEquals(view.last(), m4)
+ self.assertEquals(view.next(), m2)
+ self.assertEquals(view.previous(), None)
+
+ view = CheckinMessageView()
+ view.context = m4
+ m4.__parent__ = folder
+ self.assertEquals(view.first(), m1)
+ self.assertEquals(view.last(), m4)
+ self.assertEquals(view.next(), None)
+ self.assertEquals(view.previous(), m3)
+
+
+class TestMessageNameChooser(unittest.TestCase):
+
+ def test_chooseName(self):
+ from z3checkins.folder import MessageNameChooser
+ msg = MessageStub(message_id="msg at id")
+ chooser = MessageNameChooser(None)
+ self.assertEquals(chooser.chooseName(None, msg), msg.message_id)
+ msg2 = MessageStub(message_id="foo at bar")
+ self.assertEquals(chooser.chooseName(None, msg2), msg2.message_id)
+
+ def test_checkName(self):
+ from z3checkins.folder import MessageNameChooser
+ msg = MessageStub(message_id="msg at id")
+ chooser = MessageNameChooser(None)
+ self.assertEquals(chooser.checkName("msg at id", msg), True)
+
+
+class TestMessageSized(unittest.TestCase):
+
+ def test_interface(self):
+ from z3checkins.folder import MessageSized
+ from zope.app.size.interfaces import ISized
+ self.assert_(ISized.providedBy(MessageSized(MessageStub())))
+
+ def test_sizeForSorting(self):
+ from z3checkins.folder import MessageSized
+ msg = MessageStub()
+ sized = MessageSized(msg)
+ msg.full_text = '*' * 42;
+ self.assertEquals(sized.sizeForSorting(), 42)
+ msg.full_text = '*' * 32768;
+ self.assertEquals(sized.sizeForSorting(), 32768)
+
+ def test_sizeForDisplay(self):
+ from z3checkins.folder import MessageSized
+ msg = MessageStub()
+ sized = MessageSized(msg)
+ msg.full_text = '*' * 42;
+ self.assertEquals(sized.sizeForDisplay(), u"42 bytes")
+ msg.full_text = '*' * 32767;
+ self.assertEquals(sized.sizeForDisplay(), u"31 KB")
+ msg.full_text = '*' * 32768;
+ self.assertEquals(sized.sizeForDisplay(), u"32 KB")
+
+
+def open_test_data(filename):
+ """Open a file relative to the location of this module."""
+ base = os.path.dirname(__file__)
+ return open(os.path.join(base, filename))
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestFixedTimezone))
+ suite.addTest(unittest.makeSuite(TestRFCDateTimeFormatter))
+ suite.addTest(unittest.makeSuite(TestISODateTimeFormatter))
+ suite.addTest(unittest.makeSuite(TestCheckinMessage))
+ suite.addTest(unittest.makeSuite(TestCheckinMessageParser))
+ suite.addTest(unittest.makeSuite(TestMessageContainerAdapter))
+ suite.addTest(unittest.makeSuite(TestMessageUpload))
+ suite.addTest(unittest.makeSuite(TestContainerView))
+ suite.addTest(unittest.makeSuite(TestCheckinMessageView))
+ suite.addTest(unittest.makeSuite(TestMessageNameChooser))
+ suite.addTest(unittest.makeSuite(TestMessageSized))
+ return suite
+
+
+if __name__ == "__main__":
+ unittest.main()
Property changes on: Zope3/trunk/src/z3checkins/tests/test_message.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/z3checkins/zope3.png
===================================================================
(Binary files differ)
Property changes on: Zope3/trunk/src/z3checkins/zope3.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
More information about the Zope3-Checkins
mailing list