--- 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}">«</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} ${'»' if pi.children and pi.parent else ''}</a></li>
% endfor
</ul>