#!/usr/bin/env python2.4 # -*- coding: utf-8 -*- """ WWW-sovellukset (TJTA270) Viikkotehtävän ratkaisu Copyright (c) Esa-Matti Suuronen This file is part of Sokkeli web framework. Sokkeli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Sokkeli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Sokkeli. If not, see . """ import os.path import re class DefaultingDict(dict): """We don't want exception for every missing key in templates""" default_value = "## '%s' key not set ##" def __getitem__(self, key): try: return self._get_item_call_first(key) except KeyError: return self.default_value % key def _get_item_call_first(self, key): """ Try to call the item first. With this we can program bit more in functional manner. """ item = dict.__getitem__(self, key) try: return item() except TypeError: return item def read_template_from_file(file, template_dir=None): try: # Just read it if it is file a like object return file.read() except AttributeError: pass # We'll maybe it is a filename if not template_dir: template_dir = os.path.join( os.path.abspath(os.path.dirname(__file__)), "templates" ) # We will let exception to fly on purpose so that cgitb can format that f = open(os.path.join(template_dir, file), 'r') template = f.read() f.close() return template class Sapluuna(object): """Very simple templating engine for WWW-sovellukset course at university of Jyväskylä. """ forin_pattern = re.compile( r"%\(for\s+([A-Za-z0-9_]+)" r"\s+in\s+" r"([A-Za-z0-9_]+)\)l" r"(.*?)" r"%\(endfor\)l$", re.MULTILINE + re.DOTALL) def __init__(self, template, context={}, headers={}): self.headers = { 'Content-type': 'text/html', } self.template = template self.headers.update(headers) self.context = DefaultingDict(context) def __repr__(self): return "<%s %s>" % ( self.__class__.__name__, self.filename ) def __str__(self): return self.render() def update_headers(self, headers): self.headers.update(headers) def update_context(self, context): self.context.update(context) def render_headers(self): return "\n".join("%s: %s" %( key.strip(), value.strip()) for key, value in self.headers.items()) def __iter__(self): """ Taa voi olla vahan hidas, mutta toiminee nyt viikkotehtavan verran """ return iter(self.render()) def render(self, context={}): self.update_context(context) template = self._expand_for_ins(self.template) return template % self.context def render_response(self, context={}): return """%s %s""" % ( self.render_headers(), self.render(context), ) def _expand_for_ins(self, template): """ Adds 'For in' loops to templating. Uses kinda similar syntax as Python string formatting does. Eg. %(for var in list)l Item in list %(var)s <- regular string format %(endfor)l TODO: Support nested loops """ # Search "for in" patters in non-greedy manner # (One "for in" per while loop iteration) match = self.forin_pattern.search(template) while match: (template_var, iterable_key, to_be_repeated) = match.groups() part_repeated = "" for var in self.context[iterable_key]: block_dict = DefaultingDict({template_var: var}) try: # If input is dict like, add ability to use them # like this %(dict_like.key)s for k, v in var.items(): block_dict["%s.%s" % (template_var, k)] = v except (AttributeError, TypeError, ValueError): pass # Input variable was not a dict like object for k, v in var.__dict__.items(): block_dict["%s.%s" % (template_var, k)] = v part_repeated += to_be_repeated % block_dict # Replace first "for in" match template = re.sub(self.forin_pattern, part_repeated, template, 1) # Search for next math match = self.forin_pattern.search(template) return template if __name__ == '__main__': sap = Sapluuna(""" Alku %(for var in lista)l Alkio %(var)s %(endfor)l %(for var in toinen_lista)l Toisesta listasta url %(var.url)s id %(var.id)s %(endfor)l Loppu """, { 'lista': ["eka", "toka"], 'toinen_lista': [ {"url": "http://foo", "id": 1}, {"url": "http://bar", "id": 2}, ], } ) print sap.render()