bloat code with even more layers of indirection, split off the filesystem-based stuff into a separate lib.filesystem package (next, move it to sites/www.qmsk.net) sites
authorTero Marttila <terom@fixme.fi>
Sat, 07 Feb 2009 16:33:27 +0200
branchsites
changeset 31 107062ebb6f9
parent 30 a86a25a9f75b
child 32 be954df4f0e8
bloat code with even more layers of indirection, split off the filesystem-based stuff into a separate lib.filesystem package (next, move it to sites/www.qmsk.net)
index.cgi
lib/filesystem/__init__.py
lib/filesystem/map.py
lib/filesystem/menu.py
lib/filesystem/page.py
lib/filesystem/page_tree.py
lib/handler.py
lib/map.py
lib/menu.py
lib/page.py
lib/page_tree.py
lib/site.py
lib/wsgi.py
sites/www.qmsk.net/__init__.py
--- a/index.cgi	Sat Feb 07 06:54:52 2009 +0200
+++ b/index.cgi	Sat Feb 07 16:33:27 2009 +0200
@@ -27,19 +27,16 @@
     """
 
     try :
-        from lib import page_tree
-
-        # load page list
-        page_tree.load()
-        
-        # our WSGI app
-        from lib import wsgi
+        from lib import wsgi, site
 
         # create handler
         cgi_handler = wsgiref.handlers.CGIHandler()
+
+        # create app
+        app = wsgi.Application(site.SiteModuleCollection('sites'))
         
         # run once
-        cgi_handler.run(wsgi.app)
+        cgi_handler.run(app)
 
     except :
         cgi_error()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/filesystem/map.py	Sat Feb 07 16:33:27 2009 +0200
@@ -0,0 +1,131 @@
+
+import os, os.path
+
+from lib import http, template, map
+
+import page, page_tree, menu
+
+class FilesystemMapper (map.Mapper) :
+    """
+        Translates requests to handlers based on a filesystem directory containing various kinds of files
+    """
+
+    # list of page handlers, by type
+    PAGE_TYPES = [
+        ('html',                    page.HTMLPage           ),
+        (template.TEMPLATE_EXT,     page.TemplatePage       ),
+    ]
+
+    def __init__ (self, path) :
+        """
+            Create, path is where the pages are stored. The list of pages is loaded from $path/list
+        """
+        
+        # store
+        self.path = path
+
+        # load the page tree
+        self.tree = page_tree.PageTree(path + '/list')
+    
+    def _lookup_page_type (self, url, path, filename, basename, extension, tail) :
+        """
+            We found the file that we looked for, now get the correct type
+        """
+
+        # find appropriate handler
+        for handler_ext, type in self.PAGE_TYPES :
+            # match against file extension?
+            if handler_ext == extension :
+                # found handler, return instance
+                return type(self, url, path, basename, tail)
+
+        # no handler found
+        raise PageError("No handler found for page %r of type %r" % (url, extension))
+
+    def _lookup_page (self, name) :
+        """
+            Look up and return a Page object for the given page, or raise an error
+        """
+
+        # inital path
+        path = self.path
+        url_segments = []
+
+        # name segments
+        segments = name.split('/')
+
+        # iterate through the parts of the page segments
+        while True :
+            segment = None
+
+            # pop segment
+            if segments :
+                segment = segments.pop(0)
+
+                url_segments.append(segment)
+
+            # translate empty -> index
+            if not segment :
+                segment = 'index'
+
+            # look for it in the dir
+            for filename in os.listdir(path) :
+                # build full file path
+                file_path = os.path.join(path, filename)
+
+                # stat, recurse into subdirectory?
+                if os.path.isdir(file_path) and filename == segment :
+                    # use new dir
+                    path = file_path
+
+                    # break for-loop to look at next segment
+                    break
+     
+                # split into basename + extension
+                basename, extension = os.path.splitext(filename)
+
+                # ...remove that dot
+                extension = extension.lstrip('.')
+                
+                # match against requested page name?
+                if basename == segment :
+                    # found the file we wanted
+                    return self._lookup_page_type('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments))
+                
+                else :
+                    # inspect next file in dir
+                    continue
+
+            else :
+                # did not find any dir or file, break out of while loop
+                break
+
+        # did not find the filename we were looking for in os.listdir
+        raise PageError("Page not found: %s" % name, status='404 Not Found')
+
+    def handle_request (self, request) :
+        """
+            Looks up the appropriate Page, and then renders it
+        """
+
+        # determine the page name
+        page_name = request.get_page_name()
+
+        # get the page handler
+        p = self._lookup_page(page_name)
+
+        # bind to request
+        p.bind_request(request)
+
+        # render the template
+        response_data = template.render("layout",
+            site_root_url   = request.get_script_dir(),
+            site_page_url   = request.get_page_prefix(),
+            page            = p,
+            menu            = menu.Menu(self, p),
+        )
+        
+        # return the response
+        return http.Response(response_data)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/filesystem/menu.py	Sat Feb 07 16:33:27 2009 +0200
@@ -0,0 +1,23 @@
+"""
+    Handling the list of available pages
+"""
+
+class Menu (object) :
+    """
+        Contains info needed to render the menu
+    """
+
+    def __init__ (self, fs, page) :
+        """
+            Gather the menu information for the given page, as part of the given FilesystemMapper
+        """
+
+        # the selected page
+        self.page = fs.tree.get_page(page.url)
+
+        # the selected pagen's inheritance
+        self.ancestry = self.page.get_ancestry() if self.page else []
+        
+        # list of menu items == root children, since we always show the full menu...
+        self.items = fs.tree.root.children
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/filesystem/page.py	Sat Feb 07 16:33:27 2009 +0200
@@ -0,0 +1,132 @@
+
+"""
+    Handling page requests
+"""
+
+# for filesystem ops
+import os, os.path
+import time
+
+from lib import http, template, config
+
+class PageError (http.ResponseError) :
+    """
+        Error looking up/handling a page
+    """
+
+    pass
+
+# XXX: should inherit from PageInfo
+class Page (object) :
+    """
+        This object represents the information about our attempt to render some specific page
+    """
+
+    def __init__ (self, fs, url, path, basename, url_tail, charset='utf8') :
+        """
+            Initialize the page at the given location
+            
+            @param fs the FilesysteMapper
+            @param url the URL leading to this page
+            @param path the filesystem path to this page's file
+            @param basename the filesystem name of this page's file, without the file extension
+            @param url_trail trailing URL for this page
+            @param charset file charset
+        """
+        
+        # store
+        self.fs = fs
+        self.url = url
+        self.path = path
+        self.basename = basename
+        self.url_tail = url_tail
+        self.charset = charset
+
+        # unbound
+        self.request = None
+
+        # sub-init
+        self._init()
+
+    def _init (self) :
+        """
+            Do initial data loading, etc
+        """
+        
+        pass
+
+    def bind_request (self, request) :
+        """
+            Bind this page-render to the given request
+        """
+
+        self.request = request
+
+    @property
+    def title (self) :
+        """
+            Return the page's title
+
+            Defaults to the retreiving the page title from page_list, or basename in Titlecase.
+        """
+        
+        # lookup in PageTree
+        page_info = self.fs.tree.get_page(self.url)
+        
+        # fallback to titlecase
+        if page_info :
+            title = page_info.title
+
+        else :
+            title = self.basename.title()
+
+        return title
+    
+    @property
+    def content (self) :
+        """
+            Return the page content as a string
+        """
+
+        abstract
+    
+    @property
+    def modified (self) :
+        """
+            Returns the page modification timestamp
+        """
+        
+        # stat
+        timestamp = os.stat(self.path).st_mtime
+
+        return time.strftime(config.DATETIME_FMT, time.gmtime(timestamp))
+
+class HTMLPage (Page) :
+    """
+        A simple .html page that's just passed through directly
+    """
+
+    @property
+    def content (self) :
+        """
+            Opens the .html file, reads and returns contents
+        """
+
+        return open(self.path, 'rb').read().decode(self.charset)
+
+class TemplatePage (Page) :
+    """
+        A template that's rendered using our template library
+    """
+    
+    @property
+    def content (self) :
+        """
+            Loads the .tmpl file, and renders it
+        """
+
+        return template.render_file(self.path,
+            request     = self.request,
+            page_tree   = self.fs.tree
+        )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/filesystem/page_tree.py	Sat Feb 07 16:33:27 2009 +0200
@@ -0,0 +1,218 @@
+"""
+    Implements the tree containing pages and their metadata
+"""
+
+from lib import tree_parse
+
+class PageTreeError (Exception) :
+    """
+        Error parsing/loading the page tree
+    """
+
+    pass
+
+class PageInfo (object) :
+    """
+        Contains metainformation about a page
+    """
+
+    def __init__ (self, parent, name, title, children=None) :
+        """
+            Initialize, children defaults to empty list
+        """
+
+        # store
+        self.parent = parent
+        self.name = name
+        self.title = title
+        self.children = children if children else []
+
+        # no url get
+        self._url = None
+    
+    def set_parent (self, parent) :
+        """
+            Set a parent where non was set before
+        """
+
+        assert self.parent is None
+
+        self.parent = parent
+
+    def add_child (self, child) :
+        """
+            Add a PageInfo child
+        """
+
+        self.children.append(child)
+    
+    def get_child (self, name) :
+        """
+            Look up a child by name, returning None if not found
+        """
+
+        return dict((c.name, c) for c in self.children).get(name)
+
+    def get_ancestry (self) :
+        """
+            Returns a list of this page's parents and the page itself, but not root
+        """
+        
+        # collect in reverse order
+        ancestry = []
+        
+        # starting from self
+        item = self
+        
+        # add all items, but not root
+        while item and item.parent :
+            ancestry.append(item)
+
+            item = item.parent
+
+        # reverse
+        ancestry.reverse()
+        
+        # done
+        return ancestry
+
+    @property
+    def url (self) :
+        """
+            Build this page's URL
+        """
+
+        # cached?
+        if self._url :
+            return self._url
+
+        segments = [item.name for item in self.get_ancestry()]
+        
+        # add empty segment if dir
+        if self.children :
+            segments.append('')
+        
+        # join
+        url = '/'.join(segments)
+        
+        # cache
+        self._url = url
+        
+        # done
+        return url
+
+class PageTree (object) :
+    """
+        The tree of pages, rooted at .root.
+
+        Use load_page_tree to initialize the global page_tree instance, and then use that
+    """
+
+    def __init__ (self, path) :
+        """
+            Loads the PageTree root from the given file
+        """
+        
+        # store
+        self.path = path
+        
+        # load
+        self._load(path)
+
+    def _load (self, path) :
+        """
+            Processes the lines in the given file
+        """
+        
+        # parse tree
+        tree = tree_parse.parse(path, ':')
+
+        if not tree :
+            raise PageTreeError("No root node found")
+
+        def _create_node (parent, item) :
+            """
+                Creates and returns a PageInfo from the given parent node and (line_number, line, children) tuple item
+            """
+
+            # unpack
+            line_number, line, children = item
+            
+            # parse line
+            url = title = None
+            
+            try :
+                url, title = line.split(':')
+
+            except :
+                raise PageTreeError("Invalid line: %s:%d: %r" % (path, line_number, line))
+
+            # remove whitespace
+            url = url.strip()
+            title = title.strip()
+            
+            # create PageInfo
+            node = PageInfo(parent, url, title)
+            
+            # set node children
+            node.children = [
+                _create_node(node, child_item) for child_item in children
+            ]
+
+            # return
+            return node
+        
+        # translate
+        self.root = _create_node(None, tree)
+            
+        # *evil cackle*
+        self.root.children.insert(0, self.root)
+        
+    def get_page (self, url) :
+        """
+            Lookup the given page URL, and return the matching PageInfo object, or None, if not found
+        """
+        
+        # start from root
+        node = self.root
+        
+        # traverse the object tree
+        for segment in url.split('/') :
+            if segment :
+                node = node.get_child(segment)
+
+            if not node :
+                return None
+        
+        # return
+        return node
+    
+    def get_siblings (self, url) :
+        """
+            Get the list of siblings for the given url, including the given page itself
+        """
+        
+        # look up the page itself
+        page = self.get_page(url)
+        
+        # specialcase root/unknown node
+        if page and page.parent :
+            return page.parent.children
+
+        else :
+            return self.root.children
+    
+    def dump (self) :
+        """
+            Returns a string representation of the tree
+        """
+        
+        def _print_node (indent, node) :
+            return '\n'.join('%s%s' % (' '*indent, line) for line in [
+                "%-15s : %s" % (node.name, node.title)
+            ] + [
+                _print_node(indent + 4, child) for child in node.children if child != node
+            ])
+
+        return _print_node(0, self.root)
+
--- a/lib/handler.py	Sat Feb 07 06:54:52 2009 +0200
+++ b/lib/handler.py	Sat Feb 07 16:33:27 2009 +0200
@@ -2,7 +2,7 @@
     The actual application behaviour, i.e. generating a Response from a Request :)
 """
 
-class Handler (object) :
+class RequestHandler (object) :
     """
         A handler handles a Request, returning a Response
     """
@@ -15,35 +15,10 @@
     def handle_request (self, request) :
         """
             Handle the request, returning a Response object
+
+            XXX: rename to __call__ kplzthx
         """
 
         return self.func(request, *self.args, **self.kwargs)
 
-# fs handler
-import http, page, menu, template
-
-def handle_request (request) :
-    """
-        Take the Request, and return a Response
-    """
-
-    # determine the page name
-    page_name = request.get_page_name()
-
-    # get the page handler
-    p = page.lookup(page_name)
 
-    # bind to request
-    p.bind_request(request)
-
-    # render the template
-    response_data = template.render("layout",
-        site_root_url   = request.get_script_dir(),
-        site_page_url   = request.get_page_prefix(),
-        page            = p,
-        menu            = menu.Menu(p),
-    )
-    
-    # return the response
-    return http.Response(response_data)
-
--- a/lib/map.py	Sat Feb 07 06:54:52 2009 +0200
+++ b/lib/map.py	Sat Feb 07 16:33:27 2009 +0200
@@ -13,14 +13,14 @@
     def __init__ (self, url) :
         super(MappingError, self).__init__("URL not found: %s" % (url, ), status='404 Not Found')
 
-class Mapper (object) :
+class Mapper (handler.RequestHandler) :
     """
-        Translates requests to handlers
+        Looks up the handler to use based on the URL
     """
 
-    def map_request (self, request) :
+    def handle_request (self, request) :
         """
-            Map the given request, returning a Handler
+            Map the given request
         """
 
         abstract
@@ -73,40 +73,24 @@
             Returns the appropriate handler
         """
 
+        # find handler to use
+        handler = None
+
         # just test each mapping in turn
         for mapping in self.mappings :
             handler = mapping.test(request)
 
             if handler :
-                return handler
-        
-        # fail, not found
-        raise MappingError(request.get_page_name())
+                break
         
-class FilesystemMapper (Mapper) :
-    """
-        Translates requests to handlers based on a filesystem directory containing various kinds of files
-    """
-
-    def __init__ (self, path) :
-        """
-            Create, path is where the pages are stored
-        """
+        if not handler :
+            # fail, not found
+            raise MappingError(request.get_page_name())
         
-        # store
-        self.path = path
-    
-    def map_request (self, request) :
-        """
-            Looks up the appropriate Page, and then returns a generic Handler
-        """
-
-        # XXX: harcoded
-        return handler.Handler(handler.handle_request)
+        # passthrough
+        return handler.handle_request(request)
 
 # "friendly" names
-fstree  = FilesystemMapper
-
 map     = SimpleMapping
 mapre   = RegexpMapping
 
--- a/lib/menu.py	Sat Feb 07 06:54:52 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-"""
-    Handling the list of available pages
-"""
-
-# for page_list
-from page_tree import page_tree
-
-class Menu (object) :
-    """
-        Contains info needed to render the menu
-    """
-
-    def __init__ (self, page) :
-        """
-            Gather the menu information for the given page
-        """
-
-        # the selected page
-        self.page = page_tree.get_page(page.url)
-
-        # the selected pagen's inheritance
-        self.ancestry = self.page.get_ancestry() if self.page else []
-        
-        # list of menu items == root children, since we always show the full menu...
-        self.items = page_tree.root.children
-    
--- a/lib/page.py	Sat Feb 07 06:54:52 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-
-"""
-    Handling page requests
-"""
-
-# for filesystem ops
-import os, os.path
-import time
-
-# for ResponseError
-import http
-
-# for TemplatePage
-import template
-
-from page_tree import page_tree
-import config
-
-# path to directory containing the page heirarcy
-PAGE_DIR = "pages"
-
-class PageError (http.ResponseError) :
-    """
-        Error looking up/handling a page
-    """
-
-    pass
-
-# XXX: should inherit from PageInfo
-class Page (object) :
-    """
-        This object represents the information about our attempt to render some specific page
-    """
-
-    def __init__ (self, url, path, basename, url_tail, charset='utf8') :
-        """
-            Initialize the page at the given location
-
-            @param url the URL leading to this page
-            @param path the filesystem path to this page's file
-            @param basename the filesystem name of this page's file, without the file extension
-            @param url_trail trailing URL for this page
-            @param charset file charset
-        """
-        
-        # store
-        self.url = url
-        self.path = path
-        self.basename = basename
-        self.url_tail = url_tail
-        self.charset = charset
-
-        # unbound
-        self.request = None
-
-        # sub-init
-        self._init()
-
-    def _init (self) :
-        """
-            Do initial data loading, etc
-        """
-        
-        pass
-
-    def bind_request (self, request) :
-        """
-            Bind this page-render to the given request
-        """
-
-        self.request = request
-
-    @property
-    def title (self) :
-        """
-            Return the page's title
-
-            Defaults to the retreiving the page title from page_list, or basename in Titlecase.
-        """
-        
-        # lookup in page_list
-        page_info = page_tree.get_page(self.url)
-        
-        # fallback to titlecase
-        if page_info :
-            title = page_info.title
-
-        else :
-            title = self.basename.title()
-
-        return title
-    
-    @property
-    def content (self) :
-        """
-            Return the page content as a string
-        """
-
-        abstract
-    
-    @property
-    def modified (self) :
-        """
-            Returns the page modification timestamp
-        """
-        
-        # stat
-        timestamp = os.stat(self.path).st_mtime
-
-        return time.strftime(config.DATETIME_FMT, time.gmtime(timestamp))
-
-class HTMLPage (Page) :
-    """
-        A simple .html page that's just passed through directly
-    """
-
-    @property
-    def content (self) :
-        """
-            Opens the .html file, reads and returns contents
-        """
-
-        return open(self.path, 'rb').read().decode(self.charset)
-
-class TemplatePage (Page) :
-    """
-        A template that's rendered using our template library
-    """
-    
-    @property
-    def content (self) :
-        """
-            Loads the .tmpl file, and renders it
-        """
-
-        return template.render_file(self.path,
-            request     = self.request,
-            page_tree   = page_tree
-        )
-
-# list of page handlers, by type
-TYPE_HANDLERS = [
-    ('html',                    HTMLPage        ),
-    (template.TEMPLATE_EXT,     TemplatePage    ),
-]
-
-def _lookup_handler (url, path, filename, basename, extension, tail) :
-    """
-        We found the file that we looked for, now get its handler
-    """
-
-    # find appropriate handler
-    for handler_ext, handler in TYPE_HANDLERS :
-        # match against file extension?
-        if handler_ext == extension :
-            # found handler, return instance
-            return handler(url, path, basename, tail)
-
-    # no handler found
-    raise PageError("No handler found for page %r of type %r" % (url, extension))
-
-def lookup (name) :
-    """
-        Look up and return a Page object for the given page, or raise an error
-    """
-
-    # inital path
-    path = PAGE_DIR
-    url_segments = []
-
-    # name segments
-    segments = name.split('/')
-
-    # iterate through the parts of the page segments
-    while True :
-        segment = None
-
-        # pop segment
-        if segments :
-            segment = segments.pop(0)
-
-            url_segments.append(segment)
-
-        # translate empty -> index
-        if not segment :
-            segment = 'index'
-
-        # look for it in the dir
-        for filename in os.listdir(path) :
-            # build full file path
-            file_path = os.path.join(path, filename)
-
-            # stat, recurse into subdirectory?
-            if os.path.isdir(file_path) and filename == segment :
-                # use new dir
-                path = file_path
-
-                # break for-loop to look at next segment
-                break
- 
-            # split into basename + extension
-            basename, extension = os.path.splitext(filename)
-
-            # ...remove that dot
-            extension = extension.lstrip('.')
-            
-            # match against requested page name?
-            if basename == segment :
-                # found the file we wanted
-                return _lookup_handler('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments))
-            
-            else :
-                # inspect next file in dir
-                continue
-
-        else :
-            # did not find any dir or file, break out of while loop
-            break
-
-    # did not find the filename we were looking for in os.listdir
-    raise PageError("Page not found: %s" % name, status='404 Not Found')
-
--- a/lib/page_tree.py	Sat Feb 07 06:54:52 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +0,0 @@
-"""
-    Implements the tree containing pages and their metadata
-"""
-
-import tree_parse
-
-# path to file containing the page metadata tree
-PAGE_TREE_FILE = "pages/list"
-
-class PageTreeError (Exception) :
-    """
-        Error parsing/loading the page tree
-    """
-
-    pass
-
-class PageInfo (object) :
-    """
-        Contains metainformation about a page
-    """
-
-    def __init__ (self, parent, name, title, children=None) :
-        """
-            Initialize, children defaults to empty list
-        """
-
-        # store
-        self.parent = parent
-        self.name = name
-        self.title = title
-        self.children = children if children else []
-
-        # no url get
-        self._url = None
-    
-    def set_parent (self, parent) :
-        """
-            Set a parent where non was set before
-        """
-
-        assert self.parent is None
-
-        self.parent = parent
-
-    def add_child (self, child) :
-        """
-            Add a PageInfo child
-        """
-
-        self.children.append(child)
-    
-    def get_child (self, name) :
-        """
-            Look up a child by name, returning None if not found
-        """
-
-        return dict((c.name, c) for c in self.children).get(name)
-
-    def get_ancestry (self) :
-        """
-            Returns a list of this page's parents and the page itself, but not root
-        """
-        
-        # collect in reverse order
-        ancestry = []
-        
-        # starting from self
-        item = self
-        
-        # add all items, but not root
-        while item and item.parent :
-            ancestry.append(item)
-
-            item = item.parent
-
-        # reverse
-        ancestry.reverse()
-        
-        # done
-        return ancestry
-
-    @property
-    def url (self) :
-        """
-            Build this page's URL
-        """
-
-        # cached?
-        if self._url :
-            return self._url
-
-        segments = [item.name for item in self.get_ancestry()]
-        
-        # add empty segment if dir
-        if self.children :
-            segments.append('')
-        
-        # join
-        url = '/'.join(segments)
-        
-        # cache
-        self._url = url
-        
-        # done
-        return url
-
-class PageTree (object) :
-    """
-        The tree of pages, rooted at .root.
-
-        Use load_page_tree to initialize the global page_tree instance, and then use that
-    """
-
-    def __init__ (self) :
-        """
-            Empty PageList, must call load_page_list to initialize, once
-        """
-
-    def _load (self, path) :
-        """
-            Processes the lines in the given file
-        """
-        
-        # parse tree
-        tree = tree_parse.parse(path, ':')
-
-        if not tree :
-            raise PageTreeError("No root node found")
-
-        def _create_node (parent, item) :
-            """
-                Creates and returns a PageInfo from the given parent node and (line_number, line, children) tuple item
-            """
-
-            # unpack
-            line_number, line, children = item
-            
-            # parse line
-            url = title = None
-            
-            try :
-                url, title = line.split(':')
-
-            except :
-                raise PageTreeError("Invalid line: %s:%d: %r" % (path, line_number, line))
-
-            # remove whitespace
-            url = url.strip()
-            title = title.strip()
-            
-            # create PageInfo
-            node = PageInfo(parent, url, title)
-            
-            # set node children
-            node.children = [
-                _create_node(node, child_item) for child_item in children
-            ]
-
-            # return
-            return node
-        
-        # translate
-        self.root = _create_node(None, tree)
-            
-        # *evil cackle*
-        self.root.children.insert(0, self.root)
-        
-    def get_page (self, url) :
-        """
-            Lookup the given page URL, and return the matching PageInfo object, or None, if not found
-        """
-        
-        # start from root
-        node = self.root
-        
-        # traverse the object tree
-        for segment in url.split('/') :
-            if segment :
-                node = node.get_child(segment)
-
-            if not node :
-                return None
-        
-        # return
-        return node
-    
-    def get_siblings (self, url) :
-        """
-            Get the list of siblings for the given url, including the given page itself
-        """
-        
-        # look up the page itself
-        page = self.get_page(url)
-        
-        # specialcase root/unknown node
-        if page and page.parent :
-            return page.parent.children
-
-        else :
-            return self.root.children
-    
-    def dump (self) :
-        """
-            Returns a string representation of the tree
-        """
-        
-        def _print_node (indent, node) :
-            return '\n'.join('%s%s' % (' '*indent, line) for line in [
-                "%-15s : %s" % (node.name, node.title)
-            ] + [
-                _print_node(indent + 4, child) for child in node.children if child != node
-            ])
-
-        return _print_node(0, self.root)
-
-# global singleton PageList instance
-page_tree = PageTree()
-
-def load () :
-    """
-        Load the global singleton PageInfo instance
-    """
-    
-    page_tree._load(PAGE_TREE_FILE)
-
--- a/lib/site.py	Sat Feb 07 06:54:52 2009 +0200
+++ b/lib/site.py	Sat Feb 07 16:33:27 2009 +0200
@@ -4,53 +4,82 @@
 
 import imp
 
-SITE_DIR = "sites"
+import handler
 
-class Site (object) :
+class Site (handler.RequestHandler) :
     """
         A site is a website and its configuration
+
+        XXX: need to somehow communicate the site name to our downstream handler
     """
 
-    def __init__ (self, name) :
+    def __init__ (self, name, handler) :
         """
             The given name must be like a valid hostname, e.g. 'www.qmsk.net'
         """
 
         # store
         self.name = name
+        self.handler = handler
 
-        # load the site
-        self._load()
-
-    def _load (self) :
+    def handle_request (self, request) :
         """
-            Loads this site, as a python module (i.e. dir with __init__.py)
+            Map the request through our handler...
         """
 
-        # first, we need to find it
-        file, pathname, description = imp.find_module(self.name, [SITE_DIR])
+        return self.handler.handle_request(request)
 
-        # then, we can load it
-        self.module = imp.load_module(self.name, file, pathname, description)
-        
-        # create our mapper
-        self.mapper = self.module.build_mapper()
+class SiteModule (Site) :
+    """
+        A site, represented as python module/package, with the following module attributes:
 
-    def get_mapper (self) :
+            handler     - the RequestHandler to use
+    """
+
+    def __init__ (self, name, module) :
         """
-            Return the Mapper for this site
+            Create the Site based on the given module
         """
 
-        return self.mapper
+        super(SiteModule, self).__init__(name,
+            module.handler
+        )
 
-def lookup (request) :
+class SiteModuleCollection (handler.RequestHandler) :
     """
-        Lookup and return a Site object for the given request
+        A collection of SiteModules, looking up the correct site to use based on the request hostname
     """
 
-    # request hostnmae
-    hostname = request.env.get('HTTP_POST')
+    def __init__ (self, path) :
+        """
+            Initialize to load site modules from the given path
+        """
 
-    # XXX: hardcoded for now
-    return Site("www.qmsk.net")
+        self.path = path
+        self.site_cache = dict()
 
+    def handle_request (self, request) :
+        """
+            Lookup and return a Site object for the given request
+        """
+
+        # request hostnmae
+        name = request.env.get('HTTP_HOST')
+        
+        # already loaded?
+        if name in self.site_cache :
+            site = self.site_cache[name]
+
+        else :
+            # first, we need to find it
+            file, pathname, description = imp.find_module(name, [self.path])
+
+            # then, we can load the module
+            module = imp.load_module(name, file, pathname, description)
+            
+            # then build+cache the SiteModule
+            site = self.site_cache[name] = SiteModule(name, module)
+
+        # then execute the site's request handler
+        return site.handle_request(request)
+
--- a/lib/wsgi.py	Sat Feb 07 06:54:52 2009 +0200
+++ b/lib/wsgi.py	Sat Feb 07 16:33:27 2009 +0200
@@ -9,63 +9,60 @@
 # for Request/Response
 import http
 
-# to lookup the Site
-from site import lookup as site_lookup
-
-# for the request -> response bit :)
-import handler
-
-def request_handler (env, start_response) :
+class Application (object) :
     """
-        The actual request handling code
+        Our WSGI application, implements the wsgi __call__ interface
     """
 
-    # build Request object
-    request = http.Request(env)
-
-    # lookup site
-    site = site_lookup(request)
-
-    # mapper...
-    mapper = site.get_mapper()
+    def __init__ (self, handler) :
+        """
+            Initialize to use the given handler for requests
+        """
 
-    # lookup handler
-    handler = mapper.map_request(request)
-    
-    try :
-        # request -> response
-        response = handler.handle_request(request)
-
-    except http.ResponseError, err :
-        # just use the generated response
-        response = err.get_response()
-
-    # send response
-    assert response, "No response"
+        self.handler = handler
     
-    # send response status/headers
-    start_response(response.get_status(), response.get_headers())
-
-    # send respones data
-    yield response.get_data()
-
-def app (env, start_response) :
-    """
-        Wraps request_handler to trap errors
-    """
+    def handle_request (self, env, start_response) :
+        """
+            The actual request handling code
+        """
 
-    try :
-        # passthrough request_handler
-        for chunk in request_handler(env, start_response) :
-            yield chunk
+        # build Request object
+        request = http.Request(env)
 
-    except :
-        # execption info
-        info = sys.exc_info()
+        try :
+            # request -> response using our handler
+            response = self.handler.handle_request(request)
 
-        # try and send 500 ISE to browser, if no headers yet...
-        start_response("500 Internal Server Error", [('Content-type', "text/plain; charset=utf8")], info)
+        except http.ResponseError, err :
+            # just use the generated response
+            response = err.get_response()
 
-        # send traceback
-        yield traceback.format_exc()
+        # send response
+        assert response, "No response"
+        
+        # send response status/headers
+        start_response(response.get_status(), response.get_headers())
 
+        # send respones data
+        yield response.get_data()
+
+    def __call__ (self, env, start_response) :
+        """
+            Wraps handle_request to trap errors
+        """
+
+        try :
+            # passthrough request_handler
+            for chunk in self.handle_request(env, start_response) :
+                yield chunk
+
+        except :
+            # execption info
+            info = sys.exc_info()
+
+            # try and send 500 ISE to browser, if no headers yet...
+            start_response("500 Internal Server Error", [('Content-type', "text/plain; charset=utf8")], info)
+
+            # send traceback
+            yield traceback.format_exc()
+
--- a/sites/www.qmsk.net/__init__.py	Sat Feb 07 06:54:52 2009 +0200
+++ b/sites/www.qmsk.net/__init__.py	Sat Feb 07 16:33:27 2009 +0200
@@ -2,7 +2,8 @@
     The www.qmsk.net site is just a simple site with a filesystem-based URL mapping
 """
 
-from lib.map import fstree
+from lib.filesystem.map import FilesystemMapper as _fstree
 
-build_mapper = lambda: fstree("site/www.qmsk.net")
+# global mapper attribute
+handler = _fstree("pages")