# HG changeset patch # User Tero Marttila # Date 1233953280 -7200 # Node ID 0ce1f471e9d78a46d1277c12d2aab93a7ba4389c # Parent d6a8258bd90ea11b927018fd0ea23e638a2118fa and it works, a lot better than before diff -r d6a8258bd90e -r 0ce1f471e9d7 .hgignore --- a/.hgignore Fri Feb 06 21:31:02 2009 +0200 +++ b/.hgignore Fri Feb 06 22:48:00 2009 +0200 @@ -1,4 +1,4 @@ syntax: regexp \.[^/]+.sw[op]$ -^site/templates/[^/]+.py$ +^cache/templates/. diff -r d6a8258bd90e -r 0ce1f471e9d7 lib/handler.py --- a/lib/handler.py Fri Feb 06 21:31:02 2009 +0200 +++ b/lib/handler.py Fri Feb 06 22:48:00 2009 +0200 @@ -2,12 +2,26 @@ The actual application behaviour, i.e. generating a Response from a Request :) """ -import http, page +import http, page, template def handle_request (request) : """ Take the Request, and return a Response """ - return http.Response("Hello World") + # determine the page name + page_name = request.get_page_name() + # get the page handler + p = page.lookup(page_name) + + # render the template + response_data = template.render("layout", + site_root_url = request.get_script_dir(), + page_title = p.get_title(), + page_content = p.get_content(), + ) + + # return the response + return http.Response(response_data) + diff -r d6a8258bd90e -r 0ce1f471e9d7 lib/http.py --- a/lib/http.py Fri Feb 06 21:31:02 2009 +0200 +++ b/lib/http.py Fri Feb 06 22:48:00 2009 +0200 @@ -8,6 +8,9 @@ # for header handling import wsgiref.headers +# for path handling +import os.path + class Request (object) : """ HTTP Request with associated metadata @@ -26,6 +29,29 @@ # parse query args self.arg_dict = cgi.parse_qs(self.arg_str, True) + + def get_script_dir (self) : + """ + Returns the URL path to the requested script's directory with no trailing slash, i.e. + + / -> + /foo.cgi -> + /foo/bar.cgi -> /foo + """ + + return os.path.dirname(self.env['SCRIPT_NAME']) + + def get_page_name (self) : + """ + Returns the requested page path with no leading slash, i.e. + + /foo.cgi -> + /foo.cgi/ -> + /foo.cgi/bar -> bar + /foo.cgi/quux/ -> quux/ + """ + + return os.path.normpath(self.env['PATH_INFO']).lstrip('/') class Response (object) : """ @@ -91,19 +117,25 @@ A response with an error code / message """ - def __init__ (self, status, message) : + def __init__ (self, status, message, details=None) : """ Build a plain error message response with the given status/message + + @param status HTTP status code + @param message short message to describe errors + @param details optional details, plaintext """ data = """\ %(title)s

%(title)s

%(message)s

+%(details)s """ % dict( title = status, - message = message + message = message, + details = '
%s
' % details if details else '' ) super(ErrorResponse, self).__init__(data, status=status) @@ -113,14 +145,15 @@ An exception that results in a specfic 4xx ErrorResponse message to the client """ - def __init__ (self, message, status='400 Bad Request') : + def __init__ (self, message, status='400 Bad Request', details=None) : self.status = status self.message = message + self.details = details super(ResponseError, self).__init__(message) def get_response (self) : - return ErrorResponse(self.status, self.message) + return ErrorResponse(self.status, self.message, self.details) class Redirect (Response) : """ diff -r d6a8258bd90e -r 0ce1f471e9d7 lib/page.py --- a/lib/page.py Fri Feb 06 21:31:02 2009 +0200 +++ b/lib/page.py Fri Feb 06 22:48:00 2009 +0200 @@ -3,19 +3,169 @@ Handling page requests """ -def handle_html (env) : - return "A HTML page" +# for filesystem ops +import os, os.path +# for ResponseError +import http + +# for TemplatePage +import template + +# path to directory containing the page heirarcy +PAGE_DIR = "pages" + +class PageError (http.ResponseError) : + """ + Error looking up/handling a page + """ + + pass + +class Page (object) : + """ + This object represents the information about our attempt to render some specific page + """ + + def __init__ (self, url, path, basename, url_tail) : + """ + Initialize the page at the given location + + @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 + """ + + # store + self.url = url + self.path = path + self.basename = basename + self.url_tail = url_tail + + # sub-init + self._init() + + def _init (self) : + """ + Do initial data loading, etc + """ + + pass + + def get_title (self) : + """ + Return the page's title + + Defaults to the Titlecase'd file basename + """ + + return self.basename.title() + + def get_content (self) : + """ + Return the page content as a string + """ + + abstract + +class HTMLPage (Page) : + """ + A simple .html page that's just passed through directly + """ + + def get_content (self) : + """ + Opens the .html file, reads and returns contents + """ + + return open(self.path, 'rb').read() + +class TemplatePage (Page) : + """ + A template that's rendered using our template library + """ + + def get_content (self) : + """ + Loads the .tmpl file, and renders it + """ + + return template.render_file(self.path) # list of page handlers, by type -type_handlers = [ - ('.html', handle_html), +TYPE_HANDLERS = [ + ('html', HTMLPage ), + (template.TEMPLATE_EXT, TemplatePage ), ] -def lookup_handler (path) : +def _lookup_handler (url, path, filename, basename, extension, tail) : """ - Look up and return a handler for the given page, or raise an error + We found the file that we looked for, now get its handler """ - return handle_html + # find appropriate handler + for handler_ext, handler in TYPE_HANDLERS : + # match against file extension? + if handler_ext == extension : + # found handler, return instance + return handler(url, path, basename, tail) + # no handler found + raise PageError("No handler found for page %r of type %r" % (url, extension)) + +def lookup (name) : + """ + Look up and return a Page object for the given page, or raise an error + """ + + # inital path + path = PAGE_DIR + url_segments = [] + + # name segments + segments = name.split('/') + + # iterate through the parts of the page segments + while segments : + # pop segment + segment = segments.pop(0) + + # add to page url + 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 _lookup_handler('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments)) + + else : + # inspect next file in dir + continue + + # did not find the filename we were looking for in os.listdir + raise PageError("Page not found: %s" % name, status='404 Not Found') + diff -r d6a8258bd90e -r 0ce1f471e9d7 lib/template.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/template.py Fri Feb 06 22:48:00 2009 +0200 @@ -0,0 +1,80 @@ +""" + Template handler +""" + +# use Mako +from mako import exceptions +from mako.template import Template +from mako.lookup import TemplateLookup + +# for http.ResponseError +import http + +# path to template files +TEMPLATE_DIR = "templates" + +# path to cached templates +CACHE_DIR = "cache/templates" + +# template file extension +TEMPLATE_EXT = "tmpl" + + +# our Mako template lookup handler +_lookup = TemplateLookup(directories=[TEMPLATE_DIR], module_directory=CACHE_DIR) + + +class TemplateError (http.ResponseError) : + """ + Raised by the template module functions + """ + + pass + +def lookup (name) : + """ + Looks up a template based on the bare "name", which does not include the path or file extension + """ + + try : + return _lookup.get_template("%s.%s" % (name, TEMPLATE_EXT)) + + except : + raise TemplateError("Template broken: %r" % (name, ), status='500 Internal Server Error', details=exceptions.text_error_template().render()) + +def load (path) : + """ + Loads a template from a specific file + """ + + try : + return Template(filename=path, module_directory=CACHE_DIR) + + except : + raise TemplateError("Template broken: %r" % (path, ), status='500 Internal Server Error', details=exceptions.text_error_template().render()) + +def render_template (tpl, **params) : + """ + Render the given template, returning the output, or raising a TemplateError + """ + + try : + return tpl.render(**params) + + except : + raise TemplateError("Template render failed", status='500 Internal Server Error', details=exceptions.text_error_template().render()) + +def render (name, **params) : + """ + Render a template, using lookup() on the given name + """ + + return render_template(lookup(name), **params) + +def render_file (path, **params) : + """ + Render a template, using load() on the given path + """ + + return render_template(load(path), **params) + diff -r d6a8258bd90e -r 0ce1f471e9d7 pages/foo.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pages/foo.html Fri Feb 06 22:48:00 2009 +0200 @@ -0,0 +1,3 @@ + +HTML foo! + diff -r d6a8258bd90e -r 0ce1f471e9d7 pages/index.py --- a/pages/index.py Fri Feb 06 21:31:02 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ - -import page - -class Page (page.Page) : - """ - Main page with simple stuff - """ - - title = "Main Page" - name = "Main" - path = "" - - parent = None - menu = [ - "", - "main" - ] - - def render_template (self) : - return self._build_template(_templates.main) - diff -r d6a8258bd90e -r 0ce1f471e9d7 pages/index.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pages/index.tmpl Fri Feb 06 22:48:00 2009 +0200 @@ -0,0 +1,8 @@ +

A Mako template!

+ + + diff -r d6a8258bd90e -r 0ce1f471e9d7 templates/layout.tmpl --- a/templates/layout.tmpl Fri Feb 06 21:31:02 2009 +0200 +++ b/templates/layout.tmpl Fri Feb 06 22:48:00 2009 +0200 @@ -1,18 +1,9 @@ -#def page_title -qmsk.net -#end def -#def page_content -

Content Goes Here

- -Content goes here. -#end def - - qmsk.net :: $page_title - + qmsk.net :: ${page_title} +
- $page_content + ${page_content}