better error handling of import errors in CGI, and split PageTree out from page.py into page_tree.py and tree_parse.py
authorTero Marttila <terom@fixme.fi>
Sat, 07 Feb 2009 02:46:58 +0200
changeset 16 4a40718c7b4b
parent 15 e2fe2baa7910
child 17 b538e1f7011c
better error handling of import errors in CGI, and split PageTree out from page.py into page_tree.py and tree_parse.py
index.cgi
lib/menu.py
lib/page.py
lib/page_tree.py
lib/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
+