#!/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()