##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation 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.
#
##############################################################################
"""
Convenience functions for traversing the object tree.
This module provides :class:`zope.traversing.interfaces.ITraversalAPI`
"""
import six
from zope.interface import moduleProvides
from zope.location.interfaces import ILocationInfo, IRoot
from zope.traversing.interfaces import ITraversalAPI, ITraverser
moduleProvides(ITraversalAPI)
__all__ = tuple(ITraversalAPI)
_marker = object()
[docs]def joinPath(path, *args):
"""
Join the given relative paths to the given path.
"""
if not args:
# Concatenating u'' is much quicker than unicode(path)
return u'' + path
if path != '/' and path.endswith('/'):
raise ValueError('path must not end with a "/": %s' % path)
if path != '/':
path += u'/'
for arg in args:
if arg.startswith('/') or arg.endswith('/'):
raise ValueError("Leading or trailing slashes in path elements")
return _normalizePath(path + u'/'.join(args))
[docs]def getPath(obj):
"""
Returns a string representing the physical path to the object.
"""
return ILocationInfo(obj).getPath()
[docs]def getRoot(obj):
"""
Returns the root of the traversal for the given object.
"""
return ILocationInfo(obj).getRoot()
[docs]def traverse(object, path, default=_marker, request=None):
"""
"""
traverser = ITraverser(object)
if default is _marker:
return traverser.traverse(path, request=request)
return traverser.traverse(path, default=default, request=request)
[docs]def traverseName(obj, name, default=_marker, traversable=None, request=None):
"""
Traverse a single step 'name' relative to the given object.
"""
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')
return obj
[docs]def getName(obj):
"""
Get the name an object was traversed via
"""
return ILocationInfo(obj).getName()
[docs]def getParent(obj):
"""
Returns the container the object was traversed via.
"""
try:
location_info = ILocationInfo(obj)
except TypeError:
pass
else:
return location_info.getParent()
# XXX Keep the old implementation as the fallback behaviour in the case
# that obj doesn't have a location parent. This seems advisable as the
# 'parent' is sometimes taken to mean the traversal parent, and the
# __parent__ attribute is used for both.
if IRoot.providedBy(obj):
return None
parent = getattr(obj, '__parent__', None)
if parent is not None:
return parent
raise TypeError("Not enough context information to get parent", obj)
[docs]def getParents(obj):
"""
Returns a list starting with the given object's parent followed by
each of its parents.
"""
return ILocationInfo(obj).getParents()
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)
[docs]def canonicalPath(path_or_object):
"""
Returns a canonical absolute unicode path for the given path or
object.
"""
if isinstance(path_or_object, six.string_types):
path = path_or_object
if not path:
raise ValueError("path must be non-empty: %s" % path)
else:
path = getPath(path_or_object)
path = u'' + 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.
return _normalizePath(path)
# import this down here to avoid circular imports
from zope.traversing.adapters import traversePathElement
# Synchronize the documentation.
for name in ITraversalAPI.names():
if name in globals():
globals()[name].__doc__ = ITraversalAPI[name].__doc__
del name