[Zope3-checkins] CVS: Zope3/src/zope/app/traversing - __init__.py:1.13 adapters.py:1.5
Marius Gedminas
mgedmin@codeworks.lt
Mon, 24 Mar 2003 11:42:52 -0500
Update of /cvs-repository/Zope3/src/zope/app/traversing
In directory cvs.zope.org:/tmp/cvs-serv1757/src/zope/app/traversing
Modified Files:
__init__.py adapters.py
Log Message:
zope.app.traversing changes:
- canonicalPath() now actually makes the path canonical
- locationAsTuple() is gone
- traverse() now gets an adapter to ITraverser instead of hardcoding Traverser
- traverseName() now accepts a 'traversable' argument opening the way for
optimizations
- some tests had to be updated
SteveA & Marius
=== Zope3/src/zope/app/traversing/__init__.py 1.12 => 1.13 ===
--- Zope3/src/zope/app/traversing/__init__.py:1.12 Fri Mar 21 10:29:10 2003
+++ Zope3/src/zope/app/traversing/__init__.py Mon Mar 24 11:42:22 2003
@@ -18,11 +18,9 @@
from zope.app.interfaces.traversing import IObjectName, IContainmentRoot
from zope.app.interfaces.traversing import ITraverser, IPhysicallyLocatable
from zope.proxy.context import getWrapperContainer, isWrapper
-from types import StringTypes
__all__ = ['traverse', 'traverseName', 'objectName', 'getParent',
- 'getParents', 'getPath', 'getRoot', 'locationAsTuple',
- 'canonicalPath']
+ 'getParents', 'getPath', 'getRoot', 'canonicalPath']
_marker = object()
@@ -36,11 +34,10 @@
"""
return getAdapter(obj, IPhysicallyLocatable).getRoot()
-def traverse(place, path, default=_marker, request=None):
- """Traverse 'path' relative to 'place'
+def traverse(object, path, default=_marker, request=None):
+ """Traverse 'path' relative to the given object.
- 'path' can be a string with path segments separated by '/'
- or a sequence of path segments.
+ 'path' is a string with path segments separated by '/'.
'request' is passed in when traversing from presentation code. This
allows paths like @@foo to work.
@@ -54,28 +51,38 @@
code unexpectedly.
Consider using traverseName instead.
"""
- traverser = Traverser(place)
+ traverser = getAdapter(object, ITraverser)
if default is _marker:
return traverser.traverse(path, request=request)
else:
return traverser.traverse(path, default=default, request=request)
-# XXX This should have an additional optional argument where you
-# can pass an ITraversable to use, otherwise it should get
-# an adapter for ITraversable from the object and use that to
-# traverse one step.
-def traverseName(obj, name, default=_marker):
- """Traverse a single step 'name' relative to 'place'
+def traverseName(obj, name, default=_marker, traversable=None, request=None):
+ """Traverse a single step 'name' relative to the given object.
- 'name' must be a string. 'name' will be treated as a single
- path segment, no matter what characters it contains.
+ 'name' must be a string. '.' and '..' are treated specially, as well as
+ names starting with '@' or '+'. Otherwise 'name' will be treated as a
+ single path segment.
- Raises NotFoundError if path cannot be found
- Raises TypeError if place is not context wrapped
+ You can explicitly pass in an ITraversable as the 'traversable'
+ argument. If you do not, the given object will be adapted to ITraversable.
+
+ 'request' is passed in when traversing from presentation code. This
+ allows paths like @@foo to work.
+
+ Raises NotFoundError if path cannot be found and 'default' was not provided.
"""
- # by passing [name] to traverse (above), we ensure that name is
- # treated as a single path segment, regardless of any '/' characters
- return traverse(obj, [name], default=default)
+ further_path = []
+ if default is _marker:
+ obj = traversePathElement(obj, name, further_path,
+ traversable=traversable, request=request)
+ else:
+ obj = traversePathElement(obj, name, further_path, default=default,
+ traversable=traversable, request=request)
+ if further_path:
+ raise NotImplementedError('further_path returned from traverse')
+ else:
+ return obj
def objectName(obj):
"""Get the name an object was traversed via
@@ -121,58 +128,45 @@
return parents
raise TypeError, "Not enough context information to get all parents"
-def locationAsTuple(location):
- """Given a location as a unicode or ascii string or as a tuple of
- unicode or ascii strings, returns the location as a tuple of
- unicode strings.
-
- Raises a ValueError if a poorly formed location is given.
- """
- if not location:
- raise ValueError("location must be non-empty: %s" % repr(location))
- if isinstance(location, StringTypes):
- if location == u'/': # matches '/' or u'/'
- return (u'',)
- t = tuple(location.split(u'/'))
- elif location.__class__ == tuple:
- # isinstance doesn't work when tuple is security-wrapped
- t = tuple(map(unicode, location))
- else:
- raise ValueError("location must be a string or a tuple of strings: %s"
- % repr(location))
+def canonicalPath(path_or_object):
+ """Returns a canonical absolute unicode path for the given path or object.
- if len(t) > 1 and t[-1] == u'': # matches '' or u''
- raise ValueError("location tuple must not end with empty string: %s"
- % repr(t))
- if '' in t[1:]:
- raise ValueError("location tuple must not contain '' except at the"
- " start: %s" % repr(t))
- return t
-
-def canonicalPath(location):
- """Given a location as a unicode or ascii string or as a tuple of
- unicode or ascii strings, returns the location as a slash-separated
- unicode string.
-
- Raises ValueError if a poorly formed location is given.
- """
- if not location:
- raise ValueError("location must be non-empty: %s" % repr(location))
- if isinstance(location, StringTypes):
- u = unicode(location)
- elif location.__class__ == tuple:
- # isinstance doesn't work when tuple is security-wrapped
- u = u'/'.join(location)
- if not u: # special case for u''
- return u'/'
+ Resolves segments that are '.' or '..'.
+
+ Raises ValueError if a badly formed path is given.
+ """
+ if isinstance(path_or_object, (str, unicode)):
+ path = path_or_object
+ if not path:
+ raise ValueError("path must be non-empty: %s" % path)
else:
- raise ValueError("location must be a string or a tuple of strings: %s"
- % repr(location))
- if u != '/' and u[-1] == u'/':
- raise ValueError("location must not end with a slash: %s" % u)
- if u.find(u'//') != -1:
- raise ValueError("location must not contain // : %s" % u)
- return u
+ path = getPath(path_or_object)
+
+ path = unicode(path)
+
+ # Special case for the root path.
+ if path == u'/':
+ return path
+
+ if path[0] != u'/':
+ raise ValueError('canonical path must start with a "/": %s' % path)
+ if path[-1] == u'/':
+ raise ValueError('path must not end with a "/": %s' % path)
+
+ # Break path into segments. Process '.' and '..' segments.
+ new_segments = []
+ for segment in path.split(u'/')[1:]: # skip empty segment at the start
+ if segment == u'.':
+ continue
+ if segment == u'..':
+ new_segments.pop() # raises IndexError if there is nothing to pop
+ continue
+ if not segment:
+ raise ValueError('path must not contain empty segments: %s'
+ % path)
+ new_segments.append(segment)
+
+ return u'/' + (u'/'.join(new_segments))
# import this down here to avoid circular imports
-from zope.app.traversing.adapters import Traverser
+from zope.app.traversing.adapters import traversePathElement
=== Zope3/src/zope/app/traversing/adapters.py 1.4 => 1.5 ===
--- Zope3/src/zope/app/traversing/adapters.py:1.4 Wed Mar 19 14:57:33 2003
+++ Zope3/src/zope/app/traversing/adapters.py Mon Mar 24 11:42:22 2003
@@ -173,37 +173,64 @@
try:
while path:
name = pop()
-
- if name == '.':
- continue
-
- if name == '..':
- # XXX This doesn't look right. Why fall back to curr?
- curr = getWrapperContainer(curr) or curr
- continue
-
-
- if name and name[:1] in '@+':
- ns, nm, parms = parameterizedNameParse(name)
- if ns:
- curr = namespaceLookup(name, ns, nm, parms,
- curr, request)
- continue
- else:
- parms = ()
- nm = name
-
- traversable = queryAdapter(curr, ITraversable, None)
- if traversable is None:
- raise NotFoundError(
- 'No traversable adapter found', curr)
-
- next = traversable.traverse(nm, parms, name, path)
- curr = ContextWrapper(next, curr, name=name)
+ curr = traversePathElement(curr, name, path, request=request)
return curr
except NotFoundError:
if default == _marker:
raise
return default
+
+
+def traversePathElement(obj, name, further_path, default=_marker,
+ traversable=None, request=None):
+ """Traverse a single step 'name' relative to the given object.
+
+ 'name' must be a string. '.' and '..' are treated specially, as well as
+ names starting with '@' or '+'. Otherwise 'name' will be treated as a
+ single path segment.
+
+ 'further_path' is a list of names still to be traversed. This method
+ is allowed to change the contents of 'further_path'.
+
+ You can explicitly pass in an ITraversable as the 'traversable'
+ argument. If you do not, the given object will be adapted to ITraversable.
+
+ 'request' is passed in when traversing from presentation code. This
+ allows paths like @@foo to work.
+
+ Raises NotFoundError if path cannot be found and 'default' was not provided.
+ """
+
+ if name == '.':
+ return obj
+
+ if name == '..':
+ # XXX This doesn't look right. Why fall back to obj?
+ obj = getWrapperContainer(obj) or obj
+ return obj
+
+ if name and name[:1] in '@+':
+ ns, nm, parms = parameterizedNameParse(name)
+ if ns:
+ return namespaceLookup(name, ns, nm, parms, obj, request)
+ else:
+ parms = ()
+ nm = name
+
+ if traversable is None:
+ traversable = queryAdapter(obj, ITraversable, None)
+ if traversable is None:
+ raise NotFoundError('No traversable adapter found', obj)
+
+ try:
+ next_item = traversable.traverse(nm, parms, name, further_path)
+ obj = ContextWrapper(next_item, obj, name=name)
+ except NotFoundError:
+ if default != _marker:
+ return default
+ else:
+ raise
+
+ return obj