Seite 1 von 1
Mini-Template-Engine
Verfasst: Sonntag 5. Juli 2009, 10:28
von sma
Wenn es Micro-Webrahmenwerke gibt, muss es auch Mini-Template-Engines geben. Meine ist noch ein bisschen zu groß und ich bin nicht 100% von dem Ansatz überzeugt, aber ich habe mal versucht, etwas wie Djangos Engine in 100 Zeilen zu pressen (ohne die ganzen eingebauten Tags und Filter):
http://gist.github.com/140888
Die Engine kann so etwas verarbeiten:
Code: Alles auswählen
Beispiel:
{% if len(items) %}
{% for i in items %}
{% if i|odd %}{{ i }}{% else %}-{% end %}
{% end %}
{% end %}
Im Gegensatz zu Django ist alles, was wie Variablen aussieht, echter Python-Code, der mit `eval` verarbeitet wird. Filter realisiere ich durch Überladen von `|`. Geschwindigkeitsrekorde wird die Engine auch nicht aufstellen, baue ich doch überall Strings zusammen. Und Fehler fange ich schon mal gar nicht ab...
Stefan
Verfasst: Sonntag 5. Juli 2009, 19:57
von derdon
Statt
reicht doch auch
oder übersehe ich da etwas?
Edit: Warum hast du aus Zeile 81 kein lambda gemacht?
Edit2: Warum kein re.finditer in Zeile 7?
Verfasst: Montag 6. Juli 2009, 00:14
von stuhlbein
Coole sache, vorallem da es wirklich gut in einige Mini-frameworks passen würde.
Leider hat es den ersten Test-durchlauf nicht bestanden, da ich anscheinend was falsch machte?
Der code:
Code: Alles auswählen
import os
import minidjango
if __name__ == '__main__':
print minidjango.Template("""
{% for key, value in environ_items %}
{{ key }} := "{{ value }}"
{% end %}
""").render({
'environ_items': os.environ.iteritems
})
Die Fehlermeldung:
Code: Alles auswählen
Traceback (most recent call last):
File "./md_test.py", line 11, in <module>
""").render({
File "/home/nil/Desktop/minidjango.py", line 7, in Template
return Block(None, iter(re.findall(tokens, source)))
File "/home/nil/Desktop/minidjango.py", line 38, in __init__
body.append(tags[parts[0]](parts, tokens))
File "/home/nil/Desktop/minidjango.py", line 68, in __init__
assert 'in' == parts[2]
AssertionError

Verfasst: Montag 6. Juli 2009, 11:13
von Leonidas
Man sieht an der Fehlermeldung recht deutlich dass es an der 3 Stellen in ``{% for %}`` ein ``in`` erwartet, somit wird also kein Tuple-Unpacking unterstützt.
Verfasst: Montag 6. Juli 2009, 11:44
von Defnull
Wäre es nicht einfacher, den kram Zeile für Zeile mit ein wenig re-magie in nativen python-code umzuwandeln und das dann komplett in den eval zu quetschen? Beispiel:
wird zu
Code: Alles auswählen
def render(list):
#%for item in list:
for item in list:
# { item | repr }
yield " "
yield repr(item)
yield "\n"
#%end
Man müsste stumpf alles mit % als python code übernehmen, anhand der %if/for/while und %end Instruktionen die Einrückung mit zählen und alle Strings dazwischen mit yield ohne Änderung zurück geben. Das wäre denke ich auch deutlich performanter. Ich glaub das bastel ich mal die Tage.
Verfasst: Montag 6. Juli 2009, 12:42
von Dauerbaustelle
Naja, aber da verlierst du ja komplett den Sandbox- bzw. Unabhängigkeitsfaktor.
Verfasst: Montag 6. Juli 2009, 13:19
von Defnull
Du kannst ja immer noch kontrollieren, was du als python Ausdruck zu lässt, aber ich finde es umständlich, die Python-Sprache in Python-Klassen abzubilden und dann einzeln aus zu führen O.o Sandbox erreichst du später mit beschnittenen globals() und locals(). Was du mit Unabhängigkeit meinst, weis ich nicht so recht.
Bei Templates gibt es ja generell einen Glaubenskrieg: Manche trauen den Template-Schreibern und manche trauen ihnen nicht. Ich gehöre zu der ersten Fraktion.
Verfasst: Montag 6. Juli 2009, 13:49
von Dauerbaustelle
Ich auch, ich finde aber, man sollte den User stets davor schützen, alles kaputtzuhauen ;-)
Verfasst: Montag 6. Juli 2009, 20:47
von sma
@deron, das `len` sollte nur zeigen, dass eingebaute Funktionen funktionieren. Zu Zeile 81: Ich ziehe ein `def a(): return 1` einem `a = lambda: 1` auf Grundebene vor, selbst wenn es länger ist. Zu Zeile 7: `finditer` liefert match-Objekte, ich wollte aber die Strings selbst. Ich hätte `(m.group() for m in re.finditer(...))` benutzen können, aber das ist mehr und Speicherplatz war mir egal.
@Bitfish, Tupelzuweisung habe ich einfach nicht implementiert. Hatte dran gedacht und wäre nicht weiter schwer, müsste nur ein bisschen ändern, wie ich Tags parse.
@Defnull, der klassische Ansatz wie Javas JSPs oder Rubys ERBs funktionieren ist, das Programm quasi umzukrempeln und aus dem statischen Text mit ein bisschen Programmcode viel Programmcode mit ein paar zusätzlichen prints (o.ä.) zu machen. Ob man's dadurch kürzer hinbekommt, kann ich nicht überblicken. Die Mako-Syntax mit dem `%` wollte ich ursprünglich umsetzen, doch das wurde komplizierter zu parsen. Außerdem wollte ich meinen bereits existierenden Textmate-Mode für Django-Templates weiter verwenden. Ich bin auf deine Lösung gespannt.
Wenn man wirklich nur `if` und `for` braucht, ist es wahrscheinlich einfach, egal, ob man nun Djangos oder Makos Syntax realisieren will. Mein Verfahren ist generischer. Vielleicht zu generisch.
Ich könnte mir übrigens vorstellen, dass man auch HAML in < 100 Zeilen verarbeiten kann. Das geht eigentlich recht gut mit Pythons Einrückung zusammen, funktioniert dann aber natürlich nur für XHTML, nicht für beliebige Texte.
@Dauberbaustelle, ich bin da bei Defnull und in der Gruppe der Leute, die Template-Schreibern (nämlich mir) trauen. Und mit einem Template, in dem ich Python-Code aufrufen kann, kann ich nicht mehr oder weniger "kaputthauen" als mit einem Python-Programm selnbst.
Irgendwie nagt aber bei mir im Hinterkopf, dass man Templates noch anders angehen kann und vielleicht sollte. Und wenn nur, um anders zu sein. Dazu demnächst mehr.
Stefan
Verfasst: Montag 6. Juli 2009, 21:07
von Defnull
sma hat geschrieben:Ich bin auf deine Lösung gespannt.
Ich war schon fleißig
http://www.python-forum.de/topic-19500.html
Es ist sogar schon in Bottle integriert:
http://www.python-forum.de/post-141198.html