~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/urls/__init__.py

  • Committer: William Grant
  • Date: 2009-07-01 11:59:49 UTC
  • mto: (1294.4.2 ui-the-third)
  • mto: This revision was merged to the branch mainline in revision 1353.
  • Revision ID: grantw@unimelb.edu.au-20090701115949-shkpykxetfbs53lx
Add an object-traversal-based router.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2009 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
"""Object traversal URL utilities."""
 
19
 
 
20
import os.path
 
21
import sys
 
22
 
 
23
ROOT = object() # Marker object for the root.
 
24
 
 
25
class RoutingError(Exception):
 
26
    pass
 
27
 
 
28
class NotFound(RoutingError):
 
29
    """The path did not resolve to an object."""
 
30
    pass
 
31
 
 
32
class RoutelessObject(NotFound):
 
33
    """The path led to an object with no routes."""
 
34
    pass
 
35
 
 
36
class InsufficientPathSegments(NotFound):
 
37
    """The path led to a route that expected more arguments."""
 
38
    pass
 
39
 
 
40
class NoPath(RoutingError):
 
41
    """There is no path from the given object to the root."""
 
42
    pass
 
43
 
 
44
class RouteConflict(RoutingError):
 
45
    """A route with the same discriminator is already registered."""
 
46
    pass
 
47
 
 
48
def _segment_path(path):
 
49
    """Split a path into its segments, after normalisation.
 
50
 
 
51
       >>> _segment_path('/path/to/something')
 
52
       ['path', 'to', 'something']
 
53
       >>> _segment_path('/path/to/something/')
 
54
       ['path', 'to', 'something']
 
55
       >>> _segment_path('/')
 
56
       []
 
57
    """
 
58
 
 
59
    segments = os.path.normpath(path).split('/')
 
60
 
 
61
    # Remove empty segments caused by leading and trailing seperators.
 
62
    if segments[0] == '':
 
63
        segments.pop(0)
 
64
    if segments[-1] == '':
 
65
        segments.pop()
 
66
    return segments
 
67
 
 
68
class Router(object):
 
69
    '''Router to resolve and generate paths.
 
70
 
 
71
    Maintains a registry of forward and reverse routes, dealing with paths
 
72
    to objects and views published in the URL space.
 
73
    '''
 
74
 
 
75
    def __init__(self, root, default='+index'):
 
76
        self.fmap = {} # Forward map.
 
77
        self.rmap = {} # Reverse map.
 
78
        self.root = root
 
79
        self.default = default
 
80
 
 
81
    def add_forward(self, src, segment, func, argc):
 
82
        """Register a forward (path resolution) route."""
 
83
 
 
84
        if src not in self.fmap:
 
85
            self.fmap[src] = {}
 
86
 
 
87
        # If a route already exists with the same source and name, we have a
 
88
        # conflict. We don't like conflicts.
 
89
        if segment in self.fmap[src]:
 
90
            raise RouteConflict((src, segment, func),
 
91
                                (src, segment, self.fmap[src][segment][0]))
 
92
 
 
93
        self.fmap[src][segment] = (func, argc)
 
94
 
 
95
 
 
96
    def add_reverse(self, src, func):
 
97
        """Register a reverse (path generation) route."""
 
98
 
 
99
        if src in self.rmap:
 
100
             raise RouteConflict((src, func), (src, self.rmap[src]))
 
101
        self.rmap[src] = func
 
102
 
 
103
    def resolve(self, path):
 
104
        """Resolve a path into an object.
 
105
 
 
106
        Traverses the tree of routes using the given path.
 
107
        """
 
108
 
 
109
        (obj, todo) = self._traverse(_segment_path(path), self.root)
 
110
 
 
111
        # If we have no segments remaining, let's try the default route.
 
112
        # If there is no default route for this object, we don't care.
 
113
        if len(todo) == 0:
 
114
            obj = self._traverse([self.default], obj)[0]
 
115
 
 
116
        return obj
 
117
 
 
118
    def generate(self, obj):
 
119
        """Resolve an object into a path.
 
120
 
 
121
        Traverse up the tree of reverse routes, generating a path which
 
122
        resolves to the object.
 
123
        """
 
124
 
 
125
        # Attempt to get all the way to the top. Each reverse route should
 
126
        # return a (parent, pathsegments) tuple.
 
127
        curobj = obj
 
128
        names = []
 
129
 
 
130
        # None represents the root.
 
131
        while curobj is not ROOT:
 
132
            route = self.rmap.get(type(curobj))
 
133
            if route is None:
 
134
                raise NoPath(obj, curobj)
 
135
            (curobj, newnames) = route(curobj)
 
136
 
 
137
            # The reverse route can return either a string of one segment,
 
138
            # or a tuple of many.
 
139
            if isinstance(newnames, basestring):
 
140
                names.insert(0, newnames)
 
141
            else:
 
142
                names = list(newnames) + list(names)
 
143
 
 
144
        # Generate nice URLs for the default route, if it is the last. We need
 
145
        # to have an empty path rather than none at all in order to preserve
 
146
        # the trailing /.
 
147
        if names[-1] == self.default:
 
148
            del names[-1]
 
149
 
 
150
        return os.path.join('/', *names)
 
151
 
 
152
    def _traverse(self, todo, obj):
 
153
        """Populate the object stack given a list of path segments.
 
154
 
 
155
        Traverses the forward route tree, using the given path segments.
 
156
 
 
157
        Intended to be used by route(), and nobody else.
 
158
        """
 
159
        while todo:
 
160
            # Check if we have any routes for this object at all.
 
161
            try:
 
162
                names = self.fmap[type(obj)]
 
163
            except KeyError:
 
164
                return (obj, todo)
 
165
 
 
166
            routebits = names.get(todo[0])
 
167
 
 
168
            if routebits is not None:
 
169
                route, argc = routebits
 
170
                # The first path segment is the route identifier, so we skip
 
171
                # it when identifying arguments.
 
172
                lastseg = todo[0]
 
173
                args = todo[1:argc + 1]
 
174
                todo = todo[argc + 1:]
 
175
            else:
 
176
                # Attempt traversal directly (with no intermediate segment)
 
177
                # as a last resort.
 
178
                if None in names:
 
179
                    route, argc = names[None]
 
180
                    lastseg = None
 
181
                    args = todo[:argc]
 
182
                    todo = todo[argc:]
 
183
                else:
 
184
                    raise NotFound(obj, todo[0])
 
185
 
 
186
            if len(args) != argc:
 
187
                # There were too few path segments left. Die.
 
188
                raise InsufficientPathSegments(obj, lastseg, len(args))
 
189
 
 
190
            obj = route(obj, *args)
 
191
        return (obj, ())
 
192