Ich geb zu, die Idee is geklaut, SMA verfolgt aber eine komplett anderen Ansatz: Während er eine eigene Sprache in Python schreiben will, möchte ich lediglich das Whitespace-Problem bei eingebettetem Python umgehen. Das reicht für meine Zwecke und ist deutlich einfacher
Ich bin übrigens fertig. Der Code ist nicht schön, funktioniert mit dem oben genannten Beispiel aber sehr gut
Meine Ziele:
1) Zeilen: 75/100 (erreicht)
2) Features: Templates können alles, was Python kann (erreicht)
3) Speed: Das Script kompiliert auf meiner Maschine (AMD3000) etwa 1000 Templates pro Sekunde und rendert diese etwa 10.000 mal pro Sekunde

(erreicht!)
Hier der Code:
Code: Alles auswählen
import re
from StringIO import StringIO
class Template(object):
''' Allows to compile a template file into python code and execute it with different locals'''
re_block = re.compile(r'^\s*%\s*((if|elif|else|try|except|finally|for|while|with).*:)\s*$')
re_end = re.compile(r'^\s*%\s*end(.*?)\s*$')
re_code = re.compile(r'^\s*%\s*(.*?)\s*$')
re_inc = re.compile(r'\{\{(.*?)\}\}')
def __init__(self, filename):
''' Compiles a template file '''
self.filename = filename
self.code = "\n".join(list(self.compile(filename)))
self.co = compile(self.code, '<string>', 'exec')
def compile(self, filename):
def code_str(level, line, value):
return ' '*level + "stdout.write(r'''%s''') # Line: %d" % (value.replace("'","\'").replace('\\','\\\\'), line)
def code_print(level, line, value):
return ' '*level + 'stdout.write(str(%s)) # Line: %d' % (value, line)
def code_raw(level, line, value):
return ' '*level + value + ' # Line: %d' % line
level = 0
ln = 0
fp = open(filename, 'r')
for line in fp:
ln += 1
m = self.re_block.match(line)
if m:
if m.group(2).strip().lower() in ('elif','else','except','finally'):
if level > 0:
level -= 1
else:
yield code_raw(level, ln, '#Unexpected end of block: ' + m.group(1).strip())
yield code_raw(level, ln, m.group(1).strip())
level += 1
continue
m = self.re_end.match(line)
if m:
if level > 0:
yield code_raw(level, ln, '#END ' + m.group(1).strip())
level -= 1
else:
yield code_raw(level, ln, '#Unexpected end of block: ' + m.group(1).strip())
continue
m = self.re_code.match(line)
if m:
yield code_raw(level, ln, m.group(1).strip())
continue
lasts = 0
for m in self.re_inc.finditer(line):
yield code_str(level, ln, line[lasts:m.start(0)])
lasts = m.end(0)
yield code_print(level, ln, m.group(1).strip())
if lasts:
yield code_str(level, ln, line[lasts:])
continue
yield code_str(level, ln, line)
fp.close()
def render(self, **args):
io = StringIO()
args['stdout'] = io
args['__builtins__'] = __builtins__
eval(self.co, {}, args)
io.seek(0)
return io
Und nochmal die Benchmark Ergebnisse:
Code: Alles auswählen
from ttpl import Template
from timeit import Timer
print 'Test run:'
t = Template('./ttpl/test.tpl')
output = t.render(l = [1,'a'])
print output.read()
print 'Speed test:'
t = Timer("t = Template('./ttpl/test.tpl')", "from ttpl import Template").timeit(10000)
print "Compiled 10000 templates in %f seconds (%d/s)" % (t, 10000/t)
t = Timer("t.render(l = [1,'a'])", """from ttpl import Template
t = Template('./ttpl/test.tpl')""").timeit(10000)
print "Rendered 10000 templates in %f seconds (%d/s)" % (t, 10000/t)
Das Wirft bei mir (AMD3000, 4GB Ram, Python2.6) folgendes aus:
Code: Alles auswählen
Test run:
<html>
<head>
<title>Test</title>
</head>
<body>
<ul>
<li>
Zahl: 1
</li>
<li>
Other: 'a'
</li>
</ul>
</body>
</html>
Speed test:
Compiled 10000 templates in 9.270781 seconds (1078/s)
Rendered 10000 templates in 1.010130 seconds (9899/s)
Entwicklungszeit: Etwa 40 Minuten