funky PageTree stuff
authorTero Marttila <terom@fixme.fi>
Sat, 07 Feb 2009 01:33:30 +0200
changeset 11 fa216534ae45
parent 10 d83b10c210e3
child 12 2abc5ace0b15
funky PageTree stuff
index.cgi
lib/menu.py
lib/page.py
lib/template.py
pages/debug.tmpl
pages/list
pages/projects/foo.html
pages/projects/index.html
static/style.css
templates/layout.tmpl
--- a/index.cgi	Fri Feb 06 23:55:23 2009 +0200
+++ b/index.cgi	Sat Feb 07 01:33:30 2009 +0200
@@ -15,6 +15,23 @@
     """
         Run in CGI mode
     """
+
+    try :
+        from lib import page
+
+        # load page list
+        page.load_page_tree()
+
+    except :
+        import traceback, sys
+
+        print 'Status: 500 Internal Server Error\r'
+        print 'Content-type: text/plain\r'
+        print '\r'
+
+        traceback.print_exc(100, sys.stdout)
+
+        return
     
     # create handler
     cgi_handler = wsgiref.handlers.CGIHandler()
--- a/lib/menu.py	Fri Feb 06 23:55:23 2009 +0200
+++ b/lib/menu.py	Sat Feb 07 01:33:30 2009 +0200
@@ -3,7 +3,7 @@
 """
 
 # for page_list
-from page import page_list as _page_list
+from page import page_tree as _page_tree
 
 class Menu (object) :
     """
@@ -16,8 +16,8 @@
         """
 
         # the selected page
-        self.page = page
+        self.page = _page_tree.get_page(page.url)
         
-        # list of siblings
-        self.siblings = _page_list.get_siblings(page)
+        # list of menu items == page siblings
+        self.items = _page_tree.get_siblings(page.url)
     
--- a/lib/page.py	Fri Feb 06 23:55:23 2009 +0200
+++ b/lib/page.py	Sat Feb 07 01:33:30 2009 +0200
@@ -25,31 +25,132 @@
 
     pass
 
-class PageList (object) :
+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)
+
+    @property
+    def url (self) :
+        """
+            Build this page's URL
+        """
+
+        # cached?
+        if self._url :
+            return self._url
+        
+        # collect URL segments in reverse order
+        segments = []
+
+        # add empty segment if dir
+        if self.children :
+            segments.append('')
+        
+        # iterate over ancestry
+        item = self
+        
+        # add all parent names, but not root's
+        while item and item.parent :
+            segments.append(item.name)
+
+            item = item.parent
+
+        # reverse segments
+        segments.reverse()
+
+        # join
+        url = '/'.join(segments)
+        
+        # cache
+        self._url = url
+        
+        # done
+        return url
+
+class PageTree (object) :
     """
         The list of pages
     """
 
     def __init__ (self) :
         """
-            Loads the page list from the list file
+            Empty PageList, must call load_page_list to initialize, once
         """
 
-        # initialize list of pages
-        self.pages = []
-
-        # load from file
-        self._load(PAGE_LIST_FILE)
-
-    def _load (self, path) :
+    def _load (self, path=PAGE_LIST_FILE) :
         """
             Processes the lines in the given file
         """
+        
+        # collect the page list
+        pages = []
+        
+        # stack of (indent, PageInfo) items
+        stack = []
+
+        # the previous item processed, None for first one
+        prev = None
 
         for line in open(path, 'rb') :
-            # ignore whitespace
+            indent = 0
+
+            # count indent
+            for char in line :
+                # tabs break things
+                assert char != '\t'
+                
+                # increment up to first non-space char
+                if char == ' ' :
+                    indent += 1
+
+                elif char == ':' :
+                    indent = 0
+                    break
+
+                else :
+                    break
+            
+            # strip whitespace
             line = line.strip()
-            
+
             # ignore empty lines
             if not line :
                 continue
@@ -57,52 +158,135 @@
             # parse line
             url, title = line.split(':')
 
-            # add
-            self._add_item(url.strip(), title.strip())
-    
-    def _add_item (self, url, title) :
-        """
-            Add item to pages list
-        """
-
-        # remove index from URL
-        if url.endswith('/index') or url == 'index' :
-            url = url[:-len('/index')]
-
-        self.pages.append((url, title))
-    
-    def get_title (self, page) :
-        """
-            Gets the title for the given page, or None if not found
-        """
+            # remove whitespace
+            url = url.strip()
+            title = title.strip()
+            
+            # create PageInfo item without parent
+            item = PageInfo(None, url, title)
 
-        return dict(self.pages).get(page)
-
-    def get_siblings (self, page) :
-        """
-            Gets the (url, title) tuple list of siblings (including the given page itself) for the given page
-        """
-    
-        siblings = []
+            # are we the first item?
+            if not prev :
+                assert url == '', "Page list must begin with root item"
+                
+                # root node does not have a parent
+                parent = None
+                
+                # set root
+                self.root = item
 
-        # parent url
-        parent = os.path.split(page.url)[0]
+                # tee hee hee
+                self.root.add_child(self.root)
 
-        # how many segments in the page name
-        segment_count = len(page.url.split('/'))
+                # initialize stack
+                stack.append((0, self.root))
+                
+            else :
+                # peek stack
+                stack_indent, stack_parent = stack[-1]
+
+                # new indent level?
+                if indent > stack_indent :
+                    # set parent to previous item, and push new indent level + parent to stack
+                    parent = prev
+
+                    # push new indent level + its parent
+                    stack.append((indent, parent))
+
+                # same indent level as previous
+                elif indent == stack_indent :
+                    # parent is the one of the current stack level, stack doesn't change
+                    parent = stack_parent
+                
+                # unravel stack
+                elif indent < stack_indent :
+                    while True :
+                        # remove current stack level
+                        stack.pop(-1)
+
+                        # peek next level
+                        stack_indent, stack_parent = stack[-1]
+                        
+                        # found the level to return to?
+                        if stack_indent == indent :
+                            # restore prev
+                            parent = stack_parent
+
+                            break
+
+                        elif stack_indent < indent :
+                            assert False, "Bad un-indent"
+            
+            # add to parent?
+            if parent :
+                item.set_parent(parent)
+                parent.add_child(item)
+
+            # update prev
+            prev = item
         
-        # go through all pages
-        for url, title in self.pages :
-            # it's a sibling if the parent is the same, and the number of segments it the same
-            if url.startswith(parent) and len(url.split('/')) == segment_count :
-                siblings.append((url, title))
+        # get root
+        assert hasattr(self, 'root'), "No root item found!"
+        
+    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 siblings
+        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
 
-# global singleton instance
-page_list = PageList()
+        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
+            ])
 
+        return _print_node(0, self.root)
+
+# global singleton PageList instance
+page_tree = PageTree()
+
+def load_page_tree () :
+    """
+        Load the global singleton PageInfo instance
+    """
+    
+    page_tree._load()
+
+# XXX: should inherit from PageInfo
 class Page (object) :
     """
         This object represents the information about our attempt to render some specific page
@@ -153,10 +337,13 @@
         """
         
         # lookup in page_list
-        title = page_list.get_title(self.url)
+        page_info = page_tree.get_page(self.url)
         
         # fallback to titlecase
-        if not title :
+        if page_info :
+            title = page_info.title
+
+        else :
             title = self.basename.title()
 
         return title
@@ -195,6 +382,7 @@
 
         return template.render_file(self.path,
             request     = self.request,
+            page_tree   = page_tree
         )
 
 # list of page handlers, by type
--- a/lib/template.py	Fri Feb 06 23:55:23 2009 +0200
+++ b/lib/template.py	Sat Feb 07 01:33:30 2009 +0200
@@ -60,9 +60,15 @@
 
     try :
         return tpl.render(**params)
+    
+    # a template may render other templates
+    except TemplateError :
+        raise
 
     except :
-        raise TemplateError("Template render failed", status='500 Internal Server Error', details=exceptions.text_error_template().render())
+        details = exceptions.text_error_template().render()
+
+        raise TemplateError("Template render failed", status='500 Internal Server Error', details=details)
 
 def render (name, **params) :
     """
--- a/pages/debug.tmpl	Fri Feb 06 23:55:23 2009 +0200
+++ b/pages/debug.tmpl	Sat Feb 07 01:33:30 2009 +0200
@@ -1,3 +1,7 @@
+
+<pre>
+${page_tree.dump() | h}
+</pre>
 
 <dl>
     <lh>List of request env variables</lh>
--- a/pages/list	Fri Feb 06 23:55:23 2009 +0200
+++ b/pages/list	Sat Feb 07 01:33:30 2009 +0200
@@ -1,6 +1,7 @@
 
-index       : Index
-foo         : Foo
-projects    : Projects list
-about       : About page
+            : Index
+foo             : Foo
+projects        : Projects list
+    foo             : Project Foo
+about           : About page
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pages/projects/foo.html	Sat Feb 07 01:33:30 2009 +0200
@@ -0,0 +1,1 @@
+The foo project
--- a/pages/projects/index.html	Fri Feb 06 23:55:23 2009 +0200
+++ b/pages/projects/index.html	Sat Feb 07 01:33:30 2009 +0200
@@ -1,1 +1,4 @@
-Projects list
+Projects list:
+
+<a href="foo">Foo</a>
+
--- a/static/style.css	Fri Feb 06 23:55:23 2009 +0200
+++ b/static/style.css	Sat Feb 07 01:33:30 2009 +0200
@@ -43,6 +43,10 @@
     border-bottom: 1px dashed #a5a5a5;
 }
 
+div#header a:hover {
+    text-decoration: none;
+}
+
 /*
  * Main navigation menu
  */
--- a/templates/layout.tmpl	Fri Feb 06 23:55:23 2009 +0200
+++ b/templates/layout.tmpl	Sat Feb 07 01:33:30 2009 +0200
@@ -7,12 +7,15 @@
     </head>
     <body>
             <div id="header">
-                QMSK.NET
+                <a href="${site_page_url}/">QMSK.NET</a>
             </div>
  
             <ul id="nav">
-            % for (url, title) in menu.siblings :
-                <li><a href="${site_page_url}/${url}"${' class="selected-page"' if url == page.url else ''}>${title}</a></li>
+            % if menu.page.parent and menu.page.parent.parent :
+                <li><a href="${site_page_url}/${menu.page.parent.url}">&laquo;</a></li>
+            % endif
+            % for pi in menu.items :
+                <li><a href="${site_page_url}/${pi.url}"${' class="selected-page"' if pi.url == page.url else ''}>${pi.title} ${'&raquo;' if pi.children and pi.parent else ''}</a></li>
             % endfor
             </ul>