#!/usr/bin/env python ''' The Genshi4web2py module provides Genshi markup templating functionality to the web2py framework. It does not provide Genshi's text-based templating since web2py's built-in template is so similar, it would be redundant. To enable Genshi templating on a controller basis, put the following code in your controller:: from Genshi4web2py import render response.postprocessing.append(lambda x: render(x, request, response)) If you would prefer to enable Genshi for a complete Application, the above code can be placed in a model file. This module can also make use of web2py's cache system for a large speed boost. To do so pass cache to render like so:: response.postprocessing.append(lambda x: render(x, request, response, cache=cache)) Note that using the cache for templates will cause frustration in a development environment since changes to a view will only be reflected when the cache refreshes (the default is every 10 minutes). @var regex_include: a compiled regular expression used to identify Genshi templates. @var Genshi: indicates if Genshi support is enabled @var READLIMIT: the max number of bytes of a template to read when identifying a template as a Genshi template ''' import re import os import types try: from genshi.template import TemplateLoader, Context, TemplateNotFound, TemplateSyntaxError from genshi.filters.html import HTMLFormFiller from genshi import HTML Genshi = True READLIMIT=512 regex_include=re.compile('(?P(?:\{\{\s*include\s+[\'"]|' + '[^\'"]*)' + '[\'"]\s*(?:\}\}|(?:/>|>\s*)))') except ImportError, e: Genshi = False def render(vars, request, response, **kwargs): '''Renders the controller output with a Genshi Template B{Render Options} - "renderOptions" can be specified as a dictionary in L{vars}. It is used to specify certain options the rendering engine should use. Specifying any of these is optional. Below are the specific valid rendering options:: - B{viewdirs}: a list of paths to template folders in which to look for views - B{view}: the name of the view to use for the controller calling L{render} - B{Content-Type}: the Content-Type header as it should be sent to the browser - B{HTMLFormFill}: a dictionary of values used to fill elements of forms in side the template after template parsing - B{GenshiDoctype}: the doctype that Genshi will apply to the resulting rendered output - B{GenshiRenderEngine}: the engine Genshi will use to render the view @type vars: dict @param vars: a dictionary of variables to pass to the template @param request: the web2py request instance. Used to determine the current application folder @param response: the web2py response instance. Used to set headers if needed. @type kwargs: dict @param kwargs: any additional keyword arguments will be made available for the run environment of the view. @returns: If the template exists and is valid, a normal string will be returned; otherwise the passed L{vars} will be returned to be parsed by a downstream template renderer. @raises TemplateSyntaxError: In the event that a template has a syntax error, a TemplateSyntaxError will be raised. ''' if type(vars) == types.StringType: return vars cache = kwargs.get('cache', None) appfolder = os.path.dirname(os.path.normpath(request.folder)) initview = os.path.join(appfolder, 'init', 'views') viewdirs = [ os.path.join(request.folder, 'views') ] if os.path.exists(initview) and viewdirs[0] != initview: viewdirs.append(initview) opts = {'viewdirs': viewdirs ,'view':response.view ,'Content-Type':"text/html; charset=utf-8" ,'GenshiDoctype':'html' ,'GenshiRenderEngine':'html' ,'HTMLFormFill':vars.get('HTMLFormFill') } if vars.get('renderOptions'): opts.update(vars['renderOptions']) cachekey = ''.join(opts['viewdirs']) if cache: loader = cache.ram(cachekey , lambda:TemplateLoader(opts['viewdirs'] , auto_reload=False)) else: loader = TemplateLoader(opts['viewdirs'], auto_reload=False) try: if cache: output = cache.ram(cachekey+opts['view'] , lambda:loader.load(opts['view'])) else: output = loader.load(opts['view']) except TemplateNotFound, e: return vars except TemplateSyntaxError, e: f = open(os.path.normpath(e.filename)).read(READLIMIT) if not regex_include.search(f): return vars else: raise e ctxt = Context(request=request, response=response, HTML=HTML) ctxt.update(vars) ctxt.update(kwargs) output = output.generate(ctxt) if opts.get('HTMLFormFill'): try: output = output.filter(HTMLFormFiller(data=opts['HTMLFormFill'])) except: pass output = output.render(opts['GenshiRenderEngine'] , doctype=opts['GenshiDoctype']) response.headers['Content-Type']=opts['Content-Type'] return output