[Checkins] SVN: megrok.trails/trunk/src/megrok/trails/ Initial
import of megrok.trails code, which should already work for
Brandon Rhodes
brandon at rhodesmill.org
Tue Oct 9 01:00:32 EDT 2007
Log message for revision 80733:
Initial import of megrok.trails code, which should already work for
simple cases. Some ftests will have to come next, once I learn how to
set them up like Grok does, whose ftests are quite beautiful.
Changed:
U megrok.trails/trunk/src/megrok/trails/__init__.py
A megrok.trails/trunk/src/megrok/trails/components.py
-=-
Modified: megrok.trails/trunk/src/megrok/trails/__init__.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/__init__.py 2007-10-09 03:46:44 UTC (rev 80732)
+++ megrok.trails/trunk/src/megrok/trails/__init__.py 2007-10-09 05:00:32 UTC (rev 80733)
@@ -1 +1,15 @@
-# Trails!
+##############################################################################
+#
+# Copyright (c) 2006-2007 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.
+#
+##############################################################################
+
+from megrok.trails.components import TrailHead, Trail
Added: megrok.trails/trunk/src/megrok/trails/components.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/components.py (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/components.py 2007-10-09 05:00:32 UTC (rev 80733)
@@ -0,0 +1,189 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 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.
+#
+##############################################################################
+
+"""megrok.trails components"""
+
+import grok
+import urllib
+from zope.component import provideAdapter
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.publisher.browser import BrowserView
+
+
+class TrailRegistry(object):
+ """Remembers which Trails have been defined for which classes.
+
+ When Zope tries to determine the URL of an object
+ and asks our TrailAbsoluteURL adapter for its opinion,
+ it consults an instance of this registry which stores,
+ for each class for which a Trail has been defined,
+ the corresponding Trail.
+
+ """
+ def __init__(self):
+ self.classes = {}
+
+ def register(self, pattern):
+ self.classes[pattern.cls] = pattern
+
+ def __getitem__(self, key):
+ return self.classes[key]
+
+# At the moment, we keep a single global registry here in the module.
+# Someday we might wish to make this a local utility inside of each
+# Grok site, so that different sites can return different URLs for the
+# same sorts of object.
+
+_registry = TrailRegistry()
+
+
+_safe = '@+' # Characters that we don't want to have quoted
+
+class TrailAbsoluteURL(BrowserView):
+ """Return the Absolute URL of an object for which a Trail is defined."""
+
+ def __unicode__(self):
+ return urllib.unquote(self.__str__()).decode('utf-8')
+
+ def __str__(self):
+ cls = type(self.context)
+ pattern = _registry[cls]
+ return pattern.url(self.context, self.request)
+ #url += '/' + urllib.quote(name.encode('utf-8'), _safe)
+
+ __call__ = __str__
+
+ def breadcrumbs(self):
+ url = self()
+ return ({ 'name': url.strip('/'), 'url': url},)
+
+
+class Trail(object):
+ """The URL pattern defined for a particular class.
+
+ One can imagine many different ways of implementing Trails;
+ this simple one allows patterns like '/account/:username'
+ to result in URLs like '/account/brandon'.
+
+ """
+ def __init__(self, spec, cls):
+ """Create a Trail to an object.
+
+ Calls should look like: Trail('/account/:username', Account)
+
+ """
+ self.spec = spec
+ self.parts = spec.strip('/').split('/')
+ self.cls = cls
+ _registry.register(self)
+ provideAdapter(TrailAbsoluteURL, (cls, IHTTPRequest), IAbsoluteURL)
+
+ def match(self, namelist):
+ """Determine whether this Trail matches a URL.
+
+ Given a list of names like ['archive', '2007', 'October']
+ which represent a URL like '.../archive/2007/October',
+ determine whether they match this particular Trail.
+
+ If there is no match, return None.
+
+ If there is a match, then return an instance the class
+ we were given during instantiation, supplying its constructor
+ with the arguments taken from the URL.
+
+ """
+ parts = self.parts
+ if len(namelist) != len(parts):
+ return False
+ result = {}
+ for name, part in zip(namelist, self.parts):
+ if part.startswith(':'):
+ result[part[1:]] = name
+ elif part != name:
+ return None
+ return self.cls(**result)
+
+ def url(self, obj, request):
+ """Return the URL of an object as defined by this Trail.
+
+ Given an instance of the class for which this Trail was defined,
+ return its URL as a string.
+
+ """
+ def subst(part, obj):
+ if part.startswith(':'):
+ return str(getattr(obj, part[1:]))
+ return part
+
+ parts = [ subst(part, obj) for part in self.parts ]
+ return grok.url(request, grok.getSite()) + '/' + '/'.join(parts)
+
+
+class _Dummy(object):
+ """Dummy class.
+
+ We cannot define a subclass of grok.Traverser without the
+ Traverser grokker getting all upset about its needing to declare a
+ context, so this Dummy class servers as the context for the
+ TrailHead class. Each time users actually subclass TrailHead
+ themselves, they will define their own context.
+ """
+
+class TrailHead(grok.Traverser):
+ """Dispatch URLs to a collection of trails.
+
+ In order to use Trails, create a subclass of TrailHead, and
+ declare as its context the class on which you want URLs built.
+ You yourself must provide the machinery for users to navigate to
+ that class; then Trails will take over and accept subsequent URL
+ components until a Trail is matched.
+
+ When creating a subclass of TrailHead, provide it with a class
+ variable named ``trails`` that lists one or more Trail objects.
+
+ """
+ grok.context(_Dummy)
+
+ def traverse(self, name):
+ namelist = [ name ]
+ for trail in self.trails:
+ m = trail.match(namelist)
+ if m:
+ return m
+ return TrailFork(self.trails, namelist)
+
+
+class TrailFork(grok.Model):
+ """A fork in the trail.
+
+ A TrailFork represents a point at which some URL components have
+ been collected past a TrailHead, but they have not yet matched one
+ of that TrailHead's Trails. This Traverser accepts the next URL
+ component past the TrailFork and determines whether we have yet
+ matched any of the Trails of this TrailHead.
+
+ """
+ def __init__(self, trails, namelist):
+ self.trails = trails
+ self.namelist = namelist
+
+ def traverse(self, name):
+ namelist = list(self.namelist)
+ namelist.append(name)
+ for trail in self.trails:
+ m = trail.match(namelist)
+ if m:
+ return m
+ return TrailFork(self.trails, namelist)
More information about the Checkins
mailing list