sites/irclogs.qmsk.net/urls.py
branchsites
changeset 36 02d4040d5946
parent 35 79ea0e2a6cc2
child 37 1f13c384508e
equal deleted inserted replaced
35:79ea0e2a6cc2 36:02d4040d5946
     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