better error handling of import errors in CGI, and split PageTree out from page.py into page_tree.py and tree_parse.py
--- a/index.cgi Sat Feb 07 02:24:59 2009 +0200
+++ b/index.cgi Sat Feb 07 02:46:58 2009 +0200
@@ -8,8 +8,18 @@
# CGI handler for WSGI
import wsgiref.handlers
-# our WSGI app
-from lib import wsgi
+def cgi_error () :
+ """
+ Dumps out a raw traceback of the current exception to stdout, intended for use from 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)
def cgi_main () :
"""
@@ -17,27 +27,22 @@
"""
try :
- from lib import page
+ from lib import page_tree
# load page list
- page.load_page_tree()
+ page_tree.load()
+
+ # our WSGI app
+ from lib import wsgi
+
+ # create handler
+ cgi_handler = wsgiref.handlers.CGIHandler()
+
+ # run once
+ cgi_handler.run(wsgi.app)
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
+ cgi_error()
- # create handler
- cgi_handler = wsgiref.handlers.CGIHandler()
-
- # run once
- cgi_handler.run(wsgi.app)
-
if __name__ == '__main__' :
cgi_main()
--- a/lib/menu.py Sat Feb 07 02:24:59 2009 +0200
+++ b/lib/menu.py Sat Feb 07 02:46:58 2009 +0200
@@ -3,7 +3,7 @@
"""
# for page_list
-from page import page_tree as _page_tree
+from page_tree import page_tree
class Menu (object) :
"""
@@ -16,11 +16,11 @@
"""
# the selected page
- self.page = _page_tree.get_page(page.url)
+ 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 == page siblings
- self.items = _page_tree.root.children
+ # 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 02:24:59 2009 +0200
+++ b/lib/page.py Sat Feb 07 02:46:58 2009 +0200
@@ -12,12 +12,11 @@
# for TemplatePage
import template
+from page_tree import page_tree
+
# path to directory containing the page heirarcy
PAGE_DIR = "pages"
-# path to directory containing the list of visible pages
-PAGE_LIST_FILE = os.path.join(PAGE_DIR, "list")
-
class PageError (http.ResponseError) :
"""
Error looking up/handling a page
@@ -25,277 +24,6 @@
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 list of pages
- """
-
- def __init__ (self) :
- """
- Empty PageList, must call load_page_list to initialize, once
- """
-
- 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') :
- 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
-
- # parse line
- url, title = line.split(':')
-
- # remove whitespace
- url = url.strip()
- title = title.strip()
-
- # create PageInfo item without parent
- item = PageInfo(None, url, title)
-
- # 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
-
- # tee hee hee
- self.root.add_child(self.root)
-
- # 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
-
- # 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 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_page_tree () :
- """
- Load the global singleton PageInfo instance
- """
-
- page_tree._load()
-
# XXX: should inherit from PageInfo
class Page (object) :
"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/page_tree.py Sat Feb 07 02:46:58 2009 +0200
@@ -0,0 +1,210 @@
+"""
+ 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 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, ':')
+
+ 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 = line.split(':')
+
+ # 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)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/tree_parse.py Sat Feb 07 02:46:58 2009 +0200
@@ -0,0 +1,123 @@
+
+"""
+ Parsing trees of node stored using a python-like syntax.
+
+ A file consists of a number of lines, and each line consists of indenting whitespace and data. Each line has a parent
+"""
+
+def _read_lines (path, stop_tokens='') :
+ """
+ Reads lines from the given path, ignoring empty lines, and yielding (line_number, indent, line) tuples, where
+ line_number is the line number, indent counts the amount of leading whitespace, and line is the actual line
+ data with whitespace stripped.
+
+ Stop tokens is a list of chars to stop counting indentation on - if such a line begins with such a char, its
+ indentation is taken as zero.
+ """
+
+ for line_number, line in enumerate(open(path, 'rb')) :
+ 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 in stop_tokens :
+ # consider line as not having any indentation at all
+ indent = 0
+ break
+
+ else :
+ break
+
+ # strip whitespace
+ line = line.strip()
+
+ # ignore empty lines
+ if not line :
+ continue
+
+ # yield
+ yield line_number + 1, indent, line
+
+def parse (path, stop_tokens='') :
+ """
+ Reads and parses the file at the given path, returning a list of (line_number, line, children) tuples.
+ """
+
+ # stack of (indent, PageInfo) items
+ stack = []
+
+ # the root item
+ root = None
+
+ # the previous item processed, None for first one
+ prev = None
+
+ # read lines
+ for line_number, indent, line in _read_lines(path, stop_tokens) :
+ # create item
+ item = (line_number, line, [])
+
+ # are we the first item?
+ if not prev :
+ # root node does not have a parent
+ parent = None
+
+ # set root
+ root = item
+
+ # initialize stack
+ stack.append((0, 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 :
+ parent[2].append(item)
+
+ # update prev
+ prev = item
+
+ # return the root
+ return root
+