terom@39: """ terom@39: Tree-based URL mapping terom@39: """ terom@39: terom@39: import re terom@39: import os.path terom@39: terom@60: from qmsk.web import handler terom@39: terom@39: class URLError (Exception) : terom@39: """ terom@39: Error with an URL definition terom@39: """ terom@39: terom@39: pass terom@39: terom@39: class LabelValue (object) : terom@39: """ terom@39: Represents the value of a ValueLabel... love these names terom@39: """ terom@39: terom@51: def __init__ (self, label, value, is_default) : terom@39: """ terom@39: Just store terom@39: """ terom@39: terom@39: self.label = label terom@39: self.value = value terom@51: self.is_default = is_default terom@39: terom@39: def __str__ (self) : terom@51: return "%s%s" % (self.label.key, "=%r" % (self.value, ) if not self.is_default else '') terom@39: terom@39: def __repr__ (self) : terom@39: return "<%s>" % self terom@39: terom@39: class Label (object) : terom@39: """ terom@39: Base class for URL labels (i.e. the segments of the URL between /s) terom@39: """ terom@39: terom@39: @staticmethod terom@47: def parse (mask, defaults, config) : terom@39: """ terom@47: Parse the given label-segment, and return a *Label instance. Config is the URLConfig to use terom@39: """ terom@39: terom@39: # empty? terom@39: if not mask : terom@39: return EmptyLabel() terom@39: terom@39: # simple value? terom@39: match = SimpleValueLabel.EXPR.match(mask) terom@39: terom@39: if match : terom@39: # key terom@39: key = match.group('key') terom@39: terom@39: # type terom@49: type_name = match.group("type") terom@39: terom@47: # lookup type, None -> default terom@49: type = config.get_type(type_name) terom@39: terom@39: # defaults? terom@39: default = defaults.get(key) terom@39: terom@39: if not default : terom@39: default = match.group('default') terom@39: terom@39: if default : terom@39: # apply type to default terom@47: default = type.parse(default) terom@39: terom@39: # build terom@49: return SimpleValueLabel(key, type_name, type, default) terom@39: terom@39: # static? terom@39: match = StaticLabel.EXPR.match(mask) terom@39: terom@39: if match : terom@39: return StaticLabel(match.group('name')) terom@39: terom@39: # invalid terom@39: raise URLError("Invalid label: %r" % (mask, )) terom@39: terom@39: def match (self, value=None) : terom@39: """ terom@39: Match this label against the given value, returning either True to match without a value, a LabelValue terom@39: object, or boolean false to not match. terom@39: terom@39: If value is None, this means that only a default value should be returned. terom@39: """ terom@39: terom@39: abstract terom@42: terom@42: def build (self, value_dict) : terom@42: """ terom@42: Return a string representing this label, using the values in the given value_dict if needed terom@42: """ terom@42: terom@42: abstract terom@39: terom@51: def build_default (self, value_dict) : terom@51: """ terom@51: Return an (is_default, value) tuple terom@51: """ terom@51: terom@51: abstract terom@51: terom@51: terom@39: class EmptyLabel (Label) : terom@39: """ terom@39: An empty label, i.e. just a slash in the URL terom@39: """ terom@39: terom@39: def __eq__ (self, other) : terom@39: """ terom@39: Just compares type terom@39: """ terom@39: terom@39: return isinstance(other, EmptyLabel) terom@39: terom@39: def match (self, value=None) : terom@39: """ terom@39: Match empty string -> True terom@39: """ terom@39: terom@39: # no default terom@39: if value is None : terom@39: return False terom@39: terom@39: # only empty segments terom@39: if value == '' : terom@39: return True terom@42: terom@42: def build (self, values) : terom@51: return '' terom@51: terom@51: def build_default (self, values) : terom@51: return (False, '') terom@39: terom@39: def __str__ (self) : terom@39: return '' terom@39: terom@39: class StaticLabel (Label) : terom@39: """ terom@39: A simple literal Label, used for fixed terms in the URL terom@39: """ terom@39: terom@79: EXPR = re.compile(r'^(?P[a-zA-Z0-9_.-]+)$') terom@39: terom@39: def __init__ (self, name) : terom@39: """ terom@39: The given name is the literal name of this label terom@39: """ terom@39: terom@39: self.name = name terom@39: terom@39: def __eq__ (self, other) : terom@39: """ terom@39: Compares names terom@39: """ terom@39: terom@39: return isinstance(other, StaticLabel) and self.name == other.name terom@39: terom@39: def match (self, value=None) : terom@39: """ terom@39: Match exactly -> True terom@39: """ terom@39: terom@39: # no defaults terom@39: if value is None : terom@39: return False terom@39: terom@39: # match name terom@39: if value == self.name : terom@39: return True terom@39: terom@42: def build (self, values) : terom@51: return self.name terom@51: terom@51: def build_default (self, values) : terom@51: return (False, self.name) terom@42: terom@39: def __str__ (self) : terom@39: return self.name terom@39: terom@39: class ValueLabel (Label) : terom@39: """ terom@39: A label with a key and a value terom@39: terom@39: XXX: do we even need this? terom@39: """ terom@39: terom@39: def __init__ (self, key, default) : terom@39: """ terom@39: Set the key and default value. Default value may be None if there is no default value defined terom@39: """ terom@39: terom@39: self.key = key terom@39: self.default = default terom@39: terom@39: def __eq__ (self, other) : terom@39: """ terom@39: Compares keys terom@39: """ terom@39: terom@39: return isinstance(other, ValueLabel) and self.key == other.key terom@42: terom@42: def build (self, values) : terom@42: """ terom@42: Return either the assigned value from values, our default value, or raise an error terom@42: """ terom@48: terom@54: # just proxy to build_default terom@79: return self.build_default(values)[1] terom@39: terom@51: def build_default (self, values) : terom@51: """ terom@51: Check if we have a value in values, and return based on that terom@51: """ terom@54: terom@51: # state terom@51: is_default = False terom@51: terom@54: # value given? terom@54: if self.key not in values or values[self.key] is None : terom@54: # error on missing non-default terom@54: if self.default is None : terom@54: raise URLError("No value given for label %r" % (self.key, )) terom@54: terom@54: # use default terom@54: else : terom@54: is_default = True terom@54: value = self.default terom@51: terom@54: else : terom@54: # lookup the value obj to use terom@54: value = values[self.key] terom@51: terom@51: # convert value back to str terom@51: value = self.type.build(value) terom@51: terom@51: # return terom@51: return (is_default, value) terom@51: terom@39: class SimpleValueLabel (ValueLabel) : terom@39: """ terom@39: A label that has a name and a simple string value terom@39: """ terom@39: terom@58: EXPR = re.compile(r'^\{(?P[a-zA-Z_][a-zA-Z0-9_]*)(:(?P[a-zA-Z_][a-zA-Z0-9_]*))?(=(?P[^}]*))?\}$') terom@39: terom@49: def __init__ (self, key, type_name, type, default) : terom@39: """ terom@49: The given key is the name of this label's value. terom@49: terom@49: The given type_name is is None for the default type, otherwise the type's name. Type is a URLType. terom@39: """ terom@39: terom@39: # type terom@49: self.type_name = type_name terom@39: self.type = type terom@39: terom@39: # store terom@39: self.key = key terom@39: self.default = default terom@39: terom@39: def match (self, value=None) : terom@39: """ terom@39: Match -> LabelValue terom@39: """ terom@39: terom@39: # default? terom@50: if value is None and self.default is not None : terom@51: return LabelValue(self, self.default, True) terom@39: terom@39: # only non-empty values! terom@39: elif value : terom@47: # test terom@47: if not self.type.test(value) : terom@47: return False terom@47: terom@39: # convert with type terom@47: value = self.type.parse(value) terom@39: terom@51: return LabelValue(self, value, False) terom@39: terom@39: def __str__ (self) : terom@39: return '{%s%s%s}' % ( terom@39: self.key, terom@79: (':%s' % (self.type_name, ) if self.type_name is not None else ''), terom@79: '=%s' % (self.default, ) if self.default is not None else '', terom@39: ) terom@41: terom@47: class URLType (object) : terom@47: """ terom@47: Handles the type-ness of values in the URL terom@47: """ terom@47: terom@47: def test (self, value) : terom@47: """ terom@47: Tests if the given value is valid for this type. terom@47: terom@47: Defaults to calling parse(), and returning False on errors, True otherwise terom@47: """ terom@47: terom@47: try : terom@47: self.parse(value) terom@47: terom@47: except : terom@47: return False terom@47: terom@47: else : terom@47: return True terom@47: terom@47: def parse (self, value) : terom@47: """ terom@47: Parse the given value, which was tested earlier with test(), and return the value object terom@47: """ terom@47: terom@47: abstract terom@47: terom@58: def append (self, old_value, value) : terom@58: """ terom@58: Handle multiple values for this type, by combining the given old value and new value (both from parse). terom@58: terom@58: Defaults to raise an error terom@58: """ terom@58: terom@58: raise URLError("Multiple values for argument") terom@47: terom@47: def build (self, obj) : terom@47: """ terom@47: Reverse of parse(), return an url-value built from the given object terom@47: """ terom@47: terom@47: abstract terom@47: terom@58: def build_multi (self, obj) : terom@58: """ terom@58: Return a list of string values for the given object value (as from parse/append). terom@58: terom@58: Defaults to return [self.build(obj)] terom@58: """ terom@58: terom@58: return [self.build(obj)] terom@58: terom@47: class URLStringType (URLType) : terom@47: """ terom@47: The default URLType, just plain strings. terom@80: terom@80: XXX: decodeing here, or elsewhere? terom@47: """ terom@47: terom@47: def parse (self, value) : terom@47: """ terom@47: Identitiy terom@47: """ terom@47: terom@47: return value terom@47: terom@47: def build (self, obj) : terom@47: return str(obj) terom@47: terom@47: class URLIntegerType (URLType) : terom@47: """ terom@47: A URLType for simple integers terom@47: """ terom@47: terom@50: def __init__ (self, allow_negative=True, allow_zero=True, max=None) : terom@47: """ terom@50: Pass in allow_negative=False to disallow negative numbers, allow_zero=False to disallow zero, or non-zero terom@50: max to specifiy maximum value terom@47: """ terom@47: terom@50: self.allow_negative = allow_negative terom@50: self.allow_zero = allow_zero terom@47: self.max = max terom@47: terom@47: def _validate (self, value) : terom@47: """ terom@47: Test to make sure value fits our criteria terom@47: """ terom@47: terom@47: # negative? terom@50: if not self.allow_negative and value < 0 : terom@47: raise ValueError("value is negative") terom@47: terom@47: # zero? terom@50: if not self.allow_zero and value == 0 : terom@47: raise ValueError("value is zero") terom@47: terom@47: # max? terom@47: if self.max is not None and value > max : terom@47: raise ValueError("value is too large: %d" % value) terom@47: terom@47: return value terom@47: terom@47: def parse (self, value) : terom@47: """ terom@47: Convert str -> int terom@47: """ terom@47: terom@47: return self._validate(int(value)) terom@47: terom@47: def build (self, obj) : terom@47: """ terom@47: Convert int -> str terom@47: """ terom@47: terom@47: return unicode(self._validate(obj)) terom@47: terom@58: class URLListType (URLType) : terom@58: """ terom@58: A list of strings terom@58: """ terom@58: terom@58: def parse (self, value) : terom@58: return [value] terom@58: terom@58: def append (self, old_value, value) : terom@58: return old_value + value terom@58: terom@58: def build_multi (self, obj) : terom@58: return obj terom@58: terom@41: class URLConfig (object) : terom@41: """ terom@47: Global configuration relevant to all URLs. This can be used to construct a set of URLs and then create an terom@47: URLTree out of them. Simply call the url_config() instace with the normal URL arguments (except, of course, terom@47: config), and finally just pass the url_config to URLTree (it's iterable). terom@49: terom@49: XXX: rename to URLFactory? terom@41: """ terom@41: terom@41: # built-in type codes terom@41: BUILTIN_TYPES = { terom@49: # default - string terom@49: None : URLStringType(), terom@49: terom@43: # string terom@47: 'str' : URLStringType(), terom@43: terom@41: # integer terom@47: 'int' : URLIntegerType(), terom@58: terom@58: # list of strs terom@58: 'list' : URLListType(), terom@41: } terom@41: terom@49: def __init__ (self, type_dict=None, ignore_extra_args=True) : terom@41: """ terom@41: Create an URLConfig for use with URL terom@41: terom@47: If type_dict is given, it should be a dict of { type_names: URLType }, and they will be available for terom@49: type specifications in addition to the defaults. This will call type._init_name with the key, so do terom@49: *not* initialize the name yourself. terom@49: terom@49: If ignore_extra_args is given, unrecognized query arguments will be ignored. terom@41: """ terom@41: terom@41: # build our type_dict terom@41: self.type_dict = self.BUILTIN_TYPES.copy() terom@41: terom@41: # apply the given type_dict terom@41: if type_dict : terom@47: # merge terom@41: self.type_dict.update(type_dict) terom@41: terom@47: # init terom@49: self.ignore_extra_args = ignore_extra_args terom@47: self.urls = [] terom@47: terom@47: def get_type (self, type_name=None) : terom@47: """ terom@49: Lookup an URLType by type_name, None for default. terom@47: """ terom@47: terom@47: # lookup + return terom@47: return self.type_dict[type_name] terom@47: terom@47: def __call__ (self, *args, **kwargs) : terom@47: """ terom@47: Return new URL object with this config and the given args, adding it to our list of urls terom@47: """ terom@47: terom@47: # build terom@47: url = URL(self, *args, **kwargs) terom@47: terom@47: # store terom@47: self.urls.append(url) terom@47: terom@47: # return terom@47: return url terom@47: terom@47: def __iter__ (self) : terom@47: """ terom@47: Returns all defined URLs terom@47: """ terom@47: terom@47: return iter(self.urls) terom@47: terom@39: class URL (object) : terom@39: """ terom@39: Represents a specific URL terom@39: """ terom@39: terom@41: terom@47: def __init__ (self, config, url_mask, handler, **defaults) : terom@39: """ terom@41: Create an URL using the given URLConfig, with the given url mask, handler, and default values. terom@39: """ terom@39: terom@39: # store terom@41: self.config = config terom@39: self.url_mask = url_mask terom@39: self.handler = handler terom@39: self.defaults = defaults terom@39: terom@42: # query string terom@42: self.query_args = dict() terom@51: terom@51: # remove prepending root / terom@51: url_mask = url_mask.lstrip('/') terom@42: terom@42: # parse any query string terom@42: # XXX: conflicts with regexp syntax terom@42: if '/?' in url_mask : terom@42: url_mask, query_mask = url_mask.split('/?') terom@42: terom@42: else : terom@42: query_mask = None terom@42: terom@42: # build our label path terom@47: self.label_path = [Label.parse(mask, defaults, config) for mask in url_mask.split('/')] terom@42: terom@42: # build our query args list terom@42: if query_mask : terom@42: # split into items terom@42: for query_item in query_mask.split('&') : terom@42: # parse default terom@42: if '=' in query_item : terom@42: query_item, default = query_item.split('=') terom@42: terom@42: else : terom@42: default = None terom@42: terom@42: # parse type terom@42: if ':' in query_item : terom@42: query_item, type = query_item.split(':') terom@42: else : terom@42: type = None terom@42: terom@42: # parse key terom@42: key = query_item terom@42: terom@43: # type terom@47: type = self.config.get_type(type) terom@43: terom@42: # add to query_args as (type, default) tuple terom@47: self.query_args[key] = (type, type.parse(default) if default else default) terom@42: terom@39: def get_label_path (self) : terom@39: """ terom@39: Returns a list containing the labels in this url terom@39: """ terom@39: terom@39: # copy self.label_path terom@39: return list(self.label_path) terom@39: terom@39: def execute (self, request, label_values) : terom@39: """ terom@39: Invoke the handler, using the given label values terom@39: """ terom@39: terom@39: # start with the defaults terom@40: kwargs = self.defaults.copy() terom@39: terom@51: # ...dict of those label values which are set to defaults terom@51: default_labels = {} terom@51: terom@39: # then add all the values terom@39: for label_value in label_values : terom@39: kwargs[label_value.label.key] = label_value.value terom@51: terom@51: # add key to default_values? terom@51: if label_value.is_default : terom@51: default_labels[label_value.label.key] = label_value.label terom@42: terom@42: # then parse all query args terom@42: for key, value in request.get_args() : terom@51: # lookup in our defined query args terom@51: if key in self.query_args : terom@51: # lookup spec terom@51: type, default = self.query_args[key] terom@51: terom@51: # override URL params if they were not given terom@51: elif key in default_labels : terom@51: type, default = default_labels[key].type, None terom@51: terom@51: # be strict about extraneous query args? terom@52: elif not self.config.ignore_extra_args : terom@51: raise URLError("Unrecognized query argument: %r" % (key, )) terom@52: terom@52: # ignore terom@52: else : terom@52: continue terom@42: terom@42: # normalize empty value to None terom@42: if not value : terom@42: value = None terom@42: terom@42: else : terom@47: # parse value terom@47: value = type.parse(value) terom@42: terom@42: # set default? terom@56: if value is None : terom@42: if default : terom@42: value = default terom@42: terom@42: if default == '' : terom@42: # do not pass key at all terom@42: continue terom@42: terom@42: # otherwise, fail terom@42: raise URLError("No value given for required argument: %r" % (key, )) terom@39: terom@62: # already have a non-default value? terom@62: if key in kwargs and key not in default_labels : terom@58: # append to old value terom@58: kwargs[key] = type.append(kwargs[key], value) terom@58: terom@58: else : terom@58: # set key terom@58: kwargs[key] = value terom@43: terom@43: # then check all query args terom@43: for key, (type, default) in self.query_args.iteritems() : terom@43: # skip those already present terom@43: if key in kwargs : terom@43: continue terom@43: terom@43: # apply default? terom@43: if default is None : terom@43: raise URLError("Missing required argument: %r" % (key, )) terom@43: terom@43: elif default == '' : terom@43: # skip empty default terom@43: continue terom@43: terom@43: else : terom@43: # set default terom@43: kwargs[key] = default terom@42: terom@39: # execute the handler terom@39: return self.handler(request, **kwargs) terom@42: terom@42: def build (self, request, **values) : terom@42: """ terom@51: Build an absolute URL pointing to this target, with the given values. Default values are left off if they terom@51: are at the end of the URL. terom@54: terom@54: Values given as None are ignored. terom@42: """ terom@51: terom@51: # collect segments as a list of (is_default, segment) values terom@51: segments = [(False, request.page_prefix)] + [label.build_default(values) for label in self.label_path] terom@51: terom@51: # trim default items off the end terom@51: for is_default, segment in segments[::-1] : terom@51: if is_default : terom@51: segments.pop(-1) terom@51: terom@51: else : terom@51: break terom@42: terom@51: assert segments terom@51: terom@51: # join terom@54: url = '/'.join(segment for is_default, segment in segments if segment is not None) terom@53: terom@58: # build query args as { key -> [value] } terom@58: query_args = dict((key, type.build_multi(values[key])) for key, (type, default) in self.query_args.iteritems() if key in values and values[key] is not None) terom@53: terom@59: return "%s%s" % (url, '?%s' % ('&'.join('%s=%s' % (key, value) for key, values in query_args.iteritems() for value in values)) if query_args else '') terom@39: terom@39: def __str__ (self) : terom@39: return '/'.join(str(label) for label in self.label_path) terom@39: terom@39: def __repr__ (self) : terom@39: return "URL(%r, %r)" % (str(self), self.handler) terom@39: terom@39: class URLNode (object) : terom@39: """ terom@39: Represents a node in the URLTree terom@39: """ terom@39: terom@39: def __init__ (self, parent, label) : terom@39: """ terom@39: Initialize with the given parent and label, empty children dict terom@39: """ terom@39: terom@39: # the parent URLNode terom@39: self.parent = parent terom@39: terom@39: # this node's Label terom@39: self.label = label terom@39: terom@39: # list of child URLNodes terom@39: self.children = [] terom@39: terom@39: # this node's URL, set by add_url for an empty label_path terom@39: self.url = None terom@39: terom@39: def _build_child (self, label) : terom@39: """ terom@39: Build, insert and return a new child Node terom@39: """ terom@39: terom@39: # build new child terom@39: child = URLNode(self, label) terom@39: terom@39: # add to children terom@39: self.children.append(child) terom@39: terom@39: # return terom@39: return child terom@39: terom@39: def add_url (self, url, label_path) : terom@39: """ terom@39: Add a URL object to this node under the given path. Uses recursion to process the path. terom@39: terom@39: The label_path argument is a (partial) label path as returned by URL.get_label_path. terom@39: terom@39: If label_path is empty (len zero, or begins with EmptyLabel), then the given url is assigned to this node, if no terom@39: url was assigned before. terom@39: """ terom@39: terom@39: # matches this node? terom@39: if not label_path or isinstance(label_path[0], EmptyLabel) : terom@39: if self.url : terom@39: raise URLError(url, "node already defined") terom@39: terom@39: else : terom@39: # set terom@39: self.url = url terom@39: terom@39: else : terom@39: # pop child label from label_path terom@39: child_label = label_path.pop(0) terom@39: terom@39: # look for the child to recurse into terom@39: child = None terom@39: terom@39: # look for an existing child with that label terom@39: for child in self.children : terom@39: if child.label == child_label : terom@39: # found, use this terom@39: break terom@39: terom@39: else : terom@39: # build a new child terom@39: child = self._build_child(child_label) terom@39: terom@39: # recurse to handle the rest of the label_path terom@39: child.add_url(url, label_path) terom@39: terom@39: def match (self, label_path) : terom@39: """ terom@39: Locate the URL object corresponding to the given label_path value under this node. terom@39: terom@39: Returns a (url, label_values) tuple terom@39: """ terom@39: terom@39: # determine value to use terom@39: value = None terom@39: terom@39: # empty label_path? terom@39: if not label_path or label_path[0] == '' : terom@39: # the search ends at this node terom@39: if self.url : terom@39: # this URL is the best match terom@39: return (self.url, []) terom@39: terom@39: elif not self.children : terom@39: # incomplete URL terom@39: raise URLError("no URL handler defined for this Node") terom@39: terom@39: else : terom@39: # use default value, i.e. Label.match(None) terom@39: label = None terom@39: terom@39: else : terom@39: # pop the next label from the label path terom@39: label = label_path.pop(0) terom@39: terom@39: # return one match... terom@39: match = value = None terom@39: terom@39: # recurse through our children, DFS terom@39: for child in self.children : terom@39: # match value terom@39: value = child.label.match(label) terom@39: terom@39: # skip those that don't match at all terom@39: if not value : terom@39: continue; terom@39: terom@39: # already found a match? :/ terom@39: if match : terom@39: raise URLError("Ambiguous URL") terom@39: terom@39: # ok, but continue looking to make sure there's no ambiguous URLs terom@39: match = child terom@39: terom@39: # found something? terom@39: if not match : terom@40: raise URLError("No child found for label: %s + %s + %s" % (self.get_url(), label, '/'.join(str(l) for l in label_path))) terom@39: terom@39: # ok, recurse into the match terom@39: url, label_value = match.match(label_path) terom@39: terom@39: # add our value? terom@39: if isinstance(value, LabelValue) : terom@39: label_value.append(value) terom@39: terom@39: # return the match terom@39: return url, label_value terom@40: terom@40: def get_url (self) : terom@40: """ terom@40: Returns the URL for this node, by iterating over our parents terom@40: """ terom@40: terom@40: # URL segments in reverse order terom@40: segments = [''] terom@40: terom@40: # start with ourself terom@40: node = self terom@40: terom@40: # iterate up to root terom@40: while node : terom@40: segments.append(str(node.label)) terom@40: terom@40: node = node.parent terom@40: terom@40: # reverse terom@40: segments.reverse() terom@40: terom@40: # return terom@40: return '/'.join(segments) terom@40: terom@39: def dump (self, indent=0) : terom@39: """ terom@39: Returns a multi-line string representation of this Node terom@39: """ terom@39: terom@39: return '\n'.join([ terom@39: "%-45s%s" % ( terom@39: ' '*indent + str(self.label) + ('/' if self.children else ''), terom@39: (' -> %r' % self.url) if self.url else '' terom@39: ) terom@39: ] + [ terom@39: child.dump(indent + 4) for child in self.children terom@39: ]) terom@39: terom@39: def __str__ (self) : terom@39: return "%s/[%s]" % (self.label, ','.join(str(child) for child in self.children)) terom@39: terom@60: class URLTree (handler.RequestHandler) : terom@39: """ terom@39: Map requests to handlers, using a defined tree of URLs terom@39: """ terom@39: terom@39: def __init__ (self, url_list) : terom@39: """ terom@39: Initialize the tree using the given list of URLs terom@39: """ terom@39: terom@39: # root node terom@39: self.root = URLNode(None, EmptyLabel()) terom@39: terom@39: # just add each URL terom@39: for url in url_list : terom@39: self.add_url(url) terom@39: terom@39: def add_url (self, url) : terom@39: """ terom@39: Adds the given URL to the tree. The URL must begin with a root slash. terom@39: """ terom@51: terom@39: # get url's label path terom@39: path = url.get_label_path() terom@39: terom@39: # add to root terom@39: self.root.add_url(url, path) terom@39: terom@39: def match (self, url) : terom@39: """ terom@39: Find the URL object best corresponding to the given url, matching any ValueLabels. terom@39: terom@39: Returns an (URL, [LabelValue]) tuple. terom@47: terom@47: XXX: handle unicode on URLs terom@39: """ terom@39: terom@39: # split it into labels terom@39: path = url.split('/') terom@40: terom@40: # empty URL is empty terom@40: if url : terom@40: # ensure that it doesn't start with a / terom@40: assert not self.root.label.match(path[0]), "URL must not begin with root" terom@39: terom@39: # just match starting at root terom@39: return self.root.match(path) terom@39: terom@39: def handle_request (self, request) : terom@39: """ terom@39: Looks up the request's URL, and invokes its handler terom@39: """ terom@39: terom@40: # get the requested URL terom@40: request_url = request.get_page_name() terom@40: terom@40: # find the URL+values to use terom@40: url, label_values = self.match(request_url) terom@39: terom@39: # let the URL handle it terom@40: return url.execute(request, label_values) terom@39: terom@39: