[Zope3-checkins] CVS: zopeproducts/zwiki - diff.py:1.1 READMDE.txt:1.4 TODO.txt:1.14 configure.zcml:1.20 interfaces.py:1.7 wiki.py:1.4 wikipage.py:1.3
Stephan Richter
srichter@cbu.edu
Thu, 10 Apr 2003 21:38:16 -0400
Update of /cvs-repository/zopeproducts/zwiki
In directory cvs.zope.org:/tmp/cvs-serv29237
Modified Files:
READMDE.txt TODO.txt configure.zcml interfaces.py wiki.py
wikipage.py
Added Files:
diff.py
Log Message:
- Checked in a diff modules for making diffs between the old and the new
version of the Wiki Page contents
- Added very simple mail subscription
- Some more Tweaks in the older code.
Ok, I am too tired to write more senseful things right now...
=== Added File zopeproducts/zwiki/diff.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser View Components for WikiPages
$Id: diff.py,v 1.1 2003/04/11 01:37:45 srichter Exp $
"""
from difflib import ndiff
MAX_OLD_LINES_DISPLAY = 40
MAX_NEW_LINES_DISPLAY = 40
def textdiff(old_text, new_text, verbose=1):
"""
generate a plain text diff, optimized for human readability,
between two revisions of this page, numbering back from the latest.
Alternately, a and/or b texts can be specified.
"""
old = split(old_text, '\n')
new = split(new_text, '\n')
cruncher=ndiff.SequenceMatcher(
isjunk=lambda x: x in " \\t",
a=old,
b=new)
r = []
for tag, old_lo, old_hi, new_lo, new_hi in cruncher.get_opcodes():
if tag == 'replace':
if verbose: r.append('??changed:')
r = r + _abbreviateDiffLines(old[old_lo:old_hi],'-',
MAX_OLD_LINES_DISPLAY)
r = r + _abbreviateDiffLines(new[new_lo:new_hi],'',
MAX_NEW_LINES_DISPLAY)
r.append('')
elif tag == 'delete':
if verbose: r.append('--removed:')
r = r + _abbreviateDiffLines(old[old_lo:old_hi],'-',
MAX_OLD_LINES_DISPLAY)
r.append('')
elif tag == 'insert':
if verbose: r.append('++added:')
r = r + _abbreviateDiffLines(new[new_lo:new_hi],'',
MAX_NEW_LINES_DISPLAY)
r.append('')
elif tag == 'equal':
pass
else:
raise ValueError, 'unknown tag ' + `tag`
return '\n' + join(r, '\n')
def _abbreviateDiffLines(lines, prefix, maxlines=5):
output = []
if maxlines and len(lines) > maxlines:
extra = len(lines) - maxlines
for i in xrange(maxlines - 1):
output.append(prefix + lines[i])
output.append(prefix + "[%d more line%s...]" %
(extra, ((extra == 1) and '') or 's')) # not working
else:
for line in lines:
output.append(prefix + line)
return output
=== zopeproducts/zwiki/READMDE.txt 1.3 => 1.4 ===
--- zopeproducts/zwiki/READMDE.txt:1.3 Tue Apr 8 00:15:02 2003
+++ zopeproducts/zwiki/READMDE.txt Thu Apr 10 21:37:45 2003
@@ -6,23 +6,45 @@
much more work needs to be done.
Features
+ --------
- - Wiki object. Container of all Wiki pages.
+ Rendering
- - WikiPage content object, holding the data, which turned out to be tiny.
+ - Plain Text
- - Beginnings of the PageHierarchyAdapter...no view and not tested.
+ - Structured Text (STX)
- - Rendered code recognizes Wiki names, the escaping '!' and the '[]' for
- all lower case Wiki names.
+ - reStructured Text (reST)
- - Generated Links bring you either to another Wiki page or to an add page.
- - Assinging parents, allowing to create a "virtual" hierarchy.
+ Wiki
- - Writing comments about the Wiki Page.
+ - Table of Contents
- - Somewhat sophistocated rendering mechanism. New source types and their
+ - Mail Subscription for entire Wiki
+
+ - Full-text Search
+
+
+ Wiki Page
+
+ - Proper rendering of Wiki Links
+
+ - Edit Wiki Page
+
+ - Comment on a Wiki Page
+
+ - Declare Wiki Hierarchy (Parents)
+
+ - Local, WikiPage-based Mail Subscription
+
+ - Jumping to other Wikis
+
+
+ Miscellaneous
+
+ - Somewhat sophisticated rendering mechanism. New source types and their
render methods can now be configured (added) via ZCML.
- - Implemented minimal STX support.
\ No newline at end of file
+ - A fully independent skin called 'wiki'; Note that this skill will be
+ only useful in the context of a Wiki Page.
\ No newline at end of file
=== zopeproducts/zwiki/TODO.txt 1.13 => 1.14 ===
--- zopeproducts/zwiki/TODO.txt:1.13 Thu Apr 10 07:31:38 2003
+++ zopeproducts/zwiki/TODO.txt Thu Apr 10 21:37:45 2003
@@ -9,6 +9,16 @@
- Add tests for plain text, STX, and ReST formatter.
+ - Add tests for WikiMailer
+
+ - Add tests for MailSubscriptions
+
+ - Add tests for WikiPageReadFile, WikiPageWriteFile, SearchableText
+
+ - Write tests for diff module
+
+ - Add a simple test for 'Wiki Text Index'.
+
Rendering/Views
@@ -30,7 +40,6 @@
- Check in Traverser that found subobj has self.context as parent.
- - Implement events, so that we can have E-mail subscriptions to Wiki
- changes.
-
- - Implements E-mail subscriptions.
+ - Activating diff support for edited Wiki Pages. The main issue right now
+ is to get to the old version of the text. so that we can execute the
+ Differ.
\ No newline at end of file
=== zopeproducts/zwiki/configure.zcml 1.19 => 1.20 ===
--- zopeproducts/zwiki/configure.zcml:1.19 Thu Apr 10 08:51:56 2003
+++ zopeproducts/zwiki/configure.zcml Thu Apr 10 21:37:45 2003
@@ -126,6 +126,12 @@
</content>
+ <!-- Mail Subscriptions support -->
+ <adapter
+ factory=".wikipage.MailSubscriptions"
+ provides=".interfaces.IMailSubscriptions"
+ for=".interfaces.IWiki" />
+
<content class=".wikipage.WikiPage">
@@ -151,6 +157,12 @@
provides=".interfaces.IWikiPageHierarchy"
for=".interfaces.IWikiPage" />
+ <!-- Mail Subscriptions support -->
+ <adapter
+ factory=".wikipage.MailSubscriptions"
+ provides=".interfaces.IMailSubscriptions"
+ for=".interfaces.IWikiPage" />
+
<adapter
factory=".traversal.WikiPageTraversable"
provides="zope.app.interfaces.traversing.ITraversable"
@@ -208,6 +220,15 @@
/>
</content>
+
+ <!-- Register event listener for change mails -->
+ <event:subscribe
+ subscriber=".wikipage.mailer"
+ event_types="zope.app.interfaces.event.IObjectAddedEvent
+ zope.app.interfaces.event.IObjectModifiedEvent
+ zope.app.interfaces.event.IObjectRemovedEvent
+ zope.app.interfaces.event.IObjectMovedEvent" />
+
<!-- Register the various renderers, like plain text, stx, and rest -->
<include package=".renderer" />
=== zopeproducts/zwiki/interfaces.py 1.6 => 1.7 ===
--- zopeproducts/zwiki/interfaces.py:1.6 Wed Apr 9 11:16:39 2003
+++ zopeproducts/zwiki/interfaces.py Thu Apr 10 21:37:45 2003
@@ -66,10 +66,10 @@
Pages."""
parents = List(
- title=_(u"Wiki Page Parents"),
- description=_(u"Parents of a a Wiki"),
- value_types=(TextLine(title=_(u"Parent Name"),
- description=_(u"Name of the parent wiki page.")),),
+ title = _(u"Wiki Page Parents"),
+ description = _(u"Parents of a a Wiki"),
+ value_types = (TextLine(title=_(u"Parent Name"),
+ description=_(u"Name of the parent wiki page.")),),
required=False)
def reparent(parents):
@@ -79,6 +79,19 @@
names of the parent wiki pages.
"""
+class IMailSubscriptions(Interface):
+ """This interface allows you to retrieve a list of E-mails for
+ mailings. In our context """
+
+ def getSubscriptions():
+ """Return a list of E-mails."""
+
+ def addSubscriptions(emails):
+ """Add a bunch of subscriptions, but one would be okay as well."""
+
+ def removeSubscriptions(emails):
+ """Remove a set of subscriptions."""
+
class IWikiSourceTypeService(Interface):
""" """
@@ -93,7 +106,7 @@
def getAllTitles():
"""Return a list of all titles."""
- def createObject(self, title):
+ def createObject(title):
"""Creates an object that implements the interface (note these are
just marker interfaces, so the object is minimal) that is registered
with the title passed."""
=== zopeproducts/zwiki/wiki.py 1.3 => 1.4 ===
--- zopeproducts/zwiki/wiki.py:1.3 Thu Apr 10 08:50:41 2003
+++ zopeproducts/zwiki/wiki.py Thu Apr 10 21:37:45 2003
@@ -17,7 +17,6 @@
"""
from zope.app.content.folder import Folder
from zopeproducts.zwiki.interfaces import IWiki
-from zopeproducts.zwiki.wikipage import WikiPage
class Wiki(Folder):
=== zopeproducts/zwiki/wikipage.py 1.2 => 1.3 ===
--- zopeproducts/zwiki/wikipage.py:1.2 Thu Apr 10 07:31:38 2003
+++ zopeproducts/zwiki/wikipage.py Thu Apr 10 21:37:45 2003
@@ -15,18 +15,28 @@
$Id$
"""
+import smtplib
from persistence import Persistent
from zope.component import getAdapter
from zope.proxy.context import ContextWrapper
from zope.app.traversing import getParent, objectName
+
+from zope.app.interfaces.index.text import ISearchableText
+from zope.app.interfaces.file import IReadFile, IWriteFile
from zope.app.interfaces.annotation import IAnnotations
+from zope.app.interfaces.event import ISubscriber
+from zope.app.interfaces.event import IObjectAddedEvent, IObjectModifiedEvent
+from zope.app.interfaces.event import IObjectRemovedEvent, IObjectMovedEvent
+
-from zopeproducts.zwiki.interfaces import IWikiPage, IWikiPageHierarchy
+from zopeproducts.zwiki.interfaces import \
+ IWiki, IWikiPage, IWikiPageHierarchy, IMailSubscriptions
__metaclass__ = type
HierarchyKey = 'http://www.zope.org/zwiki#1.0/PageHierarchy/parents'
+SubscriberKey = 'http://www.zope.org/zwiki#1.0/MailSubscriptions/emails'
class WikiPage(Persistent):
@@ -75,7 +85,6 @@
self.setParents(parents)
def setParents(self, parents):
- data = self._annotations.get(HierarchyKey)
self._annotations[HierarchyKey] = tuple(parents)
def getParents(self):
@@ -116,30 +125,42 @@
# Adapters for file-system style access
class WikiPageReadFile:
+ """Adapter for letting a Wiki Page look like a regular readable file."""
+
+ __implements__ = IReadFile
+ __used_for__ = IWikiPage
def __init__(self, context):
self.context = context
def read(self):
+ """See zope.app.interfaces.file.IReadFile"""
return self.context.source
def size(self):
+ """See zope.app.interfaces.file.IReadFile"""
return len(self.context.source)
+
class WikiPageWriteFile:
+ """Adapter for letting a Wiki Page look like a regular writable file."""
+ __implements__ = IWriteFile
+ __used_for__ = IWikiPage
+
def __init__(self, context):
self.context = context
def write(self, data):
+ """See zope.app.interfaces.file.IWriteFile"""
self.context.source = unicode(data)
# Adapter for ISearchableText
-from zope.app.interfaces.index.text import ISearchableText
-
class SearchableText:
+ """This adapter provides an API that allows the Wiki Pages to be indexed
+ by the Text Index."""
__implements__ = ISearchableText
__used_for__ = IWikiPage
@@ -149,3 +170,105 @@
def getSearchableText(self):
return [unicode(self.page.source)]
+
+
+# Component to fullfill mail subscriptions
+
+class MailSubscriptions:
+ """An adapter for WikiPages to provide an interface for collecting E-mails
+ for sending out change notices."""
+
+ __implements__ = IMailSubscriptions
+ __used_for__ = IWikiPage, IWiki
+
+ def __init__(self, context):
+ self.context = context
+ self._annotations = getAdapter(context, IAnnotations)
+ if not self._annotations.get(SubscriberKey):
+ self._annotations[SubscriberKey] = ()
+
+ def getSubscriptions(self):
+ "See zopeproducts.zwiki.interfaces.IMailSubscriptions"
+ return self._annotations[SubscriberKey]
+
+ def addSubscriptions(self, emails):
+ "See zopeproducts.zwiki.interfaces.IMailSubscriptions"
+ subscribers = list(self._annotations[SubscriberKey])
+ for email in emails:
+ if email not in subscribers:
+ subscribers.append(email.strip())
+ self._annotations[SubscriberKey] = tuple(subscribers)
+
+ def removeSubscriptions(self, emails):
+ "See zopeproducts.zwiki.interfaces.IMailSubscriptions"
+ subscribers = list(self._annotations[SubscriberKey])
+ for email in emails:
+ if email in subscribers:
+ subscribers.remove(email)
+ self._annotations[SubscriberKey] = tuple(subscribers)
+
+
+
+class WikiMailer:
+ """Class to handle all outgoing mail."""
+
+ __implements__ = ISubscriber
+
+ def __init__(self, host="localhost", port="25"):
+ """Initialize the the object."""
+ self.host = host
+ self.port = port
+
+ def notify(self, event):
+ """See zope.app.interfaces.event.ISubscriber"""
+ if IWikiPage.isImplementedBy(event.object):
+ if IObjectAddedEvent.isImplementedBy(event):
+ self.handleAdded(event.object)
+
+ elif IObjectModifiedEvent.isImplementedBy(event):
+ self.handleModified(event.object)
+
+ elif IObjectRemovedEvent.isImplementedBy(event):
+ self.handleRemoved(event.object)
+
+ def handleAdded(self, object):
+ subject = 'Added: '+objectName(object)
+ emails = self.getAllSubscribers(object)
+ body = object.source
+ self.mail(emails, subject, body)
+
+ def handleModified(self, object):
+ # XXX: Should have some nice diff code here.
+ # from diff import textdiff
+ subject = 'Modified: '+objectName(object)
+ emails = self.getAllSubscribers(object)
+ body = object.source
+ self.mail(emails, subject, body)
+
+ def handleRemoved(self, object):
+ subject = 'Removed: '+objectName(object)
+ emails = self.getAllSubscribers(object)
+ body = subject
+ self.mail(emails, subject, body)
+
+ def getAllSubscribers(self, object):
+ """Retrieves all email subscribers by looking into the local Wiki Page
+ and into the Wiki for the global subscriptions."""
+ emails = tuple(getAdapter(object,
+ IMailSubscriptions).getSubscriptions())
+ emails += tuple(getAdapter(getParent(object),
+ IMailSubscriptions).getSubscriptions())
+ return emails
+
+ def mail(self, emails, subject, body):
+ """Mail out the Wiki change message."""
+ if not emails:
+ return
+ msg = 'Subject: %s\n\n\n%s' %(subject, body)
+ server = smtplib.SMTP(self.host, self.port)
+ server.set_debuglevel(0)
+ server.sendmail('wiki@zope3.org', emails, msg)
+ server.quit()
+
+# Create a global mailer object.
+mailer = WikiMailer()