17 class LabelValue (object) : |
17 class LabelValue (object) : |
18 """ |
18 """ |
19 Represents the value of a ValueLabel... love these names |
19 Represents the value of a ValueLabel... love these names |
20 """ |
20 """ |
21 |
21 |
22 def __init__ (self, label, value) : |
22 def __init__ (self, label, value, is_default) : |
23 """ |
23 """ |
24 Just store |
24 Just store |
25 """ |
25 """ |
26 |
26 |
27 self.label = label |
27 self.label = label |
28 self.value = value |
28 self.value = value |
|
29 self.is_default = is_default |
29 |
30 |
30 def __str__ (self) : |
31 def __str__ (self) : |
31 return "%s=%r" % (self.label.key, self.value) |
32 return "%s%s" % (self.label.key, "=%r" % (self.value, ) if not self.is_default else '') |
32 |
33 |
33 def __repr__ (self) : |
34 def __repr__ (self) : |
34 return "<%s>" % self |
35 return "<%s>" % self |
35 |
36 |
36 class Label (object) : |
37 class Label (object) : |
98 Return a string representing this label, using the values in the given value_dict if needed |
99 Return a string representing this label, using the values in the given value_dict if needed |
99 """ |
100 """ |
100 |
101 |
101 abstract |
102 abstract |
102 |
103 |
|
104 def build_default (self, value_dict) : |
|
105 """ |
|
106 Return an (is_default, value) tuple |
|
107 """ |
|
108 |
|
109 abstract |
|
110 |
|
111 |
103 class EmptyLabel (Label) : |
112 class EmptyLabel (Label) : |
104 """ |
113 """ |
105 An empty label, i.e. just a slash in the URL |
114 An empty label, i.e. just a slash in the URL |
106 """ |
115 """ |
107 |
116 |
199 """ |
214 """ |
200 |
215 |
201 # lookup the value obj to use |
216 # lookup the value obj to use |
202 value = values.get(self.key) |
217 value = values.get(self.key) |
203 |
218 |
204 if not value and self.default : |
219 if not value and self.default is not None : |
205 value = self.default |
220 value = self.default |
206 |
221 |
207 elif not value : |
222 elif not value : |
208 raise URLError("No value given for label %r" % (self.key, )) |
223 raise URLError("No value given for label %r" % (self.key, )) |
209 |
224 |
210 # convert value back to str |
225 # convert value back to str |
211 value = self.type.build(value) |
226 value = self.type.build(value) |
212 |
227 |
213 return value |
228 return value |
|
229 |
|
230 def build_default (self, values) : |
|
231 """ |
|
232 Check if we have a value in values, and return based on that |
|
233 |
|
234 XXX: copy-paste from build() |
|
235 """ |
|
236 |
|
237 # state |
|
238 is_default = False |
|
239 |
|
240 # lookup the value obj to use |
|
241 value = values.get(self.key) |
|
242 |
|
243 if not value and self.default is not None : |
|
244 is_default = True |
|
245 value = self.default |
|
246 |
|
247 elif not value : |
|
248 raise URLError("No value given for label %r" % (self.key, )) |
|
249 |
|
250 # convert value back to str |
|
251 value = self.type.build(value) |
|
252 |
|
253 # return |
|
254 return (is_default, value) |
|
255 |
214 |
256 |
215 class SimpleValueLabel (ValueLabel) : |
257 class SimpleValueLabel (ValueLabel) : |
216 """ |
258 """ |
217 A label that has a name and a simple string value |
259 A label that has a name and a simple string value |
218 """ |
260 """ |
239 Match -> LabelValue |
281 Match -> LabelValue |
240 """ |
282 """ |
241 |
283 |
242 # default? |
284 # default? |
243 if value is None and self.default is not None : |
285 if value is None and self.default is not None : |
244 return LabelValue(self, self.default) |
286 return LabelValue(self, self.default, True) |
245 |
287 |
246 # only non-empty values! |
288 # only non-empty values! |
247 elif value : |
289 elif value : |
248 # test |
290 # test |
249 if not self.type.test(value) : |
291 if not self.type.test(value) : |
250 return False |
292 return False |
251 |
293 |
252 # convert with type |
294 # convert with type |
253 value = self.type.parse(value) |
295 value = self.type.parse(value) |
254 |
296 |
255 return LabelValue(self, value) |
297 return LabelValue(self, value, False) |
256 |
298 |
257 def __str__ (self) : |
299 def __str__ (self) : |
258 return '{%s%s%s}' % ( |
300 return '{%s%s%s}' % ( |
259 self.key, |
301 self.key, |
260 (':%s' % (self.type_name, ) if self.type_name else ''), |
302 (':%s' % (self.type_name, ) if self.type_name else ''), |
455 self.handler = handler |
497 self.handler = handler |
456 self.defaults = defaults |
498 self.defaults = defaults |
457 |
499 |
458 # query string |
500 # query string |
459 self.query_args = dict() |
501 self.query_args = dict() |
|
502 |
|
503 # remove prepending root / |
|
504 url_mask = url_mask.lstrip('/') |
460 |
505 |
461 # parse any query string |
506 # parse any query string |
462 # XXX: conflicts with regexp syntax |
507 # XXX: conflicts with regexp syntax |
463 if '/?' in url_mask : |
508 if '/?' in url_mask : |
464 url_mask, query_mask = url_mask.split('/?') |
509 url_mask, query_mask = url_mask.split('/?') |
509 """ |
554 """ |
510 |
555 |
511 # start with the defaults |
556 # start with the defaults |
512 kwargs = self.defaults.copy() |
557 kwargs = self.defaults.copy() |
513 |
558 |
|
559 # ...dict of those label values which are set to defaults |
|
560 default_labels = {} |
|
561 |
514 # then add all the values |
562 # then add all the values |
515 for label_value in label_values : |
563 for label_value in label_values : |
516 kwargs[label_value.label.key] = label_value.value |
564 kwargs[label_value.label.key] = label_value.value |
|
565 |
|
566 # add key to default_values? |
|
567 if label_value.is_default : |
|
568 default_labels[label_value.label.key] = label_value.label |
517 |
569 |
518 # then parse all query args |
570 # then parse all query args |
519 for key, value in request.get_args() : |
571 for key, value in request.get_args() : |
520 # unknown key? |
572 # lookup in our defined query args |
521 if key not in self.query_args : |
573 if key in self.query_args : |
522 # ignore? |
574 # lookup spec |
523 if self.config.ignore_extra_args : |
575 type, default = self.query_args[key] |
524 continue |
576 |
525 |
577 # override URL params if they were not given |
526 else : |
578 elif key in default_labels : |
527 raise URLError("Unrecognized query argument: %r" % (key, )) |
579 type, default = default_labels[key].type, None |
528 |
580 |
529 # lookup spec |
581 # be strict about extraneous query args? |
530 type, default = self.query_args[key] |
582 elif self.config.ignore_extra_args : |
|
583 raise URLError("Unrecognized query argument: %r" % (key, )) |
531 |
584 |
532 # normalize empty value to None |
585 # normalize empty value to None |
533 if not value : |
586 if not value : |
534 value = None |
587 value = None |
535 |
588 |
573 # execute the handler |
626 # execute the handler |
574 return self.handler(request, **kwargs) |
627 return self.handler(request, **kwargs) |
575 |
628 |
576 def build (self, request, **values) : |
629 def build (self, request, **values) : |
577 """ |
630 """ |
578 Build an absolute URL pointing to this target, with the given values |
631 Build an absolute URL pointing to this target, with the given values. Default values are left off if they |
579 """ |
632 are at the end of the URL. |
580 |
633 """ |
581 # build URL from request page prefix and our labels |
634 |
582 return request.page_prefix + '/'.join(label.build(values) for label in self.label_path) |
635 # collect segments as a list of (is_default, segment) values |
|
636 segments = [(False, request.page_prefix)] + [label.build_default(values) for label in self.label_path] |
|
637 |
|
638 # trim default items off the end |
|
639 for is_default, segment in segments[::-1] : |
|
640 if is_default : |
|
641 segments.pop(-1) |
|
642 |
|
643 else : |
|
644 break |
|
645 |
|
646 assert segments |
|
647 |
|
648 # join |
|
649 return '/'.join(segment for is_default, segment in segments) |
583 |
650 |
584 def __str__ (self) : |
651 def __str__ (self) : |
585 return '/'.join(str(label) for label in self.label_path) |
652 return '/'.join(str(label) for label in self.label_path) |
586 |
653 |
587 def __repr__ (self) : |
654 def __repr__ (self) : |
783 |
850 |
784 def add_url (self, url) : |
851 def add_url (self, url) : |
785 """ |
852 """ |
786 Adds the given URL to the tree. The URL must begin with a root slash. |
853 Adds the given URL to the tree. The URL must begin with a root slash. |
787 """ |
854 """ |
|
855 |
788 # get url's label path |
856 # get url's label path |
789 path = url.get_label_path() |
857 path = url.get_label_path() |
790 |
|
791 # should begin with root |
|
792 root_label = path.pop(0) |
|
793 assert root_label == self.root.label, "URL must begin with root" |
|
794 |
858 |
795 # add to root |
859 # add to root |
796 self.root.add_url(url, path) |
860 self.root.add_url(url, path) |
797 |
861 |
798 def match (self, url) : |
862 def match (self, url) : |