[Zope3-checkins] CVS: Zope3/src/zope/app/traversing - __init__.py:1.18

Albertas Agejevas alga@codeworks.lt
Tue, 8 Apr 2003 14:27:04 -0400


Update of /cvs-repository/Zope3/src/zope/app/traversing
In directory cvs.zope.org:/tmp/cvs-serv3531

Modified Files:
	__init__.py 
Log Message:
Enhanced joinPath to normalize . and .. path elements, added a
more verbose docstring and more tests.


=== Zope3/src/zope/app/traversing/__init__.py 1.17 => 1.18 ===
--- Zope3/src/zope/app/traversing/__init__.py:1.17	Mon Mar 31 08:32:06 2003
+++ Zope3/src/zope/app/traversing/__init__.py	Tue Apr  8 14:26:34 2003
@@ -13,6 +13,8 @@
 ##############################################################################
 """
 Convenience functions for traversing the object tree.
+
+$Id$
 """
 from zope.component import getAdapter
 from zope.app.interfaces.traversing import IObjectName, IContainmentRoot
@@ -25,26 +27,34 @@
 _marker = object()
 
 def joinPath(path, *args):
-    """Join the given args to the path, separated by slashes.
+    """Join the given relative paths to the given path.
 
     Returns a unicode path.
-    The path should be well-formed, and not end in a '/' unless it is the
-    root path.
-    The positional arguments are strings to be added to the path as new path
-    segments. These segments may contain the '/' character.
+
+    The path should be well-formed, and not end in a '/' unless it is
+    the root path. It can be either a string (ascii only) or unicode.
+    The positional arguments are relative paths to be added to the
+    path as new path segments.  The path may be absolute or relative.
+
+    A segment may not start with a '/' because that would be confused
+    with an absolute path. A segment may not end with a '/' because we
+    do not allow '/' at the end of relative paths.  A segment may
+    consist of . or .. to mean "the same place", or "the parent path"
+    respectively. A '.' should be removed and a '..' should cause the
+    segment to the left to be removed.  joinPath('/', '..') should
+    raise an exception.
     """
+
     if not args:
         return unicode(path)
-    if path != u'/' and not path.endswith('/'):
+    if path != '/' and path.endswith('/'):
+        raise ValueError('path must not end with a "/": %s' % path)
+    if path != '/':
         path += u'/'
-    clean_args = []
     for arg in args:
-        if arg.startswith('/'):
-            arg = arg[1:]
-        if arg.endswith('/'):
-            arg = arg[:-1]
-        clean_args.append(arg)
-    return path + u'/'.join(clean_args)
+        if arg.startswith('/') or arg.endswith('/'):
+            raise ValueError("Leading or trailing slashes in path elements")
+    return _normalizePath(path + u'/'.join(args))
 
 def getPath(obj):
     """Returns a string representing the physical path to the object.
@@ -150,6 +160,33 @@
             return parents
     raise TypeError, "Not enough context information to get all parents"
 
+
+def _normalizePath(path):
+    """Normalize a path by resolving '.' and '..' path elements."""
+
+    # Special case for the root path.
+    if path == u'/':
+        return path
+
+    new_segments = []
+    prefix = u''
+    if path.startswith('/'):
+        prefix = u'/'
+        path = path[1:]
+
+    for segment in path.split(u'/'):
+        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 prefix + u'/'.join(new_segments)
+
 def canonicalPath(path_or_object):
     """Returns a canonical absolute unicode path for the given path or object.
 
@@ -176,19 +213,7 @@
         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))
+    return _normalizePath(path)
 
 # import this down here to avoid circular imports
 from zope.app.traversing.adapters import traversePathElement