summaryrefslogtreecommitdiff
path: root/repoze/bfg/traversal.py
blob: c72c90e5e0c8de479fc5c0de95cebbdb8cdaa088 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import urllib
import urlparse

from zope.interface import classProvides
from zope.interface import implements
from zope.location.location import located
from zope.location.location import LocationIterator
from zope.location.interfaces import ILocation

from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import ITraverserFactory

def split_path(path):
    if path.startswith('/'):
        path = path[1:]
    if path.endswith('/'):
        path = path[:-1]
    clean=[]
    for segment in path.split('/'):
        segment = urllib.unquote(segment) # deal with spaces in path segment
        if not segment or segment=='.':
            continue
        elif segment == '..':
            del clean[-1]
        else:
            clean.append(segment)
    return clean

def step(ob, name, default):
    if name.startswith('@@'):
        return name[2:], default
    if not hasattr(ob, '__getitem__'):
        return name, default
    try:
        return name, ob[name]
    except KeyError:
        return name, default

_marker = ()

class ModelGraphTraverser(object):
    classProvides(ITraverserFactory)
    implements(ITraverser)
    def __init__(self, root, request):
        self.root = root
        self.locatable = ILocation.providedBy(root)
        self.request = request

    def __call__(self, environ):
        path = environ.get('PATH_INFO', '/')
        path = split_path(path)
        root = self.root

        ob = self.root
        name = ''

        while path:
            segment = path.pop(0)
            segment, next = step(ob, segment, _marker)
            if next is _marker:
                name = segment
                break
            if self.locatable:
                next = located(next, ob, segment)
            ob = next

        return ob, name, path

def find_interface(context, interface):
    """ Return an object providing ``interface`` anywhere in the
    parent chain of ``context`` or ``None`` if no object providing
    that interface can be found in the parent chain"""
    for location in LocationIterator(context):
        if interface.providedBy(location):
            return location

def model_url(model, request, *elements):
    """ Return the absolute URL of the model object based on the
    ``wsgi.url_scheme``, ``HTTP_HOST`` or ``SERVER_NAME`` in the
    request, plus any ``SCRIPT_NAME``.  Any positional passed in as
    ``elements`` will be joined by slashes and appended to the
    generated URL.  The passed in elements are *not* URL-quoted.  The
    ``model`` passed in must be :term:`location`-aware."""
    rpath = []
    for location in LocationIterator(model):
        if location.__name__:
            rpath.append(urllib.quote(location.__name__))
    path = list(reversed(rpath))
    path.extend(elements)
    path = '/'.join(path)
    return urlparse.urljoin(request.application_url, path)