[Checkins] SVN: Produts.RecentItemsIndex/trunk/ Eggified, ported for compatibility with Zope 2.12.

Tres Seaver tseaver at palladion.com
Tue Mar 16 18:10:38 EDT 2010


Log message for revision 110000:
  Eggified, ported for compatibility with Zope 2.12.
  
  Added skeleton for Sphinx documentation.
  

Changed:
  _U  Produts.RecentItemsIndex/trunk/
  A   Produts.RecentItemsIndex/trunk/Products/
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py
  A   Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt
  A   Produts.RecentItemsIndex/trunk/Products/__init__.py
  U   Produts.RecentItemsIndex/trunk/README.txt
  D   Produts.RecentItemsIndex/trunk/__init__.py
  A   Produts.RecentItemsIndex/trunk/bootstrap.py
  A   Produts.RecentItemsIndex/trunk/buildout.cfg
  A   Produts.RecentItemsIndex/trunk/docs/
  A   Produts.RecentItemsIndex/trunk/docs/CHANGES.rst
  A   Produts.RecentItemsIndex/trunk/docs/Makefile
  A   Produts.RecentItemsIndex/trunk/docs/_build/
  A   Produts.RecentItemsIndex/trunk/docs/_static/
  A   Produts.RecentItemsIndex/trunk/docs/_templates/
  A   Produts.RecentItemsIndex/trunk/docs/conf.py
  A   Produts.RecentItemsIndex/trunk/docs/index.rst
  D   Produts.RecentItemsIndex/trunk/index.py
  A   Produts.RecentItemsIndex/trunk/setup.py
  D   Produts.RecentItemsIndex/trunk/test.py
  D   Produts.RecentItemsIndex/trunk/version.txt

-=-

Property changes on: Produts.RecentItemsIndex/trunk
___________________________________________________________________
Added: svn:ignore
   + bin
develop-eggs
eggs
parts
*.egg-info
.installed.cfg


Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py (from rev 109997, Produts.RecentItemsIndex/trunk/__init__.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2002 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.
+#
+##############################################################################
+"""RecentItemsIndex Zope product
+
+A ZCatalog plug-in-index for indexing recent items matching a specific field
+value.
+
+$Id: __init__.py,v 1.1.1.1 2004/07/19 17:46:21 caseman Exp $"""
+
+import index
+
+def initialize(context):
+
+    context.registerClass(
+        index.RecentItemsIndex,
+        meta_type='RecentItemsIndex',
+        permission='Add Pluggable Index',
+        constructors=(index.addIndexForm,),
+        icon='www/index.gif',
+        visibility=None
+    )


Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py
___________________________________________________________________
Added: svn:mergeinfo
   + 

Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py (from rev 109997, Produts.RecentItemsIndex/trunk/index.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,313 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""ZCatalog index for efficient queries of the most recent items
+"""
+import types
+
+from zope.interface import implements
+from AccessControl import Permissions
+from AccessControl.PermissionRole import rolesForPermissionOn
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from Acquisition import aq_inner
+from Acquisition import aq_parent
+from BTrees.OOBTree import OOBTree
+from BTrees.IOBTree import IOBTree
+from BTrees.OIBTree import OIBucket
+from BTrees.Length import Length
+from App.special_dtml import DTMLFile
+from App.class_init import InitializeClass
+from OFS.SimpleItem import SimpleItem
+from Products.PluginIndexes.interfaces import IPluggableIndex
+from Products.ZCatalog.Lazy import LazyMap
+from zLOG import LOG
+from zLOG import WARNING
+    
+_marker = []
+
+def _getSourceValue(obj, attrname):
+    """Return the data to be indexed for obj"""
+    value = getattr(obj, attrname)
+    try:
+        # Try calling it
+        value = value()
+    except (TypeError, AttributeError):
+        pass
+    return value
+
+class RecentItemsIndex(SimpleItem):
+    """Recent items index"""
+
+    implements(IPluggableIndex)
+    
+    meta_type = 'Recent Items Index'
+
+    # class default;  instances get Length() in their clear()
+    numObjects = lambda: 0
+
+    manage_options = (
+        {'label': 'Overview', 'action': 'manage_main'},
+    )
+    
+    manage_main = DTMLFile('www/manageIndex', globals())
+    
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(Permissions.manage_zcatalog_indexes)
+    
+    def __init__(
+        self, id, field_name=None, date_name=None, max_length=None, 
+        guard_roles=None, guard_permission=None, extra=None, caller=None):
+        """Recent items index constructor
+        
+        id -- Zope id for index in
+        
+        field_name -- Name of attribute used to classify the objects. A
+        recent item list is created for each value of this field indexed.
+        If this value is omitted, then a single recent item list for all
+        cataloged objects is created.
+        
+        date_name -- Name of attribute containing a date which specifies the
+        object's age.
+        
+        max_length -- Maximum length of each recent items list.
+        
+        guard_roles -- A list of one or more roles that must be granted the
+        guard permission in order for an object to be indexed. Ignored if
+        no guard_permission value is given.
+        
+        guard_permission -- The permission that must be granted to the
+        guard roles for an object in order for it to be indexed. Ignored if
+        no guard_roles value is given.
+        
+        extra and caller are used by the wonderous ZCatalog addIndex 
+        machinery. You can ignore them, unfortunately I can't 8^/
+        """
+        self.id = id
+        self.field_name = field_name or getattr(extra, 'field_name', None)
+        self.date_name = date_name or extra.date_name
+        self.max_length = max_length or extra.max_length
+        assert self.max_length > 0, 'Max item length value must be 1 or greater'
+        if guard_roles is None:
+            guard_roles = getattr(extra, 'guard_roles', None)
+        if guard_permission is None:
+            guard_permission = getattr(extra, 'guard_permission', None)
+        if guard_permission is not None and guard_roles:
+            self.guard_permission = guard_permission
+            self.guard_roles = tuple(guard_roles)
+        else:
+            self.guard_permission = self.guard_roles = None
+        self.clear()
+    
+    ## Index specific methods ##
+
+    def getIndexSourceNames(self):
+        """ See IPluggableIndex.
+        """
+        return (self.field_name,)
+
+    def indexSize(self):
+        """ See IPluggableIndex.
+        """
+        return len(self._value2items)
+    
+    def getItemCounts(self):
+        """Return a dict of field value => item count"""
+        counts = {}
+        for value, items in self._value2items.items():
+            counts[value] = len(items)
+        return counts
+        
+    def query(self, value=None, limit=None, merge=1):
+        """Return a lazy sequence of catalog brains like a catalog search
+        that coorespond to the most recent items for the value(s) given.
+        If value is omitted, then the most recent for all values are returned.
+        The result are returned in order, newest first. An integer value
+        can be specified in limit which restricts the maximum number of
+        results if desired. If no limit is specified, the indexes'
+        maximum length is used as the limit
+        """
+        catalog = aq_parent(aq_inner(self))
+        if value is None and self.field_name is not None:
+            # Query all values
+            value = list(self._value2items.keys())
+        elif value is not None and self.field_name is None:
+            # Ignore value given if there is no classifier field
+            value = None
+        if isinstance(value, (types.TupleType, types.ListType)):
+            # Query for multiple values
+            results = []
+            for fieldval in value:
+                try:
+                    itempairs = self._value2items[fieldval].keys()
+                except KeyError:
+                    pass
+                else:
+                    results.extend(itempairs)
+            results.sort()
+            if merge:
+                results = [rid for date, rid in results]
+            else:
+                # Create triples expected by mergeResults()
+                results = [(date, rid, catalog.__getitem__)
+                           for date, rid in results]
+        else:
+            # Query for single value
+            try:
+                items = self._value2items[value]                    
+            except KeyError:
+                results = []
+            else:
+                if merge:
+                    results = items.values()
+                else:
+                    # Create triples expected by mergeResults()
+                    results = [(date, rid, catalog.__getitem__)
+                               for date, rid in items.keys()]
+        results.reverse()
+        if limit is not None:
+            results = results[:limit]
+        if merge:
+            return LazyMap(catalog.__getitem__, results, len(results))
+        else:
+            return results
+    
+    ## Pluggable Index API ##
+    
+    def index_object(self, docid, obj, theshold=None):
+        """Add document to index"""
+        if self.guard_permission is not None and self.guard_roles:
+            allowed_roles = rolesForPermissionOn(self.guard_permission, obj)
+            for role in allowed_roles:
+                if role in self.guard_roles:
+                    break
+            else:
+                # Object does not have proper permission grant
+                # to be in the index
+                self.unindex_object(docid)
+                return 0
+        try:
+            if self.field_name is not None:
+                fieldvalue = _getSourceValue(obj, self.field_name)
+            else:
+                fieldvalue = None
+            datevalue = _getSourceValue(obj, self.date_name)
+        except AttributeError:
+            # One or the other source attributes is missing
+            # unindex the object and bail
+            self.unindex_object(docid)
+            return 0
+        datevalue = datevalue.timeTime()
+        entry = self.getEntryForObject(docid)
+        if (entry is None or fieldvalue != entry['value'] 
+            or datevalue != entry['date']):
+            # XXX Note that setting the date older than a previously pruned
+            #     object will result in an incorrect index state. This may
+            #     present a problem if dates are changed arbitrarily 
+            if entry is None:
+                self.numObjects.change(1)
+            else:
+                # unindex existing entry
+                self.unindex_object(docid)
+            self._rid2value[docid] = fieldvalue
+            try:
+                items = self._value2items[fieldvalue]
+            except KeyError:
+                # Unseen value, create a new items bucket
+                items = self._value2items[fieldvalue] = OIBucket()
+            items[datevalue, docid] = docid
+            while len(items) > self.max_length:
+                # Prune the oldest items
+                olddate, oldrid = items.minKey()
+                # Unindex by hand to avoid theoretical infinite loops
+                self.numObjects.change(-1)
+                del items[olddate, oldrid]
+                if not items:
+                    # Not likely, unless max_length is 1
+                    del self._value2items[fieldvalue]
+                try:
+                    del self._rid2value[oldrid]
+                except KeyError:
+                    LOG('RecentItemsIndex', WARNING, 
+                        'Could not unindex field value for %s.' % oldrid)
+            return 1
+        else:
+            # Index is up to date, nothing to do
+            return 0
+    
+    def unindex_object(self, docid):
+        """Remove docid from the index. If docid is not in the index,
+        do nothing"""
+        try:
+            fieldvalue = self._rid2value[docid]
+        except KeyError:
+            return 0 # docid not in index
+        self.numObjects.change(-1)
+        del self._rid2value[docid]
+        items = self._value2items[fieldvalue]
+        for date, rid in items.keys():
+            if rid == docid:
+                del items[date, rid]
+                if not items:
+                    del self._value2items[fieldvalue]
+                return 1
+        return 1
+    
+    def _apply_index(self, request, cid=''):
+        """We do not play in normal catalog queries"""
+        return None
+        
+    def getEntryForObject(self, docid, default=None):
+        """Return a dict containing the field value and date for
+        docid, or default if it is not in the index. The returned dict
+        has the keys 'value' and 'date'
+        """
+        try:
+            fieldvalue = self._rid2value[docid]
+        except KeyError:
+            return default
+        for date, rid in self._value2items[fieldvalue].keys():
+            if rid == docid:
+                return {'value':fieldvalue, 'date':date}
+        # If we get here then _rid2values is inconsistent with _value2items
+        LOG('RecentItemsIndex', WARNING, 
+            'Field value found for item %s, but no date. '
+            'This should not happen.' % docid)
+        return default
+    
+    def hasUniqueValuesFor(self, name):
+        """Return true if the index holds the unique values for name"""
+        return name == self.field_name
+    
+    def uniqueValues(self, name=None):
+        """Return the unique field values indexed"""
+        if name is None:
+            name = self.field_name
+        if name == self.field_name:
+            return self._value2items.keys()
+        else:
+            return []
+        
+    ## ZCatalog ZMI methods ##
+
+    def clear(self):
+        """reinitialize the index"""
+        self.numObjects = Length()
+        # Mapping field value => top items
+        self._value2items = OOBTree()
+        # Mapping indexed rid => field value for unindexing
+        self._rid2value = IOBTree()
+    
+
+InitializeClass(RecentItemsIndex)
+
+addIndexForm = DTMLFile('www/addIndex', globals())


Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py
___________________________________________________________________
Added: svn:mergeinfo
   + 

Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py (from rev 109997, Produts.RecentItemsIndex/trunk/test.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,504 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Tests for Products.RecentItemsIndex.index
+"""
+import unittest
+
+class RecentItemsIndexTest(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.RecentItemsIndex.index import RecentItemsIndex
+        return RecentItemsIndex
+
+    def _makeOne(self,
+                 id='test',
+                 field_name='type',
+                 date_name='date',
+                 max_length=10,
+                 *args, **kw):
+        catalog = self._makeCatalog()
+        index = self._getTargetClass()(id, field_name, date_name, max_length,
+                                        *args, **kw)
+        return index.__of__(catalog)
+
+    def _makeDoc(self, **kw):
+
+        class Doc:
+            def __init__(self, **kw):
+                self.__dict__.update(kw)
+        return Doc(**kw)
+
+    def _makeCatalog(self):
+        from OFS.SimpleItem import SimpleItem
+
+        class DummyCatalog(SimpleItem):
+
+            def __init__(self):
+                self.docs = {}
+
+            def __getitem__(self, item):
+                return self.docs[item]
+
+        return DummyCatalog()
+
+    def _makeViewable(self, id='test_viewable'):
+        from OFS.SimpleItem import SimpleItem
+        from DateTime.DateTime import DateTime
+
+        class Viewable(SimpleItem):
+
+            date = DateTime('2/21/2004')
+            type = 'Viewable'
+
+            def __init__(self, role=None):
+                self._addRole(role)
+                if role is not None:
+                    self.manage_permission('View', [role])
+
+        return Viewable(id)
+
+    def _makeAndIndexOneDoc(self, index):
+        from DateTime.DateTime import DateTime
+        doc = self._makeDoc(type='fluke', date=DateTime('1/1/2004'))
+        return index.index_object(1, doc)
+
+    def _makeAndIndexDocs(self, index):
+        from Acquisition import aq_parent
+        from DateTime.DateTime import DateTime
+        types = ['huey', 'dooey', 'looey', 'dooey'] * 15
+        date = DateTime('1/1/2004')
+        docs = {}
+        for docid, typ in zip(range(len(types)), types):
+            if not docid % 3:
+                date = date + 1
+            if not docid % 7:
+                date = date - (docid % 3)
+            doc = docs[docid] = self._makeDoc(docid=docid, type=typ, date=date)
+            index.index_object(docid, doc)
+        aq_parent(index).docs = docs
+        return docs
+
+    def test_class_conforms_to_IPluggableIndex(self):
+        from zope.interface.verify import verifyClass
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        verifyClass(IPluggableIndex, self._getTargetClass())
+
+    def test_instance_conforms_to_IPluggableIndex(self):
+        from zope.interface.verify import verifyObject
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        index = self._makeOne()
+        verifyObject(IPluggableIndex, index)
+
+    def test_construct_with_extra(self):
+        # Simulate instantiating from ZCatalog
+        class extra:
+            field_name = 'bruford'
+            date_name = 'wakeman'
+            max_length = 25
+            guard_roles = ['Anonymous']
+            guard_permission = 'View'
+        index = self._getTargetClass()('extra', extra=extra)
+        self.assertEqual(index.getId(), 'extra')
+        self.assertEqual(index.field_name, 'bruford')
+        self.assertEqual(index.date_name, 'wakeman')
+        self.assertEqual(index.max_length, 25)
+        self.assertEqual(tuple(index.guard_roles), ('Anonymous',))
+        self.assertEqual(index.guard_permission, 'View')
+
+    def test_construct_with_no_classifier_or_guard(self):
+        # Simulate instantiating from ZCatalog
+        class extra:
+            date_name = 'modified'
+            max_length = 30
+        index = self._getTargetClass()('nuttin', extra=extra)
+        self.assertEqual(index.getId(), 'nuttin')
+        self.assertEqual(index.date_name, 'modified')
+        self.assertEqual(index.max_length, 30)
+
+    def test_construct_with_bogus_max_length(self):
+        self.assertRaises(
+            Exception, self._getTargetClass(), 'test', 'type', 'date', 0)
+        self.assertRaises(
+            Exception, self._getTargetClass(), 'test', 'type', 'date', -20)
+
+    def test_index_object_skips_obj_without_field_or_date(self):
+        index = self._makeOne()
+        doc = self._makeDoc()
+        self.failIf(index.index_object(1, doc))
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+
+    def test_index_object_skips_obj_without_date(self):
+        index = self._makeOne()
+        doc = self._makeDoc(type='cheetos')
+        self.failIf(index.index_object(1, doc))
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+
+    def test_index_object_skips_obj_without_field(self):
+        from DateTime.DateTime import DateTime
+        index = self._makeOne()
+        doc = self._makeDoc(date=DateTime('4/17/2004'))
+        self.failIf(index.index_object(1, doc))
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+
+    def test_index_single(self):
+        index = self._makeOne()
+        result = self._makeAndIndexOneDoc(index)
+        self.failUnless(result)
+        self.assertEqual(index.numObjects(), 1)
+        self.assertEqual(index.getItemCounts(), {'fluke': 1})
+
+    def test_unindex_single(self):
+        index = self._makeOne()
+        result = self._makeAndIndexOneDoc(index)
+        self.failUnless(index.unindex_object(1))
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+
+    def test_index_many(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        maxlen = index.max_length
+        self.assertEqual(index.getItemCounts(),
+                         {'huey': maxlen, 'dooey':maxlen, 'looey':maxlen})
+        self.assertEqual(index.numObjects(), maxlen*3)
+
+    def test_index_many_no_classifier(self):
+        index = self._makeOne('test', None, 'date', 10)
+        docs = self._makeAndIndexDocs(index)
+        maxlen = index.max_length
+        self.assertEqual(index.getItemCounts(), {None: maxlen,})
+        self.assertEqual(index.numObjects(), maxlen)
+
+    def test_unindex_one_type(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        for docid, doc in docs.items():
+            if doc.type == 'looey':
+                index.unindex_object(docid)
+        self.assertEqual(index.numObjects(), 20)
+        self.assertEqual(index.getItemCounts(), {'huey': 10, 'dooey':10})
+
+    def test_unindex_all(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        for docid in docs.keys():
+            index.unindex_object(docid)
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+        self.assertEqual(list(index.uniqueValues()), [])
+
+    def _get_top_docs(self, docs):
+        top = {'huey':[], 'dooey':[], 'looey':[]}
+        for doc in docs.values():
+            top[doc.type].append((doc.date.timeTime(), doc.docid))
+        for typ, docs in top.items():
+            docs.sort()
+            top[typ] = docs[-10:]
+        return top
+
+    def test_getEntryForObject(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        for docid, doc in docs.items():
+            entry = index.getEntryForObject(docid)
+            if entry is not None:
+                self.assertEqual(entry,
+                    {'value': doc.type, 'date': doc.date.timeTime()})
+            else:
+                self.failIf((doc.date.timeTime(), doc.docid) in top[doc.type])
+
+    def test_unindex_most_recent(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        item_counts = index.getItemCounts()
+        total_count = 30
+        for i in range(10):
+            for typ in ('huey', 'dooey', 'looey'):
+                nil, byebyeid = top[typ].pop()
+                self.failUnless(index.unindex_object(byebyeid))
+                item_counts[typ] -= 1
+                if not item_counts[typ]:
+                    del item_counts[typ]
+                total_count -= 1
+                self.assertEqual(index.getItemCounts(), item_counts)
+                self.assertEqual(index.numObjects(), total_count)
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.getItemCounts(), {})
+
+    def test_unindex_bogus_rid(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        self.failIf(index.unindex_object(-2000))
+
+    def _get_indexed_doc(self, index, fromtop=0):
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        items = docs.items()
+        if fromtop:
+            items.reverse()
+        for docid, doc in items:
+            entry = index.getEntryForObject(docid)
+            if entry is not None:
+                break
+        else:
+            self.fail('No objects in index')
+        self.assertEqual(entry, {'value':doc.type, 'date':doc.date.timeTime()})
+        return doc
+
+    def test_reindex_no_change(self):
+        # reindex with no change should be a no-op
+        index = self._makeOne()
+        doc = self._get_indexed_doc(index)
+        self.failIf(index.index_object(doc.docid, doc))
+        self.assertEqual(index.getEntryForObject(doc.docid),
+                         {'value':doc.type, 'date':doc.date.timeTime()})
+
+    def test_reindex_change_date(self):
+        index = self._makeOne()
+        doc = self._get_indexed_doc(index)
+        doc.date = doc.date + 10
+        self.failUnless(index.index_object(doc.docid, doc))
+        self.assertEqual(index.getEntryForObject(doc.docid),
+                         {'value':doc.type, 'date':doc.date.timeTime()})
+
+    def test_reindex_change_value(self):
+        index = self._makeOne()
+        doc = self._get_indexed_doc(index, fromtop=1)
+        oldtype = doc.type
+        for typ in index.uniqueValues():
+            if typ != oldtype:
+                doc.type = typ
+                break
+        self.failUnless(index.index_object(doc.docid, doc))
+        self.assertEqual(index.getEntryForObject(doc.docid),
+                         {'value':doc.type, 'date':doc.date.timeTime()})
+
+    def test_reindex_change_date_and_value(self):
+        index = self._makeOne()
+        doc = self._get_indexed_doc(index, fromtop=1)
+        doc.date = doc.date + 4
+        oldtype = doc.type
+        for typ in index.uniqueValues():
+            if typ != oldtype:
+                doc.type = typ
+                break
+        self.failUnless(index.index_object(doc.docid, doc))
+        self.assertEqual(index.getEntryForObject(doc.docid),
+                         {'value':doc.type, 'date':doc.date.timeTime()})
+
+    def test_query_empty_index(self):
+        index = self._makeOne()
+        result = index.query('foobar')
+        self.failIf(result)
+
+    def test_simple_query(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query('huey')
+        expected = [docid for nil, docid in top['huey']]
+        expected.reverse()
+        self.assertEqual([doc.docid for doc in result], expected)
+
+    def test_query_bogus_value(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        self.failIf(index.query('snacks'))
+
+    def test_query_limit(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query('huey', limit=3)
+        expected = [docid for nil, docid in top['huey']]
+        expected.reverse()
+        expected = expected[:3]
+        self.assertEqual([doc.docid for doc in result], expected)
+
+    def test_query_no_merge(self):
+        from Acquisition import aq_parent
+        index = self._makeOne()
+        catalog = aq_parent(index)
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query('dooey', merge=0)
+        expected = [(date, docid, catalog.__getitem__)
+                    for date, docid in top['dooey']]
+        expected.reverse()
+        for rrow, erow in zip(result, expected):
+           self.assertEqual(rrow[:2], erow[:2])
+
+    def _getExpectedTopDocs(self, index, docs, limit=None):
+        top = self._get_top_docs(docs)
+        query = ['huey', 'dooey']
+        if limit is None:
+            result = index.query(query)
+        else:
+            result = index.query(query, limit=limit)
+        expected = top['huey'] + top['dooey']
+        expected.sort()
+        expected = [docid for nil, docid in expected]
+        expected.reverse()
+        return result, expected
+
+    def test_query_multiple_values(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        result, expected = self._getExpectedTopDocs(index, docs)
+        self.assertEqual([doc.docid for doc in result], expected)
+
+    def test_query_all_values(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query()
+        expected = top['huey'] + top['dooey'] + top['looey']
+        expected.sort()
+        expected = [docid for nil, docid in expected]
+        expected.reverse()
+        self.assertEqual([doc.docid for doc in result], expected)
+        return expected
+
+    def test_query_no_classifier(self):
+        index = self._makeOne('test', None, 'date', 10)
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query()
+        expected = top['huey'] + top['dooey'] + top['looey']
+        expected.sort()
+        expected = [docid for nil, docid in expected]
+        expected.reverse()
+        self.assertEqual([doc.docid for doc in result], expected[:10])
+
+    def test_query_no_classifier_ignores_value(self):
+        index = self._makeOne('test', None, 'date', 10)
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query('ptooey')
+        expected = top['huey'] + top['dooey'] + top['looey']
+        expected.sort()
+        expected = [docid for nil, docid in expected]
+        expected.reverse()
+        self.assertEqual([doc.docid for doc in result], expected[:10])
+
+    def test_query_multiple_with_tuple(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        result, expected = self._getExpectedTopDocs(index, docs)
+        self.assertEqual([doc.docid for doc in result], expected)
+
+    def test_query_multiple_bogus_values(self):
+        index = self._makeOne()
+        self.failIf(index.query(['fooey', 'blooey']))
+        result = index.query(['blooey', 'looey'])
+        expected = index.query('looey')
+        self.assertEqual(list(result), list(expected))
+
+    def test_query_multiple_limit(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        result, expected = self._getExpectedTopDocs(index, docs, limit=4)
+        expected = expected[:4]
+        self.assertEqual([doc.docid for doc in result], expected)
+
+    def test_query_multiple_no_merge(self):
+        from Acquisition import aq_parent
+        index = self._makeOne()
+        catalog = aq_parent(index)
+        docs = self._makeAndIndexDocs(index)
+        top = self._get_top_docs(docs)
+        result = index.query(['dooey', 'huey'], merge=0)
+        expected = [(date, docid, catalog.__getitem__)
+                    for date, docid in top['huey'] + top['dooey']]
+        expected.sort()
+        expected.reverse()
+        for rrow, erow in zip(result, expected):
+           self.assertEqual(rrow[:2], erow[:2])
+
+    def test_apply_index(self):
+        # _apply_index always returns none since recent items index
+        # do not participate in the normal ZCatalog query as they
+        # handle both intersection and sorting
+        index = self._makeOne()
+        self.failUnless(index._apply_index({}) is None)
+        self.failUnless(index._apply_index({'query':'looey'}) is None)
+
+    def test_uniqueValues(self):
+        index = self._makeOne()
+        self.failIf(index.uniqueValues('type'))
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        values = list(index.uniqueValues('type'))
+        values.sort()
+        self.assertEqual(values, ['dooey', 'huey', 'looey'])
+        self.failIf(index.uniqueValues('carbtastic'))
+
+    def test_hasUniqueValuesFor(self):
+        index = self._makeOne()
+        self.failUnless(index.hasUniqueValuesFor('type'))
+        self.failIf(index.hasUniqueValuesFor('spork'))
+
+    def test_numObjects(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        self.assertEqual(index.numObjects(), 30)
+
+    def test_numObjects_small_maxlen(self):
+        index = self._makeOne()
+        index.max_length = 1
+        docs = self._makeAndIndexDocs(index)
+        self.assertEqual(index.numObjects(), 3)
+
+    def test_numObjects_empty_index(self):
+        index = self._makeOne()
+        self.assertEqual(index.numObjects(), 0)
+
+    def test_clear(self):
+        index = self._makeOne()
+        docs = self._makeAndIndexDocs(index)
+        self.failUnless(index.numObjects())
+        index.clear()
+        self.assertEqual(index.numObjects(), 0)
+
+    def test_role_permission_guard(self):
+        index = self._makeOne(
+            'test', 'type', 'date', 5, ['NerfHerder', 'Bloke'], 'View')
+        viewable = self._makeViewable('NerfHerder')
+        index.index_object(0, viewable)
+        self.assertEqual(index.numObjects(), 1)
+        notviewable = self._makeViewable()
+        index.index_object(1, notviewable)
+        self.assertEqual(index.numObjects(), 1)
+        bloke = self._makeViewable('Bloke')
+        index.index_object(2, bloke)
+        self.assertEqual(index.numObjects(), 2)
+        bloke.manage_permission('View', [])
+        index.index_object(2, bloke)
+        self.assertEqual(index.numObjects(), 1)
+        dummy = self._makeViewable('Dummy')
+        index.index_object(3, dummy)
+        self.assertEqual(index.numObjects(), 1)
+        viewable.manage_permission('View', [])
+        index.index_object(0, viewable)
+        self.assertEqual(index.numObjects(), 0)
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(RecentItemsIndexTest),
+    ))


Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py
___________________________________________________________________
Added: svn:mergeinfo
   + 

Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt (from rev 109997, Produts.RecentItemsIndex/trunk/version.txt)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1 @@
+0.1


Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt
___________________________________________________________________
Added: svn:mergeinfo
   + 

Added: Produts.RecentItemsIndex/trunk/Products/__init__.py
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/__init__.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/__init__.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

Modified: Produts.RecentItemsIndex/trunk/README.txt
===================================================================
--- Produts.RecentItemsIndex/trunk/README.txt	2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/README.txt	2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,21 +1,20 @@
-Recent Items Index
+Products.RecentItemsIndex README
+================================
 
-  This index is designed to optimize queries which ask for the most
-  recent objects that match a certain value for an attribute. The designed
-  usage is a query for the most recent objects of a particular portal
-  type.
-  
-  The index only retains up to a fixed number of items for each field 
-  value which means that the performance of queries using the index
-  are independant of the size of the catalog.
-  
-  The index also has a custom query interface so that applications
-  may query it directly for greatest efficiency since it handles both
-  the result selection and sorting simultaneously.
-  
-  At the moment the index is not searchable through the standard ZCatalog
-  'searchResults()' API. This is because the catalog does not yet support
-  indexes that can do searching and sorting simultaneously as this one
-  does.
+This product provides a ZCatalog index designed to optimize queries which
+ask for the most recent objects that match a certain value for an attribute.
+The designed usage is a query for the most recent objects of a particular
+portal type.
 
-  
+The index only retains up to a fixed number of items for each field 
+value which means that the performance of queries using the index
+are independant of the size of the catalog.
+
+The index also has a custom query interface so that applications
+may query it directly for greatest efficiency since it handles both
+the result selection and sorting simultaneously.
+
+At the moment the index is not searchable through the standard ZCatalog
+``searchResults()`` API. This is because the catalog does not yet support
+indexes that can do searching and sorting simultaneously as this one
+does.

Deleted: Produts.RecentItemsIndex/trunk/__init__.py
===================================================================
--- Produts.RecentItemsIndex/trunk/__init__.py	2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/__init__.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,32 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 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.
-#
-##############################################################################
-"""RecentItemsIndex Zope product
-
-A ZCatalog plug-in-index for indexing recent items matching a specific field
-value.
-
-$Id: __init__.py,v 1.1.1.1 2004/07/19 17:46:21 caseman Exp $"""
-
-import index
-
-def initialize(context):
-
-    context.registerClass(
-        index.RecentItemsIndex,
-        meta_type='RecentItemsIndex',
-        permission='Add Pluggable Index',
-        constructors=(index.addIndexForm,),
-        icon='www/index.gif',
-        visibility=None
-    )

Added: Produts.RecentItemsIndex/trunk/bootstrap.py
===================================================================
--- Produts.RecentItemsIndex/trunk/bootstrap.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/bootstrap.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+from optparse import OptionParser
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+# parsing arguments
+parser = OptionParser()
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="distribute", default=False,
+                   help="Use Disribute rather than Setuptools.")
+
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout' main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.version is not None:
+    VERSION = '==%s' % options.version
+else:
+    VERSION = ''
+
+USE_DISTRIBUTE = options.distribute
+args = args + ['bootstrap']
+
+to_reload = False
+try:
+    import pkg_resources
+    if not hasattr(pkg_resources, '_distribute'):
+        to_reload = True
+        raise ImportError
+except ImportError:
+    ez = {}
+    if USE_DISTRIBUTE:
+        exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
+                         ).read() in ez
+        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
+    else:
+        exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                             ).read() in ez
+        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    if to_reload:
+        reload(pkg_resources)
+    else:
+        import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+
+if USE_DISTRIBUTE:
+    requirement = 'distribute'
+else:
+    requirement = 'setuptools'
+
+if is_jython:
+    import subprocess
+
+    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
+           quote(tmpeggs), 'zc.buildout' + VERSION],
+           env=dict(os.environ,
+               PYTHONPATH=
+               ws.find(pkg_resources.Requirement.parse(requirement)).location
+               ),
+           ).wait() == 0
+
+else:
+    assert os.spawnle(
+        os.P_WAIT, sys.executable, quote (sys.executable),
+        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
+        dict(os.environ,
+            PYTHONPATH=
+            ws.find(pkg_resources.Requirement.parse(requirement)).location
+            ),
+        ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout' + VERSION)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+shutil.rmtree(tmpeggs)

Added: Produts.RecentItemsIndex/trunk/buildout.cfg
===================================================================
--- Produts.RecentItemsIndex/trunk/buildout.cfg	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/buildout.cfg	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,27 @@
+[buildout]
+develop = .
+parts =
+    test
+    zopepy
+    docs
+
+unzip = true
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = Products.RecentItemsIndex
+
+
+[zopepy]
+recipe = zc.recipe.egg
+eggs =
+    Zope2
+    Products.RecentItemsIndex
+interpreter = zopepy
+scripts = zopepy
+
+
+[docs]
+recipe = zc.recipe.egg
+eggs = Sphinx

Added: Produts.RecentItemsIndex/trunk/docs/CHANGES.rst
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/CHANGES.rst	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/CHANGES.rst	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,14 @@
+Products.RecentItemsIndex Changelog
+===================================
+
+0.1 (unreleased)
+----------------
+
+- Added minimal Sphinx documentation.
+
+- Eggified, ported for compatibility with Zope 2.12.
+
+- Initial public release.
+
+- Migrated to svn.zope.org from Casey Duncan's original version on
+  cvs.zope.org.

Added: Produts.RecentItemsIndex/trunk/docs/Makefile
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/Makefile	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/Makefile	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProjectsRecentItemsIndex.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProjectsRecentItemsIndex.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."


Property changes on: Produts.RecentItemsIndex/trunk/docs/_build
___________________________________________________________________
Added: svn:ignore
   + *


Added: Produts.RecentItemsIndex/trunk/docs/conf.py
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/conf.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/conf.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+#
+# Projects.RecentItemsIndex documentation build configuration file, created by
+# sphinx-quickstart on Tue Mar 16 18:05:54 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Projects.RecentItemsIndex'
+copyright = u'2010, Zope Foundation and contributors'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ProjectsRecentItemsIndexdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'ProjectsRecentItemsIndex.tex', u'Projects.RecentItemsIndex Documentation',
+   u'Zope Foundation and contributors', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True

Added: Produts.RecentItemsIndex/trunk/docs/index.rst
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/index.rst	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/index.rst	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,20 @@
+.. Projects.RecentItemsIndex documentation master file, created by
+   sphinx-quickstart on Tue Mar 16 18:05:54 2010.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to Projects.RecentItemsIndex's documentation!
+=====================================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+

Deleted: Produts.RecentItemsIndex/trunk/index.py
===================================================================
--- Produts.RecentItemsIndex/trunk/index.py	2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/index.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,299 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""ZCatalog index for efficient queries of the most recent items
-
-$Id: index.py,v 1.5 2004/08/10 16:01:26 caseman Exp $"""
-
-import types
-from Globals import DTMLFile, InitializeClass
-from Acquisition import aq_inner, aq_parent
-from Persistence import Persistent
-from OFS.SimpleItem import SimpleItem
-from Products.PluginIndexes.common.PluggableIndex \
-    import PluggableIndexInterface
-from Products.ZCatalog.Lazy import LazyMap
-from AccessControl import Permissions
-from AccessControl.PermissionRole import rolesForPermissionOn
-from AccessControl.SecurityInfo import ClassSecurityInfo
-from BTrees.OOBTree import OOBTree
-from BTrees.IOBTree import IOBTree
-from BTrees.OIBTree import OIBucket
-from BTrees.Length import Length
-from zLOG import LOG, WARNING
-    
-_marker = []
-
-def _getSourceValue(obj, attrname):
-    """Return the data to be indexed for obj"""
-    value = getattr(obj, attrname)
-    try:
-        # Try calling it
-        value = value()
-    except (TypeError, AttributeError):
-        pass
-    return value
-
-class RecentItemsIndex(SimpleItem):
-    """Recent items index"""
-
-    __implements__ = PluggableIndexInterface
-    
-    meta_type = 'Recent Items Index'
-
-    manage_options = (
-        {'label': 'Overview', 'action': 'manage_main'},
-    )
-    
-    manage_main = DTMLFile('www/manageIndex', globals())
-    
-    security = ClassSecurityInfo()
-    security.declareObjectProtected(Permissions.manage_zcatalog_indexes)
-    
-    def __init__(
-        self, id, field_name=None, date_name=None, max_length=None, 
-        guard_roles=None, guard_permission=None, extra=None, caller=None):
-        """Recent items index constructor
-        
-        id -- Zope id for index in
-        
-        field_name -- Name of attribute used to classify the objects. A
-        recent item list is created for each value of this field indexed.
-        If this value is omitted, then a single recent item list for all
-        cataloged objects is created.
-        
-        date_name -- Name of attribute containing a date which specifies the
-        object's age.
-        
-        max_length -- Maximum length of each recent items list.
-        
-        guard_roles -- A list of one or more roles that must be granted the
-        guard permission in order for an object to be indexed. Ignored if
-        no guard_permission value is given.
-        
-        guard_permission -- The permission that must be granted to the
-        guard roles for an object in order for it to be indexed. Ignored if
-        no guard_roles value is given.
-        
-        extra and caller are used by the wonderous ZCatalog addIndex 
-        machinery. You can ignore them, unfortunately I can't 8^/
-        """
-        self.id = id
-        self.field_name = field_name or getattr(extra, 'field_name', None)
-        self.date_name = date_name or extra.date_name
-        self.max_length = max_length or extra.max_length
-        assert self.max_length > 0, 'Max item length value must be 1 or greater'
-        if guard_roles is None:
-            guard_roles = getattr(extra, 'guard_roles', None)
-        if guard_permission is None:
-            guard_permission = getattr(extra, 'guard_permission', None)
-        if guard_permission is not None and guard_roles:
-            self.guard_permission = guard_permission
-            self.guard_roles = tuple(guard_roles)
-        else:
-            self.guard_permission = self.guard_roles = None
-        self.clear()
-    
-    ## Index specific methods ##
-    
-    def getItemCounts(self):
-        """Return a dict of field value => item count"""
-        counts = {}
-        for value, items in self._value2items.items():
-            counts[value] = len(items)
-        return counts
-        
-    def query(self, value=None, limit=None, merge=1):
-        """Return a lazy sequence of catalog brains like a catalog search
-        that coorespond to the most recent items for the value(s) given.
-        If value is omitted, then the most recent for all values are returned.
-        The result are returned in order, newest first. An integer value
-        can be specified in limit which restricts the maximum number of
-        results if desired. If no limit is specified, the indexes'
-        maximum length is used as the limit
-        """
-        catalog = aq_parent(aq_inner(self))
-        if value is None and self.field_name is not None:
-            # Query all values
-            value = list(self._value2items.keys())
-        elif value is not None and self.field_name is None:
-            # Ignore value given if there is no classifier field
-            value = None
-        if isinstance(value, (types.TupleType, types.ListType)):
-            # Query for multiple values
-            results = []
-            for fieldval in value:
-                try:
-                    itempairs = self._value2items[fieldval].keys()
-                except KeyError:
-                    pass
-                else:
-                    results.extend(itempairs)
-            results.sort()
-            if merge:
-                results = [rid for date, rid in results]
-            else:
-                # Create triples expected by mergeResults()
-                results = [(date, rid, catalog.__getitem__)
-                           for date, rid in results]
-        else:
-            # Query for single value
-            try:
-                items = self._value2items[value]                    
-            except KeyError:
-                results = []
-            else:
-                if merge:
-                    results = items.values()
-                else:
-                    # Create triples expected by mergeResults()
-                    results = [(date, rid, catalog.__getitem__)
-                               for date, rid in items.keys()]
-        results.reverse()
-        if limit is not None:
-            results = results[:limit]
-        if merge:
-            return LazyMap(catalog.__getitem__, results, len(results))
-        else:
-            return results
-    
-    ## Pluggable Index API ##
-    
-    def index_object(self, docid, obj, theshold=None):
-        """Add document to index"""
-        if self.guard_permission is not None and self.guard_roles:
-            allowed_roles = rolesForPermissionOn(self.guard_permission, obj)
-            for role in allowed_roles:
-                if role in self.guard_roles:
-                    break
-            else:
-                # Object does not have proper permission grant
-                # to be in the index
-                self.unindex_object(docid)
-                return 0
-        try:
-            if self.field_name is not None:
-                fieldvalue = _getSourceValue(obj, self.field_name)
-            else:
-                fieldvalue = None
-            datevalue = _getSourceValue(obj, self.date_name)
-        except AttributeError:
-            # One or the other source attributes is missing
-            # unindex the object and bail
-            self.unindex_object(docid)
-            return 0
-        datevalue = datevalue.timeTime()
-        entry = self.getEntryForObject(docid)
-        if (entry is None or fieldvalue != entry['value'] 
-            or datevalue != entry['date']):
-            # XXX Note that setting the date older than a previously pruned
-            #     object will result in an incorrect index state. This may
-            #     present a problem if dates are changed arbitrarily 
-            if entry is None:
-                self.numObjects.change(1)
-            else:
-                # unindex existing entry
-                self.unindex_object(docid)
-            self._rid2value[docid] = fieldvalue
-            try:
-                items = self._value2items[fieldvalue]
-            except KeyError:
-                # Unseen value, create a new items bucket
-                items = self._value2items[fieldvalue] = OIBucket()
-            items[datevalue, docid] = docid
-            while len(items) > self.max_length:
-                # Prune the oldest items
-                olddate, oldrid = items.minKey()
-                # Unindex by hand to avoid theoretical infinite loops
-                self.numObjects.change(-1)
-                del items[olddate, oldrid]
-                if not items:
-                    # Not likely, unless max_length is 1
-                    del self._value2items[fieldvalue]
-                try:
-                    del self._rid2value[oldrid]
-                except KeyError:
-                    LOG('RecentItemsIndex', WARNING, 
-                        'Could not unindex field value for %s.' % oldrid)
-            return 1
-        else:
-            # Index is up to date, nothing to do
-            return 0
-    
-    def unindex_object(self, docid):
-        """Remove docid from the index. If docid is not in the index,
-        do nothing"""
-        try:
-            fieldvalue = self._rid2value[docid]
-        except KeyError:
-            return 0 # docid not in index
-        self.numObjects.change(-1)
-        del self._rid2value[docid]
-        items = self._value2items[fieldvalue]
-        for date, rid in items.keys():
-            if rid == docid:
-                del items[date, rid]
-                if not items:
-                    del self._value2items[fieldvalue]
-                return 1
-        return 1
-    
-    def _apply_index(self, request, cid=''):
-        """We do not play in normal catalog queries"""
-        return None
-        
-    def getEntryForObject(self, docid, default=None):
-        """Return a dict containing the field value and date for
-        docid, or default if it is not in the index. The returned dict
-        has the keys 'value' and 'date'
-        """
-        try:
-            fieldvalue = self._rid2value[docid]
-        except KeyError:
-            return default
-        for date, rid in self._value2items[fieldvalue].keys():
-            if rid == docid:
-                return {'value':fieldvalue, 'date':date}
-        # If we get here then _rid2values is inconsistent with _value2items
-        LOG('RecentItemsIndex', WARNING, 
-            'Field value found for item %s, but no date. '
-            'This should not happen.' % docid)
-        return default
-    
-    def hasUniqueValuesFor(self, name):
-        """Return true if the index holds the unique values for name"""
-        return name == self.field_name
-    
-    def uniqueValues(self, name=None):
-        """Return the unique field values indexed"""
-        if name is None:
-            name = self.field_name
-        if name == self.field_name:
-            return self._value2items.keys()
-        else:
-            return []
-        
-    ## ZCatalog ZMI methods ##
-
-    def clear(self):
-        """reinitialize the index"""
-        self.numObjects = Length()
-        # Mapping field value => top items
-        self._value2items = OOBTree()
-        # Mapping indexed rid => field value for unindexing
-        self._rid2value = IOBTree()
-    
-
-InitializeClass(RecentItemsIndex)
-
-addIndexForm = DTMLFile('www/addIndex', globals())

Added: Produts.RecentItemsIndex/trunk/setup.py
===================================================================
--- Produts.RecentItemsIndex/trunk/setup.py	                        (rev 0)
+++ Produts.RecentItemsIndex/trunk/setup.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,55 @@
+import os
+from setuptools import setup
+from setuptools import find_packages
+
+NAME = 'RecentItemsIndex'
+
+here = os.path.abspath(os.path.dirname(__file__))
+package = os.path.join(here, 'Products', NAME)
+docs = os.path.join(here, 'docs')
+
+def _package_doc(name):
+    f = open(os.path.join(package, name))
+    return f.read()
+
+def _docs_doc(name):
+    f = open(os.path.join(docs, name))
+    return f.read()
+
+_boundary = '\n' + ('-' * 60) + '\n\n'
+README = ( open('README.txt').read()
+         + _boundary
+         + _docs_doc('CHANGES.rst')
+         )
+
+setup(name='Products.%s' % NAME,
+      version=_package_doc('version.txt').strip(),
+      description='Read Zope configuration state from profile dirs / tarballs',
+      long_description=README,
+      classifiers=[
+        "Development Status :: 4 - Beta",
+        "Framework :: Plone",
+        "Framework :: Zope2",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: Zope Public License",
+        "Programming Language :: Python",
+        ],
+      keywords='web application server zope zope2 cmf',
+      author="Zope Corporation and contributors",
+      author_email="zope-cmf at zope.org",
+      url="http://pypi.python.org/pypi/Products.%s" % NAME,
+      license="ZPL 2.1 (http://www.zope.org/Resources/License/ZPL-2.1)",
+      packages=find_packages(),
+      include_package_data=True,
+      namespace_packages=['Products'],
+      zip_safe=False,
+      install_requires=[
+          'setuptools',
+          'Zope2 >= 2.12.3',
+          ],
+      test_suite="Products.%s.tests" % NAME,
+      entry_points="""
+      [zope2.initialize]
+      Products.%s = Products.%s:initialize
+      """ % (NAME, NAME),
+)

Deleted: Produts.RecentItemsIndex/trunk/test.py
===================================================================
--- Produts.RecentItemsIndex/trunk/test.py	2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/test.py	2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,433 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Tests for RecentItemsIndex
-
-$Id: test.py,v 1.5 2004/08/10 16:01:26 caseman Exp $"""
-
-import os
-from unittest import TestCase, TestSuite, main, makeSuite
-
-import ZODB
-from OFS.SimpleItem import SimpleItem
-from DateTime import DateTime
-
-
-class Doc:
-    
-    def __init__(self, **kw):
-        self.__dict__.update(kw)
-        
-class DummyCatalog(SimpleItem):
-    
-    docs = {}
-    
-    def __getitem__(self, item):
-        return self.docs[item]
-
-class Viewable(SimpleItem):
-    
-    date = DateTime('2/21/2004')
-    type = 'Viewable'
-    
-    def __init__(self, role=None):
-        self._addRole(role)
-        if role is not None:
-            self.manage_permission('View', [role])
-
-class RecentItemsIndexTest(TestCase):
-
-    def setUp(self):
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        self.test = DummyCatalog()
-        self.test.index = RecentItemsIndex('test', 'type', 'date', 10)
-        self.index = self.test.index
-        
-    def test_construct_with_extra(self):
-        # Simulate instantiating from ZCatalog
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        class extra:
-            field_name = 'bruford'
-            date_name = 'wakeman'
-            max_length = 25
-            guard_roles = ['Anonymous']
-            guard_permission = 'View'
-        index = RecentItemsIndex('extra', extra=extra)
-        self.assertEqual(index.getId(), 'extra')
-        self.assertEqual(index.field_name, 'bruford')
-        self.assertEqual(index.date_name, 'wakeman')
-        self.assertEqual(index.max_length, 25)
-        self.assertEqual(tuple(index.guard_roles), ('Anonymous',))
-        self.assertEqual(index.guard_permission, 'View')
-        
-    def test_construct_with_no_classifier_or_guard(self):
-        # Simulate instantiating from ZCatalog
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        class extra:
-            date_name = 'modified'
-            max_length = 30
-        index = RecentItemsIndex('nuttin', extra=extra)
-        self.assertEqual(index.getId(), 'nuttin')
-        self.assertEqual(index.date_name, 'modified')
-        self.assertEqual(index.max_length, 30)
-    
-    def test_construct_with_bogus_max_length(self):
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        self.assertRaises(
-            Exception, RecentItemsIndex, 'test', 'type', 'date', 0)
-        self.assertRaises(
-            Exception, RecentItemsIndex, 'test', 'type', 'date', -20)
-
-    def test_index_single(self):
-        doc = Doc(type='fluke', date=DateTime('1/1/2004'))
-        self.failUnless(self.index.index_object(1, doc))
-        self.assertEqual(self.index.numObjects(), 1)
-        self.assertEqual(self.index.getItemCounts(), {'fluke': 1})
-    
-    def test_exclude_obj_without_field_and_date(self):
-        doc = Doc()
-        self.failIf(self.index.index_object(1, doc))
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})
-        doc = Doc(type='cheetos')
-        self.failIf(self.index.index_object(1, doc))
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})
-        doc = Doc(date=DateTime('4/17/2004'))
-        self.failIf(self.index.index_object(1, doc))
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})         
-
-    def test_unindex_single(self):
-        self.test_index_single()
-        self.failUnless(self.index.unindex_object(1))
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})
-    
-    def test_index_many(self):
-        types = ['huey', 'dooey', 'looey', 'dooey'] * 15
-        date = DateTime('1/1/2004')
-        docs = {}
-        for docid, typ in zip(range(len(types)), types):
-            if not docid % 3:
-                date = date + 1
-            if not docid % 7:
-                date = date - (docid % 3)
-            doc = docs[docid] = Doc(docid=docid, type=typ, date=date)
-            self.index.index_object(docid, doc)
-        maxlen = self.index.max_length
-        self.assertEqual(self.index.getItemCounts(), 
-                         {'huey': maxlen, 'dooey':maxlen, 'looey':maxlen})
-        self.assertEqual(self.index.numObjects(), maxlen*3)
-        self.test.docs = docs
-        return docs
-    
-    def test_index_many_no_classifier(self):
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        self.test.index = RecentItemsIndex('test', None, 'date', 10)
-        self.index = self.test.index
-        types = ['huey', 'dooey', 'looey', 'dooey'] * 15
-        date = DateTime('1/1/2004')
-        docs = {}
-        for docid, typ in zip(range(len(types)), types):
-            if not docid % 3:
-                date = date + 1
-            if not docid % 7:
-                date = date - (docid % 3)
-            doc = docs[docid] = Doc(docid=docid, type=typ, date=date)
-            self.index.index_object(docid, doc)
-        maxlen = self.index.max_length
-        self.assertEqual(self.index.getItemCounts(), {None: maxlen,})
-        self.assertEqual(self.index.numObjects(), maxlen)
-        self.test.docs = docs
-        return docs
-    
-    def test_unindex_one_type(self):
-        docs = self.test_index_many()
-        for docid, doc in docs.items():
-            if doc.type == 'looey':
-                self.index.unindex_object(docid)
-        self.assertEqual(self.index.numObjects(), 20)
-        self.assertEqual(self.index.getItemCounts(), {'huey': 10, 'dooey':10})           
-
-    def test_unindex_all(self):
-        docs = self.test_index_many()
-        for docid in docs.keys():
-            self.index.unindex_object(docid)
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})
-        self.assertEqual(list(self.index.uniqueValues()), [])
-    
-    def _get_top_docs(self, docs):
-        top = {'huey':[], 'dooey':[], 'looey':[]}        
-        for doc in docs.values():
-            top[doc.type].append((doc.date.timeTime(), doc.docid))
-        for typ, docs in top.items():
-            docs.sort()
-            top[typ] = docs[-10:]
-        return top
-        
-    def test_getEntryForObject(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        for docid, doc in docs.items():
-            entry = self.index.getEntryForObject(docid)
-            if entry is not None:
-                self.assertEqual(entry, 
-                    {'value': doc.type, 'date': doc.date.timeTime()})
-            else:
-                self.failIf((doc.date.timeTime(), doc.docid) in top[doc.type])
-    
-    def test_unindex_most_recent(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        item_counts = self.index.getItemCounts()
-        total_count = 30
-        for i in range(10):
-            for typ in ('huey', 'dooey', 'looey'):
-                nil, byebyeid = top[typ].pop()
-                self.failUnless(self.index.unindex_object(byebyeid))
-                item_counts[typ] -= 1
-                if not item_counts[typ]:
-                    del item_counts[typ]
-                total_count -= 1
-                self.assertEqual(self.index.getItemCounts(), item_counts)
-                self.assertEqual(self.index.numObjects(), total_count)
-        self.assertEqual(self.index.numObjects(), 0)
-        self.assertEqual(self.index.getItemCounts(), {})
-    
-    def test_unindex_bogus_rid(self):
-        self.test_index_many()
-        self.failIf(self.index.unindex_object(-2000))
-    
-    def _get_indexed_doc(self, fromtop=0):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        items = docs.items()
-        if fromtop:
-            items.reverse()
-        for docid, doc in items:
-            entry = self.index.getEntryForObject(docid)
-            if entry is not None:
-                break
-        else:
-            self.fail('No objects in index')
-        self.assertEqual(entry, {'value':doc.type, 'date':doc.date.timeTime()})
-        return doc
-    
-    def test_reindex_no_change(self):
-        # reindex with no change should be a no-op
-        doc = self._get_indexed_doc()
-        self.failIf(self.index.index_object(doc.docid, doc))
-        self.assertEqual(self.index.getEntryForObject(doc.docid),
-                         {'value':doc.type, 'date':doc.date.timeTime()})
-    
-    def test_reindex_change_date(self):
-        doc = self._get_indexed_doc()
-        doc.date = doc.date + 10
-        self.failUnless(self.index.index_object(doc.docid, doc))
-        self.assertEqual(self.index.getEntryForObject(doc.docid),
-                         {'value':doc.type, 'date':doc.date.timeTime()})
-    
-    def test_reindex_change_value(self):
-        doc = self._get_indexed_doc(fromtop=1)
-        oldtype = doc.type
-        for typ in self.index.uniqueValues():
-            if typ != oldtype:
-                doc.type = typ
-                break
-        self.failUnless(self.index.index_object(doc.docid, doc))
-        self.assertEqual(self.index.getEntryForObject(doc.docid),
-                         {'value':doc.type, 'date':doc.date.timeTime()})
-    
-    def test_reindex_change_date_and_value(self):
-        doc = self._get_indexed_doc(fromtop=1)
-        doc.date = doc.date + 4
-        oldtype = doc.type
-        for typ in self.index.uniqueValues():
-            if typ != oldtype:
-                doc.type = typ
-                break
-        self.failUnless(self.index.index_object(doc.docid, doc))
-        self.assertEqual(self.index.getEntryForObject(doc.docid),
-                         {'value':doc.type, 'date':doc.date.timeTime()})
-    
-    def test_query_empty_index(self):
-        result = self.index.query('foobar')
-        self.failIf(result)
-        
-    def test_simple_query(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query('huey')
-        expected = [docid for nil, docid in top['huey']]
-        expected.reverse()
-        self.assertEqual([doc.docid for doc in result], expected)
-    
-    def test_query_bogus_value(self):
-        docs = self.test_index_many()
-        self.failIf(self.index.query('snacks'))
-        
-    def test_query_limit(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query('huey', limit=3)
-        expected = [docid for nil, docid in top['huey']]
-        expected.reverse()
-        expected = expected[:3]
-        self.assertEqual([doc.docid for doc in result], expected)
-    
-    def test_query_no_merge(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query('dooey', merge=0)
-        expected = [(date, docid, self.test.__getitem__) 
-                    for date, docid in top['dooey']]
-        expected.reverse()
-        for rrow, erow in zip(result, expected):
-           self.assertEqual(rrow[:2], erow[:2])        
-    
-    def test_query_multiple_values(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query(['huey', 'dooey'])
-        expected = top['huey'] + top['dooey']
-        expected.sort()
-        expected = [docid for nil, docid in expected]
-        expected.reverse()
-        self.assertEqual([doc.docid for doc in result], expected)
-        return expected
-    
-    def test_query_all_values(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query()
-        expected = top['huey'] + top['dooey'] + top['looey']
-        expected.sort()
-        expected = [docid for nil, docid in expected]
-        expected.reverse()
-        self.assertEqual([doc.docid for doc in result], expected)
-        return expected
-
-    def test_query_no_classifier(self):
-        docs = self.test_index_many_no_classifier()
-        top = self._get_top_docs(docs)
-        result = self.index.query()
-        expected = top['huey'] + top['dooey'] + top['looey']
-        expected.sort()
-        expected = [docid for nil, docid in expected]
-        expected.reverse()
-        self.assertEqual([doc.docid for doc in result], expected[:10])
-
-    def test_query_no_classifier_ignores_value(self):
-        docs = self.test_index_many_no_classifier()
-        top = self._get_top_docs(docs)
-        result = self.index.query('ptooey')
-        expected = top['huey'] + top['dooey'] + top['looey']
-        expected.sort()
-        expected = [docid for nil, docid in expected]
-        expected.reverse()
-        self.assertEqual([doc.docid for doc in result], expected[:10])
-        
-    def test_query_multiple_with_tuple(self):
-        expected = self.test_query_multiple_values()
-        result = self.index.query(('huey', 'dooey'))
-        self.assertEqual([doc.docid for doc in result], expected)
-        
-    def test_query_multiple_bogus_values(self):
-        self.failIf(self.index.query(['fooey', 'blooey']))
-        result = self.index.query(['blooey', 'looey'])
-        expected = self.index.query('looey')
-        self.assertEqual(list(result), list(expected))
-        
-    def test_query_multiple_limit(self):
-        expected = self.test_query_multiple_values()[:4]
-        result = self.index.query(['huey', 'dooey'], limit=4)
-        self.assertEqual([doc.docid for doc in result], expected)
-    
-    def test_query_multiple_no_merge(self):
-        docs = self.test_index_many()
-        top = self._get_top_docs(docs)
-        result = self.index.query(['dooey', 'huey'], merge=0)
-        expected = [(date, docid, self.test.__getitem__) 
-                    for date, docid in top['huey'] + top['dooey']]
-        expected.sort()
-        expected.reverse()
-        for rrow, erow in zip(result, expected):
-           self.assertEqual(rrow[:2], erow[:2]) 
-        
-    def test_apply_index(self):
-        # _apply_index always returns none since recent items index
-        # do not participate in the normal ZCatalog query as they
-        # handle both intersection and sorting
-        self.failUnless(self.index._apply_index({}) is None)
-        self.failUnless(self.index._apply_index({'query':'looey'}) is None)
-    
-    def test_uniqueValues(self):
-        self.failIf(self.index.uniqueValues('type'))
-        docs = self.test_index_many()
-        values = list(self.index.uniqueValues('type'))
-        values.sort()
-        self.assertEqual(values, ['dooey', 'huey', 'looey'])
-        self.failIf(self.index.uniqueValues('carbtastic'))
-    
-    def test_hasUniqueValuesFor(self):
-        self.failUnless(self.index.hasUniqueValuesFor('type'))
-        self.failIf(self.index.hasUniqueValuesFor('spork'))
-    
-    def test_numObjects(self):
-        docs = self.test_index_many()
-        self.assertEqual(self.index.numObjects(), 30)
-    
-    def test_numObjects_small_maxlen(self):
-        self.index.max_length = 1
-        docs = self.test_index_many()
-        self.assertEqual(self.index.numObjects(), 3)
-    
-    def test_numObjects_empty_index(self):
-        self.assertEqual(self.index.numObjects(), 0)
-        
-    def test_clear(self):
-        self.test_index_many()
-        self.failUnless(self.index.numObjects())
-        self.index.clear()
-        self.assertEqual(self.index.numObjects(), 0)
-    
-    def test_role_permission_guard(self):
-        from Products.RecentItemsIndex.index import RecentItemsIndex
-        index = RecentItemsIndex(
-            'test', 'type', 'date', 5, ['NerfHerder', 'Bloke'], 'View')
-        viewable = Viewable('NerfHerder')
-        index.index_object(0, viewable)
-        self.assertEqual(index.numObjects(), 1)
-        notviewable = Viewable()
-        index.index_object(1, notviewable)
-        self.assertEqual(index.numObjects(), 1)
-        bloke = Viewable('Bloke')
-        index.index_object(2, bloke)
-        self.assertEqual(index.numObjects(), 2)
-        bloke.manage_permission('View', [])
-        index.index_object(2, bloke)
-        self.assertEqual(index.numObjects(), 1)
-        dummy = Viewable('Dummy')
-        index.index_object(3, dummy)
-        self.assertEqual(index.numObjects(), 1)        
-        viewable.manage_permission('View', [])
-        index.index_object(0, viewable)
-        self.assertEqual(index.numObjects(), 0)
-        
-def test_suite():
-    return TestSuite((makeSuite(RecentItemsIndexTest),))
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

Deleted: Produts.RecentItemsIndex/trunk/version.txt
===================================================================
--- Produts.RecentItemsIndex/trunk/version.txt	2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/version.txt	2010-03-16 22:10:38 UTC (rev 110000)
@@ -1 +0,0 @@
-0.1



More information about the checkins mailing list