[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