# HG changeset patch # User Tero Marttila # Date 1233967618 -7200 # Node ID 4a40718c7b4baab0a4e7bb863eb9dc03147943f2 # Parent e2fe2baa79104e8decbafe863be2e046f2e35ad0 better error handling of import errors in CGI, and split PageTree out from page.py into page_tree.py and tree_parse.py diff -r e2fe2baa7910 -r 4a40718c7b4b index.cgi --- 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() diff -r e2fe2baa7910 -r 4a40718c7b4b lib/menu.py --- 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 diff -r e2fe2baa7910 -r 4a40718c7b4b lib/page.py --- 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) : """ diff -r e2fe2baa7910 -r 4a40718c7b4b lib/page_tree.py --- /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) + diff -r e2fe2baa7910 -r 4a40718c7b4b lib/tree_parse.py --- /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 +