1 |
1 |
2 """ |
2 """ |
3 URL mapping for the irclogs.qmsk.net site |
3 URL mapping for the irclogs.qmsk.net site |
4 """ |
4 """ |
5 |
5 |
|
6 import re |
|
7 |
6 # our own handlers |
8 # our own handlers |
7 import handlers |
9 import handlers |
8 |
10 |
9 # library stuff |
11 # mapper |
10 from lib.map import StaticMapper, map, mapre |
12 from lib import map |
11 |
13 |
12 def build_mapper () : |
14 class URLError (Exception) : |
13 """ |
15 """ |
14 Construct and return the Mapping object |
16 Error with an URL definition |
15 """ |
17 """ |
16 |
18 |
17 return StaticMapper([ |
19 pass |
18 map( '/', handlers.index ), |
20 |
19 map( '/channel/%s', handlers.channel_view ), |
21 class Label (object) : |
20 mapre( r'^/channel/(\w+)/last/(\d+)(\.\w+)?', handlers.channel_last ), |
22 """ |
21 ]) |
23 Base class for URL labels (i.e. the segments of the URL between /s) |
22 |
24 """ |
|
25 |
|
26 @staticmethod |
|
27 def parse (mask, defaults) : |
|
28 """ |
|
29 Parse the given label-segment, and return a *Label instance |
|
30 """ |
|
31 |
|
32 # empty? |
|
33 if not mask : |
|
34 return EmptyLabel() |
|
35 |
|
36 # simple value? |
|
37 match = SimpleValueLabel.EXPR.match(mask) |
|
38 |
|
39 if match : |
|
40 # key |
|
41 key = match.group('key') |
|
42 |
|
43 # default? |
|
44 default = defaults.get(key) |
|
45 |
|
46 # build |
|
47 return SimpleValueLabel(key, default) |
|
48 |
|
49 # static? |
|
50 match = StaticLabel.EXPR.match(mask) |
|
51 |
|
52 if match : |
|
53 return StaticLabel(match.group('name')) |
|
54 |
|
55 # invalid |
|
56 raise URLError("Invalid label: %r" % (mask, )) |
|
57 |
|
58 |
|
59 class EmptyLabel (Label) : |
|
60 """ |
|
61 An empty label, i.e. just a slash in the URL |
|
62 """ |
|
63 |
|
64 def __eq__ (self, other) : |
|
65 """ |
|
66 Just compares type |
|
67 """ |
|
68 |
|
69 return isinstance(other, EmptyLabel) |
|
70 |
|
71 def __str__ (self) : |
|
72 return '' |
|
73 |
|
74 class StaticLabel (Label) : |
|
75 """ |
|
76 A simple literal Label, used for fixed terms in the URL |
|
77 """ |
|
78 |
|
79 EXPR = re.compile(r'^(?P<name>[a-zA-Z_.-]+)$') |
|
80 |
|
81 def __init__ (self, name) : |
|
82 """ |
|
83 The given name is the literal name of this label |
|
84 """ |
|
85 |
|
86 self.name = name |
|
87 |
|
88 def __eq__ (self, other) : |
|
89 """ |
|
90 Compares names |
|
91 """ |
|
92 |
|
93 return isinstance(other, StaticLabel) and self.name == other.name |
|
94 |
|
95 def __str__ (self) : |
|
96 return self.name |
|
97 |
|
98 class ValueLabel (Label) : |
|
99 """ |
|
100 A label with a key and a value |
|
101 """ |
|
102 |
|
103 def __init__ (self, key, default) : |
|
104 """ |
|
105 Set the key and default value. Default value may be None if there is no default value defined |
|
106 """ |
|
107 |
|
108 self.key = key |
|
109 self.default = default |
|
110 |
|
111 def __eq__ (self, other) : |
|
112 """ |
|
113 Compares keys |
|
114 """ |
|
115 |
|
116 return isinstance(other, ValueLabel) and self.key == other.key |
|
117 |
|
118 class SimpleValueLabel (ValueLabel) : |
|
119 """ |
|
120 A label that has a name and a simple string value |
|
121 """ |
|
122 |
|
123 EXPR = re.compile(r'^\{(?P<key>[a-zA-Z_][a-zA-Z0-9_]*)\}$') |
|
124 |
|
125 def __init__ (self, key, default) : |
|
126 """ |
|
127 The given key is the name of this label's value |
|
128 """ |
|
129 |
|
130 super(SimpleValueLabel, self).__init__(key, default) |
|
131 |
|
132 def __str__ (self) : |
|
133 if self.default : |
|
134 return '{%s=%s}' % (self.key, self.default) |
|
135 |
|
136 else : |
|
137 return '{%s}' % (self.key, ) |
|
138 |
|
139 class URL (object) : |
|
140 """ |
|
141 Represents a specific URL |
|
142 """ |
|
143 |
|
144 def __init__ (self, url_mask, handler, **defaults) : |
|
145 """ |
|
146 Create an URL with the given url mask, handler, and default values |
|
147 """ |
|
148 |
|
149 # store |
|
150 self.url_mask = url_mask |
|
151 self.handler = handler |
|
152 self.defaults = defaults |
|
153 |
|
154 # build our labels |
|
155 self.label_path = [Label.parse(mask, defaults) for mask in url_mask.split('/')] |
|
156 |
|
157 def get_label_path (self) : |
|
158 """ |
|
159 Returns a list containing the labels in this url |
|
160 """ |
|
161 |
|
162 # copy self.label_path |
|
163 return list(self.label_path) |
|
164 |
|
165 def execute (self, request, xxx) : |
|
166 """ |
|
167 Invoke the handler with the correct parameters |
|
168 """ |
|
169 |
|
170 xxx |
|
171 |
|
172 def __str__ (self) : |
|
173 return '/'.join(str(label) for label in self.label_path) |
|
174 |
|
175 def __repr__ (self) : |
|
176 return "URL(%r, %r)" % (str(self), self.handler) |
|
177 |
|
178 class URLNode (object) : |
|
179 """ |
|
180 Represents a node in the URLTree |
|
181 """ |
|
182 |
|
183 def __init__ (self, parent, label) : |
|
184 """ |
|
185 Initialize with the given parent and label, empty children dict |
|
186 """ |
|
187 |
|
188 # the parent URLNode |
|
189 self.parent = parent |
|
190 |
|
191 # this node's Label |
|
192 self.label = label |
|
193 |
|
194 # list of child URLNodes |
|
195 self.children = [] |
|
196 |
|
197 # this node's URL, set by add_url for an empty label_path |
|
198 self.url = None |
|
199 |
|
200 def _build_child (self, label) : |
|
201 """ |
|
202 Build, insert and return a new child Node |
|
203 """ |
|
204 |
|
205 # build new child |
|
206 child = URLNode(self, label) |
|
207 |
|
208 # add to children |
|
209 self.children.append(child) |
|
210 |
|
211 # return |
|
212 return child |
|
213 |
|
214 def add_url (self, url, label_path) : |
|
215 """ |
|
216 Add a URL object to this node under the given path. Uses recursion to process the path. |
|
217 |
|
218 The label_path argument is a (partial) label path as returned by URL.get_label_path. |
|
219 |
|
220 If label_path is empty (len zero, or begins with EmptyLabel), then the given url is assigned to this node, if no |
|
221 url was assigned before. |
|
222 """ |
|
223 |
|
224 # matches this node? |
|
225 if not label_path or isinstance(label_path[0], EmptyLabel) : |
|
226 if self.url : |
|
227 raise URLError(url, "node already defined") |
|
228 |
|
229 else : |
|
230 # set |
|
231 self.url = url |
|
232 |
|
233 else : |
|
234 # pop child label from label_path |
|
235 child_label = label_path.pop(0) |
|
236 |
|
237 # look for the child to recurse into |
|
238 child = None |
|
239 |
|
240 # look for an existing child with that label |
|
241 for child in self.children : |
|
242 if child.label == child_label : |
|
243 # found, use this |
|
244 break |
|
245 |
|
246 else : |
|
247 # build a new child |
|
248 child = self._build_child(child_label) |
|
249 |
|
250 # recurse to handle the rest of the label_path |
|
251 child.add_url(url, label_path) |
|
252 |
|
253 def match_label (self, label) : |
|
254 """ |
|
255 Match our label mask against the given label value. |
|
256 |
|
257 Returns either False (no match), True (matched, no value) or a (key, value) pair. |
|
258 """ |
|
259 |
|
260 # simple mask |
|
261 if self.label.beginswith('{') and self.label.endswith('}') : |
|
262 value_name = self.label.strip('{}') |
|
263 |
|
264 return (value_name, label) |
|
265 |
|
266 # literal mask |
|
267 elif self.label == label : |
|
268 return True |
|
269 |
|
270 else : |
|
271 return False |
|
272 |
|
273 def match (self, path, label_path) : |
|
274 """ |
|
275 Locate the URL object corresponding to the given label_path value under this node, with the given path |
|
276 containing the previously matched label values |
|
277 """ |
|
278 |
|
279 # empty label_path? |
|
280 if not label_path or not label_path[0] : |
|
281 # we wanted this node |
|
282 if self.url : |
|
283 # this URL is the best match |
|
284 return [self] |
|
285 |
|
286 elif self.children : |
|
287 # continue testing our children |
|
288 label = None |
|
289 |
|
290 else : |
|
291 # incomplete URL |
|
292 raise URLError(url, "no URL handler defined for this Node") |
|
293 |
|
294 else : |
|
295 # pop our child label from the label path |
|
296 label = label_path.pop(0) |
|
297 |
|
298 # build a list of matching children |
|
299 matches = [] |
|
300 |
|
301 # recurse through our children |
|
302 for child in self.children : |
|
303 matches.append(child.match(label_path)) |
|
304 |
|
305 # return the list of matching children |
|
306 return matches |
|
307 |
|
308 def dump (self, indent=0) : |
|
309 """ |
|
310 Returns a multi-line string representation of this Node |
|
311 """ |
|
312 |
|
313 return '\n'.join([ |
|
314 "%-45s%s" % ( |
|
315 ' '*indent + str(self.label) + ('/' if self.children else ''), |
|
316 (' -> %r' % self.url) if self.url else '' |
|
317 ) |
|
318 ] + [ |
|
319 child.dump(indent + 4) for child in self.children |
|
320 ]) |
|
321 |
|
322 def __str__ (self) : |
|
323 return "%s/[%s]" % (self.label, ','.join(str(child) for child in self.children)) |
|
324 |
|
325 class URLTree (map.Mapper) : |
|
326 """ |
|
327 Map requests to handlers, using a defined tree of URLs |
|
328 """ |
|
329 |
|
330 def __init__ (self, url_list) : |
|
331 """ |
|
332 Initialize the tree using the given list of URLs |
|
333 """ |
|
334 |
|
335 # root node |
|
336 self.root = URLNode(None, EmptyLabel()) |
|
337 |
|
338 # just add each URL |
|
339 for url in url_list : |
|
340 self.add_url(url) |
|
341 |
|
342 def add_url (self, url) : |
|
343 """ |
|
344 Adds the given URL to the tree. The URL must begin with a root slash. |
|
345 """ |
|
346 # get url's label path |
|
347 path = url.get_label_path() |
|
348 |
|
349 # should begin with root |
|
350 root_label = path.pop(0) |
|
351 assert root_label == self.root.label, "URL must begin with root" |
|
352 |
|
353 # add to root |
|
354 self.root.add_url(url, path) |
|
355 |
|
356 def match (self, url) : |
|
357 """ |
|
358 Returns the URL object best corresponding to the given url |
|
359 """ |
|
360 |
|
361 # normalize the URL |
|
362 url = os.path.normpath(url) |
|
363 |
|
364 # split it into labels |
|
365 label_path = url.split('/') |
|
366 |
|
367 # just match starting at root |
|
368 return self.root.match(label_path) |
|
369 |
|
370 def handle_request (self, request) : |
|
371 """ |
|
372 Looks up the request's URL, and invokes its handler |
|
373 """ |
|
374 |
|
375 # get the request's URL path |
|
376 url = self.match(request.get_page_name()) |
|
377 |
|
378 # XXX: figure out the argument values... |
|
379 |
|
380 # let the URL handle it |
|
381 url.execute(request, xxx) |
|
382 |
|
383 # urls |
|
384 index = URL( '/', handlers.index ) |
|
385 channel_view = URL( '/channel/{channel}', handlers.channel_view ) |
|
386 channel_last = URL( '/channel/{channel}/last/{count}/{format}', handlers.channel_last, count=100, format="html" ) |
|
387 channel_search = URL( '/channel/{channel}/search', handlers.channel_search ) |
|
388 |
|
389 # mapper |
|
390 mapper = URLTree([index, channel_view, channel_last, channel_search]) |
|
391 |