# HG changeset patch # User Tero Marttila # Date 1234019406 -7200 # Node ID 09196d5b2a39bbb6a6ca03767cecf89e99da75d9 # Parent 19ea04f4b0cdc743c46dad3dab4108a8ed39687e move lib.filesystem code to sites/www.qmsk.net, part one diff -r 19ea04f4b0cd -r 09196d5b2a39 lib/filesystem/map.py --- a/lib/filesystem/map.py Sat Feb 07 17:07:06 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ - -import os, os.path - -from lib import http, template, map - -import page, page_tree - -class FilesystemMapper (map.Mapper) : - """ - Translates requests to handlers based on a filesystem directory containing various kinds of files - """ - - # list of page handlers, by type - PAGE_TYPES = [ - ('html', page.HTMLPage ), - (template.TEMPLATE_EXT, page.TemplatePage ), - ] - - def __init__ (self, path, template) : - """ - Create, path is where the pages are stored. The list of pages is loaded from $path/list - """ - - # store - self.path = path - self.template = template - - # load the page tree - self.tree = page_tree.PageTree(path + '/list') - - def _lookup_page_type (self, url, path, filename, basename, extension, tail) : - """ - We found the file that we looked for, now get the correct type - """ - - # find appropriate handler - for handler_ext, type in self.PAGE_TYPES : - # match against file extension? - if handler_ext == extension : - # found handler, return instance - return type(self, url, path, basename, tail) - - # no handler found - raise PageError("No handler found for page %r of type %r" % (url, extension)) - - def _lookup_page (self, name) : - """ - Look up and return a Page object for the given page, or raise an error - """ - - # inital path - path = self.path - url_segments = [] - - # name segments - segments = name.split('/') - - # iterate through the parts of the page segments - while True : - segment = None - - # pop segment - if segments : - segment = segments.pop(0) - - url_segments.append(segment) - - # translate empty -> index - if not segment : - segment = 'index' - - # look for it in the dir - for filename in os.listdir(path) : - # build full file path - file_path = os.path.join(path, filename) - - # stat, recurse into subdirectory? - if os.path.isdir(file_path) and filename == segment : - # use new dir - path = file_path - - # break for-loop to look at next segment - break - - # split into basename + extension - basename, extension = os.path.splitext(filename) - - # ...remove that dot - extension = extension.lstrip('.') - - # match against requested page name? - if basename == segment : - # found the file we wanted - return self._lookup_page_type('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments)) - - else : - # inspect next file in dir - continue - - else : - # did not find any dir or file, break out of while loop - break - - # did not find the filename we were looking for in os.listdir - raise PageError("Page not found: %s" % name, status='404 Not Found') - - def handle_request (self, request) : - """ - Looks up the appropriate Page, and then renders it - """ - - # determine the page name - page_name = request.get_page_name() - - # get the page handler - page = self._lookup_page(page_name) - - # pass on - return page.handle_request(request) - diff -r 19ea04f4b0cd -r 09196d5b2a39 lib/filesystem/menu.py --- a/lib/filesystem/menu.py Sat Feb 07 17:07:06 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -""" - Handling the list of available pages -""" - -class Menu (object) : - """ - Contains info needed to render the menu - """ - - def __init__ (self, fs, page) : - """ - Gather the menu information for the given page, as part of the given FilesystemMapper - """ - - # the selected page - self.page = fs.tree.get_page(page.url) - - # the selected pagen's inheritance - self.ancestry = self.page.get_ancestry() if self.page else [] - - # list of menu items == root children, since we always show the full menu... - self.items = fs.tree.root.children - diff -r 19ea04f4b0cd -r 09196d5b2a39 lib/filesystem/page.py --- a/lib/filesystem/page.py Sat Feb 07 17:07:06 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ - -""" - Handling page requests -""" - -# for filesystem ops -import os, os.path -import time - -from lib import http, handler, template, config - -import menu - -class PageError (http.ResponseError) : - """ - Error looking up/handling a page - """ - - pass - -# XXX: should inherit from PageInfo -class Page (handler.RequestHandler) : - """ - This object represents the information about our attempt to render some specific page - """ - - def __init__ (self, fs, url, path, basename, url_tail, charset='utf8') : - """ - Initialize the page at the given location - - @param fs the FilesysteMapper - @param url the URL leading to this page - @param path the filesystem path to this page's file - @param basename the filesystem name of this page's file, without the file extension - @param url_trail trailing URL for this page - @param charset file charset - """ - - # store - self.fs = fs - self.url = url - self.path = path - self.basename = basename - self.url_tail = url_tail - self.charset = charset - - # sub-init - self._init() - - def _init (self) : - """ - Do initial data loading, etc - """ - - pass - - @property - def title (self) : - """ - Return the page's title - - Defaults to the retreiving the page title from page_list, or basename in Titlecase. - """ - - # lookup in PageTree - page_info = self.fs.tree.get_page(self.url) - - # fallback to titlecase - if page_info : - title = page_info.title - - else : - title = self.basename.title() - - return title - - @property - def content (self) : - """ - Return the page content as a string - """ - - abstract - - @property - def modified (self) : - """ - Returns the page modification timestamp - """ - - # stat - timestamp = os.stat(self.path).st_mtime - - return time.strftime(config.DATETIME_FMT, time.gmtime(timestamp)) - - def handle_request (self, request) : - """ - Renders the fs's layout template with this page + menu - """ - - # render the template - response_data = template.render(self.fs.template, - request = request, - site_root_url = request.get_script_dir(), - site_page_url = request.get_page_prefix(), - page = self, - menu = menu.Menu(self.fs, self), - ) - - # return the response - return http.Response(response_data) - -class HTMLPage (Page) : - """ - A simple .html page that's just passed through directly - """ - - @property - def content (self) : - """ - Opens the .html file, reads and returns contents - """ - - return open(self.path, 'rb').read().decode(self.charset) - -class TemplatePage (Page) : - """ - A template that's rendered using our template library - """ - - @property - def content (self) : - """ - Loads the .tmpl file, and renders it - """ - - return template.TemplateLoader.render_file(self.path, - page_tree = self.fs.tree, - ) - diff -r 19ea04f4b0cd -r 09196d5b2a39 lib/filesystem/page_tree.py --- a/lib/filesystem/page_tree.py Sat Feb 07 17:07:06 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +0,0 @@ -""" - Implements the tree containing pages and their metadata -""" - -from lib import tree_parse - -class PageTreeError (Exception) : - """ - Error parsing/loading the page tree - """ - - 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 tree of pages, rooted at .root. - - Use load_page_tree to initialize the global page_tree instance, and then use that - """ - - def __init__ (self, path) : - """ - Loads the PageTree root from the given file - """ - - # store - self.path = path - - # load - self._load(path) - - def _load (self, path) : - """ - Processes the lines in the given file - """ - - # parse tree - tree = tree_parse.parse(path, ':') - - if not tree : - raise PageTreeError("No root node found") - - 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 = None - - try : - url, title = line.split(':') - - except : - raise PageTreeError("Invalid line: %s:%d: %r" % (path, line_number, line)) - - # 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) - diff -r 19ea04f4b0cd -r 09196d5b2a39 sites/www.qmsk.net/__init__.py --- a/sites/www.qmsk.net/__init__.py Sat Feb 07 17:07:06 2009 +0200 +++ b/sites/www.qmsk.net/__init__.py Sat Feb 07 17:10:06 2009 +0200 @@ -3,8 +3,8 @@ """ from lib import template -from lib.filesystem.map import FilesystemMapper as _fstree +import map # global mapper attribute -handler = _fstree("pages", template=template.TemplateLoader.load("sites/www.qmsk.net/templates/layout.tmpl")) +handler = map.FilesystemMapper("pages", template=template.TemplateLoader.load("sites/www.qmsk.net/templates/layout.tmpl")) diff -r 19ea04f4b0cd -r 09196d5b2a39 sites/www.qmsk.net/map.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/www.qmsk.net/map.py Sat Feb 07 17:10:06 2009 +0200 @@ -0,0 +1,120 @@ + +import os, os.path + +from lib import http, template, map + +import page, page_tree + +class FilesystemMapper (map.Mapper) : + """ + Translates requests to handlers based on a filesystem directory containing various kinds of files + """ + + # list of page handlers, by type + PAGE_TYPES = [ + ('html', page.HTMLPage ), + (template.TEMPLATE_EXT, page.TemplatePage ), + ] + + def __init__ (self, path, template) : + """ + Create, path is where the pages are stored. The list of pages is loaded from $path/list + """ + + # store + self.path = path + self.template = template + + # load the page tree + self.tree = page_tree.PageTree(path + '/list') + + def _lookup_page_type (self, url, path, filename, basename, extension, tail) : + """ + We found the file that we looked for, now get the correct type + """ + + # find appropriate handler + for handler_ext, type in self.PAGE_TYPES : + # match against file extension? + if handler_ext == extension : + # found handler, return instance + return type(self, url, path, basename, tail) + + # no handler found + raise PageError("No handler found for page %r of type %r" % (url, extension)) + + def _lookup_page (self, name) : + """ + Look up and return a Page object for the given page, or raise an error + """ + + # inital path + path = self.path + url_segments = [] + + # name segments + segments = name.split('/') + + # iterate through the parts of the page segments + while True : + segment = None + + # pop segment + if segments : + segment = segments.pop(0) + + url_segments.append(segment) + + # translate empty -> index + if not segment : + segment = 'index' + + # look for it in the dir + for filename in os.listdir(path) : + # build full file path + file_path = os.path.join(path, filename) + + # stat, recurse into subdirectory? + if os.path.isdir(file_path) and filename == segment : + # use new dir + path = file_path + + # break for-loop to look at next segment + break + + # split into basename + extension + basename, extension = os.path.splitext(filename) + + # ...remove that dot + extension = extension.lstrip('.') + + # match against requested page name? + if basename == segment : + # found the file we wanted + return self._lookup_page_type('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments)) + + else : + # inspect next file in dir + continue + + else : + # did not find any dir or file, break out of while loop + break + + # did not find the filename we were looking for in os.listdir + raise PageError("Page not found: %s" % name, status='404 Not Found') + + def handle_request (self, request) : + """ + Looks up the appropriate Page, and then renders it + """ + + # determine the page name + page_name = request.get_page_name() + + # get the page handler + page = self._lookup_page(page_name) + + # pass on + return page.handle_request(request) + diff -r 19ea04f4b0cd -r 09196d5b2a39 sites/www.qmsk.net/menu.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/www.qmsk.net/menu.py Sat Feb 07 17:10:06 2009 +0200 @@ -0,0 +1,23 @@ +""" + Handling the list of available pages +""" + +class Menu (object) : + """ + Contains info needed to render the menu + """ + + def __init__ (self, fs, page) : + """ + Gather the menu information for the given page, as part of the given FilesystemMapper + """ + + # the selected page + self.page = fs.tree.get_page(page.url) + + # the selected pagen's inheritance + self.ancestry = self.page.get_ancestry() if self.page else [] + + # list of menu items == root children, since we always show the full menu... + self.items = fs.tree.root.children + diff -r 19ea04f4b0cd -r 09196d5b2a39 sites/www.qmsk.net/page.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/www.qmsk.net/page.py Sat Feb 07 17:10:06 2009 +0200 @@ -0,0 +1,140 @@ + +""" + Handling page requests +""" + +# for filesystem ops +import os, os.path +import time + +from lib import http, handler, template, config + +import menu + +class PageError (http.ResponseError) : + """ + Error looking up/handling a page + """ + + pass + +# XXX: should inherit from PageInfo +class Page (handler.RequestHandler) : + """ + This object represents the information about our attempt to render some specific page + """ + + def __init__ (self, fs, url, path, basename, url_tail, charset='utf8') : + """ + Initialize the page at the given location + + @param fs the FilesysteMapper + @param url the URL leading to this page + @param path the filesystem path to this page's file + @param basename the filesystem name of this page's file, without the file extension + @param url_trail trailing URL for this page + @param charset file charset + """ + + # store + self.fs = fs + self.url = url + self.path = path + self.basename = basename + self.url_tail = url_tail + self.charset = charset + + # sub-init + self._init() + + def _init (self) : + """ + Do initial data loading, etc + """ + + pass + + @property + def title (self) : + """ + Return the page's title + + Defaults to the retreiving the page title from page_list, or basename in Titlecase. + """ + + # lookup in PageTree + page_info = self.fs.tree.get_page(self.url) + + # fallback to titlecase + if page_info : + title = page_info.title + + else : + title = self.basename.title() + + return title + + @property + def content (self) : + """ + Return the page content as a string + """ + + abstract + + @property + def modified (self) : + """ + Returns the page modification timestamp + """ + + # stat + timestamp = os.stat(self.path).st_mtime + + return time.strftime(config.DATETIME_FMT, time.gmtime(timestamp)) + + def handle_request (self, request) : + """ + Renders the fs's layout template with this page + menu + """ + + # render the template + response_data = template.render(self.fs.template, + request = request, + site_root_url = request.get_script_dir(), + site_page_url = request.get_page_prefix(), + page = self, + menu = menu.Menu(self.fs, self), + ) + + # return the response + return http.Response(response_data) + +class HTMLPage (Page) : + """ + A simple .html page that's just passed through directly + """ + + @property + def content (self) : + """ + Opens the .html file, reads and returns contents + """ + + return open(self.path, 'rb').read().decode(self.charset) + +class TemplatePage (Page) : + """ + A template that's rendered using our template library + """ + + @property + def content (self) : + """ + Loads the .tmpl file, and renders it + """ + + return template.TemplateLoader.render_file(self.path, + page_tree = self.fs.tree, + ) + diff -r 19ea04f4b0cd -r 09196d5b2a39 sites/www.qmsk.net/page_tree.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/www.qmsk.net/page_tree.py Sat Feb 07 17:10:06 2009 +0200 @@ -0,0 +1,218 @@ +""" + Implements the tree containing pages and their metadata +""" + +from lib import tree_parse + +class PageTreeError (Exception) : + """ + Error parsing/loading the page tree + """ + + 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 tree of pages, rooted at .root. + + Use load_page_tree to initialize the global page_tree instance, and then use that + """ + + def __init__ (self, path) : + """ + Loads the PageTree root from the given file + """ + + # store + self.path = path + + # load + self._load(path) + + def _load (self, path) : + """ + Processes the lines in the given file + """ + + # parse tree + tree = tree_parse.parse(path, ':') + + if not tree : + raise PageTreeError("No root node found") + + 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 = None + + try : + url, title = line.split(':') + + except : + raise PageTreeError("Invalid line: %s:%d: %r" % (path, line_number, line)) + + # 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) +