# HG changeset patch # User Tero Marttila # Date 1233963210 -7200 # Node ID fa216534ae453567233e4b14c4feac384750bc49 # Parent d83b10c210e3347466ccd4ad333daef8a81c20fc funky PageTree stuff diff -r d83b10c210e3 -r fa216534ae45 index.cgi --- 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() diff -r d83b10c210e3 -r fa216534ae45 lib/menu.py --- 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) diff -r d83b10c210e3 -r fa216534ae45 lib/page.py --- 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 diff -r d83b10c210e3 -r fa216534ae45 lib/template.py --- 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) : """ diff -r d83b10c210e3 -r fa216534ae45 pages/debug.tmpl --- 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 @@ + +
+${page_tree.dump() | h}
+
List of request env variables diff -r d83b10c210e3 -r fa216534ae45 pages/list --- 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 diff -r d83b10c210e3 -r fa216534ae45 pages/projects/foo.html --- /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 diff -r d83b10c210e3 -r fa216534ae45 pages/projects/index.html --- 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: + +Foo + diff -r d83b10c210e3 -r fa216534ae45 static/style.css --- 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 */ diff -r d83b10c210e3 -r fa216534ae45 templates/layout.tmpl --- 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 @@