urltree.py
changeset 51 a1da82870a6b
parent 50 e4fbf480fbee
child 52 2136cdc95b86
equal deleted inserted replaced
50:e4fbf480fbee 51:a1da82870a6b
    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     
   124         # only empty segments
   133         # only empty segments
   125         if value == '' :
   134         if value == '' :
   126             return True
   135             return True
   127     
   136     
   128     def build (self, values) :
   137     def build (self, values) :
   129         return str(self)
   138         return ''
       
   139 
       
   140     def build_default (self, values) :
       
   141         return (False, '')
   130 
   142 
   131     def __str__ (self) :
   143     def __str__ (self) :
   132         return ''
   144         return ''
   133 
   145 
   134 class StaticLabel (Label) :
   146 class StaticLabel (Label) :
   164         # match name
   176         # match name
   165         if value == self.name :
   177         if value == self.name :
   166             return True
   178             return True
   167 
   179 
   168     def build (self, values) :
   180     def build (self, values) :
   169         return str(self)
   181         return self.name
       
   182 
       
   183     def build_default (self, values) :
       
   184         return (False, self.name)
   170 
   185 
   171     def __str__ (self) :
   186     def __str__ (self) :
   172         return self.name
   187         return self.name
   173 
   188 
   174 class ValueLabel (Label) :
   189 class ValueLabel (Label) :
   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) :