terom@5: terom@7: """ terom@7: Handling page requests terom@7: """ terom@7: terom@8: # for filesystem ops terom@8: import os, os.path terom@7: terom@8: # for ResponseError terom@8: import http terom@8: terom@8: # for TemplatePage terom@8: import template terom@8: terom@8: # path to directory containing the page heirarcy terom@8: PAGE_DIR = "pages" terom@8: terom@9: # path to directory containing the list of visible pages terom@9: PAGE_LIST_FILE = os.path.join(PAGE_DIR, "list") terom@9: terom@8: class PageError (http.ResponseError) : terom@8: """ terom@8: Error looking up/handling a page terom@8: """ terom@8: terom@8: pass terom@8: terom@11: class PageInfo (object) : terom@11: """ terom@11: Contains metainformation about a page terom@11: """ terom@11: terom@11: def __init__ (self, parent, name, title, children=None) : terom@11: """ terom@11: Initialize, children defaults to empty list terom@11: """ terom@11: terom@11: # store terom@11: self.parent = parent terom@11: self.name = name terom@11: self.title = title terom@11: self.children = children if children else [] terom@11: terom@11: # no url get terom@11: self._url = None terom@11: terom@11: def set_parent (self, parent) : terom@11: """ terom@11: Set a parent where non was set before terom@11: """ terom@11: terom@11: assert self.parent is None terom@11: terom@11: self.parent = parent terom@11: terom@11: def add_child (self, child) : terom@11: """ terom@11: Add a PageInfo child terom@11: """ terom@11: terom@11: self.children.append(child) terom@11: terom@11: def get_child (self, name) : terom@11: """ terom@11: Look up a child by name, returning None if not found terom@11: """ terom@11: terom@11: return dict((c.name, c) for c in self.children).get(name) terom@11: terom@11: @property terom@11: def url (self) : terom@11: """ terom@11: Build this page's URL terom@11: """ terom@11: terom@11: # cached? terom@11: if self._url : terom@11: return self._url terom@11: terom@11: # collect URL segments in reverse order terom@11: segments = [] terom@11: terom@11: # add empty segment if dir terom@11: if self.children : terom@11: segments.append('') terom@11: terom@11: # iterate over ancestry terom@11: item = self terom@11: terom@11: # add all parent names, but not root's terom@11: while item and item.parent : terom@11: segments.append(item.name) terom@11: terom@11: item = item.parent terom@11: terom@11: # reverse segments terom@11: segments.reverse() terom@11: terom@11: # join terom@11: url = '/'.join(segments) terom@11: terom@11: # cache terom@11: self._url = url terom@11: terom@11: # done terom@11: return url terom@11: terom@11: class PageTree (object) : terom@9: """ terom@9: The list of pages terom@9: """ terom@9: terom@9: def __init__ (self) : terom@9: """ terom@11: Empty PageList, must call load_page_list to initialize, once terom@9: """ terom@9: terom@11: def _load (self, path=PAGE_LIST_FILE) : terom@9: """ terom@9: Processes the lines in the given file terom@9: """ terom@11: terom@11: # collect the page list terom@11: pages = [] terom@11: terom@11: # stack of (indent, PageInfo) items terom@11: stack = [] terom@11: terom@11: # the previous item processed, None for first one terom@11: prev = None terom@9: terom@9: for line in open(path, 'rb') : terom@11: indent = 0 terom@11: terom@11: # count indent terom@11: for char in line : terom@11: # tabs break things terom@11: assert char != '\t' terom@11: terom@11: # increment up to first non-space char terom@11: if char == ' ' : terom@11: indent += 1 terom@11: terom@11: elif char == ':' : terom@11: indent = 0 terom@11: break terom@11: terom@11: else : terom@11: break terom@11: terom@11: # strip whitespace terom@9: line = line.strip() terom@11: terom@9: # ignore empty lines terom@9: if not line : terom@9: continue terom@9: terom@9: # parse line terom@9: url, title = line.split(':') terom@9: terom@11: # remove whitespace terom@11: url = url.strip() terom@11: title = title.strip() terom@11: terom@11: # create PageInfo item without parent terom@11: item = PageInfo(None, url, title) terom@9: terom@11: # are we the first item? terom@11: if not prev : terom@11: assert url == '', "Page list must begin with root item" terom@11: terom@11: # root node does not have a parent terom@11: parent = None terom@11: terom@11: # set root terom@11: self.root = item terom@9: terom@11: # tee hee hee terom@11: self.root.add_child(self.root) terom@9: terom@11: # initialize stack terom@11: stack.append((0, self.root)) terom@11: terom@11: else : terom@11: # peek stack terom@11: stack_indent, stack_parent = stack[-1] terom@11: terom@11: # new indent level? terom@11: if indent > stack_indent : terom@11: # set parent to previous item, and push new indent level + parent to stack terom@11: parent = prev terom@11: terom@11: # push new indent level + its parent terom@11: stack.append((indent, parent)) terom@11: terom@11: # same indent level as previous terom@11: elif indent == stack_indent : terom@11: # parent is the one of the current stack level, stack doesn't change terom@11: parent = stack_parent terom@11: terom@11: # unravel stack terom@11: elif indent < stack_indent : terom@11: while True : terom@11: # remove current stack level terom@11: stack.pop(-1) terom@11: terom@11: # peek next level terom@11: stack_indent, stack_parent = stack[-1] terom@11: terom@11: # found the level to return to? terom@11: if stack_indent == indent : terom@11: # restore prev terom@11: parent = stack_parent terom@11: terom@11: break terom@11: terom@11: elif stack_indent < indent : terom@11: assert False, "Bad un-indent" terom@11: terom@11: # add to parent? terom@11: if parent : terom@11: item.set_parent(parent) terom@11: parent.add_child(item) terom@11: terom@11: # update prev terom@11: prev = item terom@9: terom@11: # get root terom@11: assert hasattr(self, 'root'), "No root item found!" terom@11: terom@11: def get_page (self, url) : terom@11: """ terom@11: Lookup the given page URL, and return the matching PageInfo object, or None, if not found terom@11: """ terom@11: terom@11: # start from root terom@11: node = self.root terom@11: terom@11: # traverse the object tree terom@11: for segment in url.split('/') : terom@11: if segment : terom@11: node = node.get_child(segment) terom@11: terom@11: if not node : terom@11: return None terom@9: terom@9: # return terom@11: return node terom@11: terom@11: def get_siblings (self, url) : terom@11: """ terom@11: Get the list of siblings for the given url, including the given page itself terom@11: """ terom@11: terom@11: # look up the page itself terom@11: page = self.get_page(url) terom@11: terom@11: # specialcase root/unknown node terom@11: if page and page.parent : terom@11: return page.parent.children terom@9: terom@11: else : terom@11: return self.root.children terom@11: terom@11: def dump (self) : terom@11: """ terom@11: Returns a string representation of the tree terom@11: """ terom@11: terom@11: def _print_node (indent, node) : terom@11: return '\n'.join('%s%s' % (' '*indent, line) for line in [ terom@11: "%-15s : %s" % (node.name, node.title) terom@11: ] + [ terom@11: _print_node(indent + 4, child) for child in node.children terom@11: ]) terom@9: terom@11: return _print_node(0, self.root) terom@11: terom@11: # global singleton PageList instance terom@11: page_tree = PageTree() terom@11: terom@11: def load_page_tree () : terom@11: """ terom@11: Load the global singleton PageInfo instance terom@11: """ terom@11: terom@11: page_tree._load() terom@11: terom@11: # XXX: should inherit from PageInfo terom@8: class Page (object) : terom@8: """ terom@8: This object represents the information about our attempt to render some specific page terom@8: """ terom@8: terom@8: def __init__ (self, url, path, basename, url_tail) : terom@8: """ terom@8: Initialize the page at the given location terom@8: terom@8: @param url the URL leading to this page terom@8: @param path the filesystem path to this page's file terom@8: @param basename the filesystem name of this page's file, without the file extension terom@8: @param url_trail trailing URL for this page terom@8: """ terom@8: terom@8: # store terom@8: self.url = url terom@8: self.path = path terom@8: self.basename = basename terom@8: self.url_tail = url_tail terom@8: terom@10: # unbound terom@10: self.request = None terom@10: terom@8: # sub-init terom@8: self._init() terom@8: terom@8: def _init (self) : terom@8: """ terom@8: Do initial data loading, etc terom@8: """ terom@8: terom@8: pass terom@8: terom@10: def bind_request (self, request) : terom@10: """ terom@10: Bind this page-render to the given request terom@10: """ terom@10: terom@10: self.request = request terom@10: terom@9: @property terom@9: def title (self) : terom@8: """ terom@8: Return the page's title terom@8: terom@9: Defaults to the retreiving the page title from page_list, or basename in Titlecase. terom@8: """ terom@9: terom@9: # lookup in page_list terom@11: page_info = page_tree.get_page(self.url) terom@9: terom@9: # fallback to titlecase terom@11: if page_info : terom@11: title = page_info.title terom@11: terom@11: else : terom@9: title = self.basename.title() terom@8: terom@9: return title terom@9: terom@9: @property terom@9: def content (self) : terom@8: """ terom@8: Return the page content as a string terom@8: """ terom@8: terom@8: abstract terom@8: terom@8: class HTMLPage (Page) : terom@8: """ terom@8: A simple .html page that's just passed through directly terom@8: """ terom@8: terom@9: @property terom@9: def content (self) : terom@8: """ terom@8: Opens the .html file, reads and returns contents terom@8: """ terom@8: terom@8: return open(self.path, 'rb').read() terom@8: terom@8: class TemplatePage (Page) : terom@8: """ terom@8: A template that's rendered using our template library terom@8: """ terom@9: terom@9: @property terom@9: def content (self) : terom@8: """ terom@8: Loads the .tmpl file, and renders it terom@8: """ terom@8: terom@10: return template.render_file(self.path, terom@10: request = self.request, terom@11: page_tree = page_tree terom@10: ) terom@7: terom@7: # list of page handlers, by type terom@8: TYPE_HANDLERS = [ terom@8: ('html', HTMLPage ), terom@8: (template.TEMPLATE_EXT, TemplatePage ), terom@7: ] terom@7: terom@8: def _lookup_handler (url, path, filename, basename, extension, tail) : terom@5: """ terom@8: We found the file that we looked for, now get its handler terom@5: """ terom@5: terom@8: # find appropriate handler terom@8: for handler_ext, handler in TYPE_HANDLERS : terom@8: # match against file extension? terom@8: if handler_ext == extension : terom@8: # found handler, return instance terom@8: return handler(url, path, basename, tail) terom@5: terom@8: # no handler found terom@8: raise PageError("No handler found for page %r of type %r" % (url, extension)) terom@8: terom@8: def lookup (name) : terom@8: """ terom@8: Look up and return a Page object for the given page, or raise an error terom@8: """ terom@8: terom@8: # inital path terom@8: path = PAGE_DIR terom@8: url_segments = [] terom@8: terom@8: # name segments terom@8: segments = name.split('/') terom@8: terom@8: # iterate through the parts of the page segments terom@10: while True : terom@10: segment = None terom@10: terom@8: # pop segment terom@10: if segments : terom@10: segment = segments.pop(0) terom@8: terom@10: url_segments.append(segment) terom@8: terom@8: # translate empty -> index terom@8: if not segment : terom@8: segment = 'index' terom@10: terom@8: # look for it in the dir terom@8: for filename in os.listdir(path) : terom@8: # build full file path terom@8: file_path = os.path.join(path, filename) terom@8: terom@8: # stat, recurse into subdirectory? terom@8: if os.path.isdir(file_path) and filename == segment : terom@8: # use new dir terom@8: path = file_path terom@8: terom@8: # break for-loop to look at next segment terom@8: break terom@8: terom@8: # split into basename + extension terom@8: basename, extension = os.path.splitext(filename) terom@8: terom@8: # ...remove that dot terom@8: extension = extension.lstrip('.') terom@8: terom@8: # match against requested page name? terom@8: if basename == segment : terom@8: # found the file we wanted terom@8: return _lookup_handler('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments)) terom@8: terom@8: else : terom@8: # inspect next file in dir terom@8: continue terom@8: terom@10: else : terom@10: # did not find any dir or file, break out of while loop terom@10: break terom@10: terom@8: # did not find the filename we were looking for in os.listdir terom@8: raise PageError("Page not found: %s" % name, status='404 Not Found') terom@8: