Python-Fans finden in Empy einen wohl durchdachten Template-Prozessor für Template-basierte Webentwicklungen. Seine schlanke Syntax ermöglicht eine Wiki-Software mit nur 4 KByte Code.
In der Welt der Open-Source-Software scheinen manche Dinge einfach in der Luft zu liegen. Wie sonst ließe sich erklären, dass man nichts ahnend am Rechner sitzt, die neueste Version einer Software herunterlädt und plötzlich eine weitere Variante eines Allerweltswerkzeugs gebastelt hat – noch bevor sich die Frage “Was fange ich damit am besten an?” wirklich stellte. So geschehen mit einem Wikiwiki-Programm, das plötzlich da war, und zwar bei der Suche nach brauchbaren Anwendung für den Template-Prozessor Empy.
Aber braucht die Welt ein weiteres Wikiwiki-Programm oder einen weiteren Template-Prozessor? Sollte man nicht lieber gegen Dummheit und Hunger auf Erden kämpfen? Man ahnt schon, Wikiwikis sind einfacher zu haben, aber eins, dessen Code weniger als 4 KByte groß ist, macht nachdenklich. Es bietet zwar nur die Kernfunktionen, aber immerhin besteht dann die Chance zu verstehen, wie etwas funktioniert. Anders als bei den meisten bereits existierenden Wikis, etwa Wikipedia[1], von Dummheit und Hunger ganz zu schweigen.
Wickie kann alles
Jedenfalls ist das hier vorgestellte und nach einem freundlichen Alt-Skandinavier benannte Programm »HeyHeyWickie«[2] in einem Punkt recht bemerkenswert: Durch seine Eigenschaft, beliebigen Quellcode auszuführen, zeigt es eine ungeahnte Flexibilität. Was natürlich auch seine Problemchen hat, aber dazu später mehr.
Grundlage dieser Flexibilität ist der darunter liegende Template-Prozessor. Solche Prozessoren gibt es heute wie Sand am Meer. Gemeinsam ist ihnen, dass sie eine Textdatei als Eingabe nehmen, die darin enthaltenen speziell markierten Vorlagen (Templates) erkennen und durch einen anderen Inhalt ersetzen, bevor die neu entstandene, meist größere Datei ausgegeben wird. Diese Ersetzungen bestehen meist aus der Expansion einzelner Strings oder der Iteration über irgendwelche Daten, die im Template beschrieben werden.
Babylonische Zuständ
Die Prozessoren haben nicht nur unterschiedliche APIs, sondern definieren oft auch noch eine Art eigener Minisprache für ihre Templates; als ob nicht schon heute fast mehr künstliche als natürliche Sprachen existierten. So manche dieser Template-Prozessoren sind auch so eng verbandelt mit der eigentlichen (meist Web-)Anwendung, für die sie ursprünglich entwickelt wurden, dass sie isoliert oft gar nicht verwendbar sind.
Empy[3] hingegen bedient sich einer existierenden universellen Programmiersprache, nämlich derselben, in der es selbst implementiert ist: Python[4]. Der Prozessor entstand unabhängig von einer konkreten Anwendung und zeichnet sich durch seine Vollständigkeit und universelle Verwendbarkeit aus. Er führt beliebigen Quellcode in irgendwelchen Text- und sogar Binärdateien aus. Neben jenen Tags, die den nackten Code markieren, verfügt Empy auch über Tags, die gezielt einzelne Sprachkonstrukte wie Iterationen oder Ausnahmebehandlungen nachbilden, und geht allein mit Letzterem schon weiter als die meisten anderen Templating-Systeme.
Empy ist hervorragend dokumentiert und sehr leicht zu installieren, was es unnötig macht, Installation und Features an dieser Stelle in epischer Breite abzuhandeln. (Eine Präsentation dazu, natürlich auch mit Empy erstellt, gab es auf der European Python and Zope Conference, EPC2003, sie ist online verfügbar[5].) Stattdessen ist das eingangs erwähnte Wiki gut geeignet, Empy zu verstehen. Etwas Vorsicht ist jedoch angebracht: Das Projekt entwickelt sich allmählich weiter und nachfolgende Versionen werden auch zur Verfügung stehen, sind aber dann doch etwas umfangreicher als nur 4096 Byte.
Des Wikingers Kern
»HeyHeyWickie« selbst ist ein einfaches CGI-Programm. Es greift auf ein spezielles Verzeichnis zu (normalerweise unterhalb der lokalen Apache-Installation), wo sich Dateien befinden, die zu Wikiwiki-Webseiten werden sollen. Eine solche Datei, zum Beispiel mit dem Dateinamen »FrontPage.txt«, erkennt »HeyHeyWickie« an der Erweiterung, lädt sie, verarbeitet sie mit Empy und konvertiert sie mit Hilfe der Docutils[6] zu HTML. Jedem Dateinamen entspricht ein Wikiwiki-Name (hier einfach der Dateiname ohne die Erweiterung), der in jeder beliebigen Seite direkt vorkommen kann.
Empy und Docutils im Doppelpack
Empy und Docutils kommen hierbei zwei unterschiedliche Rollen zu, obwohl beide den Quelltext einer Datei verarbeiten. Zuerst stürzt sich Empy auf den Datei-Inhalt, was dazu führt, dass die dynamischen Code-Elemente, sofern vorhanden, ausgeführt werden. Dazu gehört auch das Expandieren der Wikiwiki-Namen zu klickbaren Hyperlinks. Bei »HeyHeyWickie« muss man dafür ein »@« voranstellen.
Docutils hat die Aufgabe, anschließend den Text (statisch aus einer Datei geladen und/oder von Empy dynamisch modifiziert) nach HTML zu konvertieren. Das Ausgangsformat ist eine Art angereichertes, lesbares Ascii in einem Format namens Restructured Text (Rest), bei dem mit zweidimensionalem Markup einzelne Elemente wie Überschriften, Listen, Hyperlinks oder Tabellen auszeichnet werden. Rest kann noch viel mehr, was sich in Einführungen[7] und der Spezifikation[8] nachlesen lässt.
Das CGI-Programm »wickie4K.py« (der Kürze wegen ohne den Vornamen »HeyHey«) besteht im Wesentlichen aus ein paar Konstanten, die zum Beispiel das Verzeichnis der Wikiwiki-Dateien angeben, sowie aus einigen kurzen HTML-Schnipseln, die unter anderem auf allen Seiten als Kopf- und Fußzeilen erscheinen. Diese würde man in der Praxis aber besser aus einem anderen Modul importieren, um mehr Flexibilität in der Ausgabe zu erzielen.
Weiterhin gibt es einige kurze Funktionen, die mehr oder weniger zwischen Dateinamen, Wikiwiki-Namen und Hyperlinks konvertieren, Dateien laden und den Inhalt mit Empy und Docutils weiterverarbeiten und schließlich mit Kopf- und Fußzeilen angereichert als HTML an den Browser ausgeben. Das Hauptprogramm kümmert sich um die unterschiedlichen (CGI-)Aktionen, nämlich das Anzeigen und Editieren von existierenden Seiten oder das Speichern einer neuen Seite.
Bis hierher hat man ein kompaktes niedliches System, das Wikiwiki-Seiten dank Empy einfach verlinkt und dank Docutils hübsch darstellen und neu anlegen kann, siehe Abbildung 1. Die eigentliche Flexibilität ergibt sich aus der Tatsache, dass jede Seite beliebigen Quellcode enthalten kann, der also nicht unbedingt ins eigentliche Hauptprogramm integriert werden muss. Im Folgenden sind dafür einige Beispiele angegeben.
|
Listing 1: »RecentChanges«-Seite |
01 @{
02 import wickie4K
03 from os.path import getmtime
04 files = wickie4K.getWikiPaths()
05 files = map(lambda f:(getmtime(f), f), files)
06 files.sort()
07 files.reverse()
08 files = map(lambda f:f[1], files)
09 files = map(wickie4K.wikiPath2name, files)
10 files = map(lambda f:f[0], files)
11 }
12
13 This is currently a list of all pages with
14 the most recently changed ones at the top:
15
16 @[for f in files]
17 - @wickie4K.wikiName2link(f)
18 @[end for]
|
Total flexibel
Zu den üblichen Seiten, die jedes Wikiwiki-System ausmachen, gehört die Recent-Changes-Seite, die dynamisch generiert wird und über die zuletzt editierten Seiten informiert. Im Falle von »HeyHeyWickie« ist dies eine normale Seite wie alle anderen auch. Es erzeugt zwar für alle Seiten eine Kopf- und -Fußzeile, die einen Link auf diese Seite enthalten, weiß aber ansonsten nichts von der besonderen Aufgabe dieser Seite.
Um eine Liste der letzten Änderungen darzustellen, muss als Erstes klar sein, welche Dateien im Wikiwiki-Verzeichnis existieren, bevor sie nach deren Zeitstempel sortiert und in der entsprechenden Reihenfolge aufgelistet werden, wobei die zuletzt editierte ganz oben erscheint. Der Code in Listing 1 bewerkstelligt genau dies. Zuerst bestimmt die Folge von Python-Anweisungen aus allen im Wikiwiki-Verzeichnis vorhandenen Quelldateien eine sortierte Liste von Wikiwiki-Namen. Darin erscheint die Datei »RecentChanges.txt« selbst als »RecentChanges«.
Danach folgt eine erklärende Zeile Text, die zu Beginn der Seite ausgegeben wird, bevor mit wenigen Empy-Tags am Ende über die zuvor bestimmte Liste iteriert wird und jeder Wikiwiki-Name als URL-Hyperlink erscheint. Die Links werden dabei im Rest-Format generiert, wobei durch den Spiegelstrich davor mittels Docutils ein Eintrag in einer hübschen Liste in HTML entsteht.

Abbildung 1: So präsentiert sich das Minimal-Wiki. Es lässt sich durchaus benutzen, um Texte über Wikis zu erstellen. Was hinter »SlashdotNews« steckt, zeigt Abbildung 2.
Alte Weisheiten
Eine andere mögliche Erweiterung sind zum Beispiel Zitate, die einige Leute nicht mehr nur unter ihre E-Mails setzen, sondern auch schon auf die Folien ihrer Präsentationen. Warum auch nicht, wenn es die Leute am Einschlafen hindert. Dieses Feature macht ganze vier Codezeilen aus, mit ein wenig mehr Code kann man das auch effizienter machen, aber darum soll es jetzt nicht gehen. Umrahmt von einem Empy-Tag für beliebige Anweisungsfolgen – »@{…}« – sieht das aus wie in Listing 2.
Wenn das irgendwo in einer Quellseite steht und die entsprechende Datei »quotes.text« mit einem Zitat pro Zeile existiert, stehen zeitlose Weisheiten bereit, etwa: “Whenever you find yourself on the side of the majority, it’s time to pause and reflect.” (Mark Twain)
Andere übliche Erweiterungen wie Angaben zum letzten Änderungszeitpunkt einer Seite oder sogar eine Aufrufstatistik oder für das Versenden von Benachrichtigungen per E-Mail für geänderte Seiten seien hier den experimentierfreudigen Lesern überlassen, die Python-Standardbibliothek enthält alles hierfür Notwendige. Man muss sich für jedes dieser Features lediglich überlegen, ob die Erweiterung am besten direkt in den Seiten oder im Hauptprogramm selbst stehen sollte.
Wer das alles noch nicht so aufregend findet, möge einfach weiterlesen. Es geht nämlich auch noch abgefahrener. Man kann auch eine Seite namens »SlashdotNews« anlegen, die ein bisschen Code enthält, der die entsprechende RDF-Datei herunterlädt und als aktuelle Liste von Links auf die betreffenden Artikel präsentiert, ganz ähnlich wie bei der Recent-Pages-Seite.
News für Net-Junkies
Das vollständige Listing dieser Slashdot-News-Seite ist in Listing 3 zu sehen. Es bedient sich des sehr kleinen Moduls Xml2obj, das John Bair als Rezept für die einfache Umwandlung von XML in Python-Objekte geschrieben und in der Sammlung von Activestate deponiert hat[9]. Wie die Seite aussieht, die dann generiert wird (falls gerade eine Verbindung zum Internet besteht), ist in Abbildung 2 zu sehen.
So viel Flexibilität ist manchmal zu viel des Guten, insbesondere dann, wenn auch andere darüber verfügen, denen man nicht unbedingt trauen kann oder sollte, jedenfalls nicht auf dem eigenen Rechner. Da »HeyHeyWickie« auf jeder Seite beliebigen Quellcode ausführen kann, sollte sich niemand über die möglichen Konsequenzen wundern, wenn es einfach so im Internet erscheint. Es stellt sich also das übliche Sandkastenproblem: Wie kann man eine halbwegs sichere Umgebung bauen, in der ein Werkzeug nur mit eingeschränkten Möglichkeiten benutzbar ist?
Aber sicher?
Die einfachste Lösung besteht natürlich darin, das Programm über eine Authentifizierung (zum Beispiel mittels Apache) nur einer bekannten Gruppe von Benutzern zur Verfügung zu stellen. Aber auch hier sind die Fähigkeiten der einzelnen Mitglieder entscheidend, mit ihren Zugangsdaten richtig umzugehen. Einzelne Benutzer könnten immer noch aus Versehen Dateien löschen. Und in vielen Fällen macht eine Zwangsregistrierung für die Anwendung oder die Benutzer keinen Sinn.
Was kann also passieren? Zum einen kann bösartiger Code ausgeführt werden, der entweder Dateien einfach löscht oder virenartige Dateien installiert. Oder es können Dateien nur ausgespäht und weiterleitet werden. Diese Schreib- und Lesezugriffe lassen sich noch relativ leicht verhindern, indem ein Programm wie »HeyHeyWickie« ausschließlich unter einem bestimmten Benutzeraccount läuft, der nur in ganz bestimmten Verzeichnissen entsprechende Rechte hat.
Für solche künstlichen Beschränkungen im Python-Interpreter gab es eine ganze Weile die Module Bastion und Rexec (Restricted Execution), die aber ab Python 2.3 nicht mehr enthalten sind. Sie wurden für zu unsicher gehalten. Gegenwärtig gibt es deshalb keine Möglichkeit, um beliebigen, nicht vertrauenswürdigen Python-Code mit dem Standardinterpreter in einer Art Sandkasten sicher auszuführen.
Kein Sandkasten, nirgends
Besonders abenteuerlustige Naturen kommen jetzt sicher auf die Idee, eine eigene Interpreterversion in einem solchen Kontext einzusetzen, in der bestimmte Module ganz nach Bedarf eingeschränkt werden können. In der Tat, das könnte man machen. Aber auch dann gibt es noch eine weitere Quelle von Problemen, etwa solche mit Endlosschleifen oder anderen Ressourcen verbrauchenden Prozessen, die von innen nur schwer zu erkennen sind. In solchen Fällen ist es wohl sinnvoller, statt des Interpreters das Betriebsystem zu bemühen, dessen Aufgabe es schließlich auch ist, die Ressourcen eines Prozesses wie Rechenzeit und Hauptspeicherbedarf zu verwalten.
Wickie, wohin?
Was als kleine Spielerei begann, hat sich unerwartet schnell als interessantes Experiment in minimalistischer Wikiwiki-Programmierung erwiesen. Durch die geschickte Kombination der Hauptstärken von Python als Allround-Programmiersprache und von Empy und Docutils als Template-Prozessoren für verschiedene Aufgaben entsteht ein System, das ein sehr flexibles Verhalten erkennen lässt. Mit extrem wenig Code kann man damit sehr einfach nachvollziehen, wie ein solches System prinzipiell funktioniert, um es anschließend auf leichte Art und Weise zu erweitern.
Die hohe Flexibilität bewirkt allerdings auch Risiken bei einem uneingeschränkten Einsatz in Anwendungen, die ohne weitere Absicherungen direkt vom Internet aus zugänglich sind. Neben Sicherheitsaspekten stellen sich prinzipiell zusätzlich Fragen bezüglich Skalierbarkeit und Effizienz, die aber hier bewusst ausgeklammert wurden. Fragt sich also, in welchen Bereichen man »HeyHeyWickie«, so flexibel wie es ist, überhaupt vernünftig einsetzen kann?
Neben seiner prinzipiellen Funktion als Experimentierplattform für Wikiwiki-Systeme und den Empy-Prozessor mag jeder seine eigenen Ideen damit verwirklichen. Praktisch für die Portierbarkeit ist dabei, dass das Format Restructured Text sich langsam als Quasistandard durchzusetzen scheint – auch in anderen Wiki-Systemen wie etwa ZWiki[10]. Neben der HTML-Ausgabe existieren auch Konverter nach Latex und PDF. Unter anderem auch wegen der Sicherheit kann die mittlerweile erweiterte Version von »HeyHeyWickie«[2] einen statischen Snapshot der gesamten Wikiwiki-Seiten als HTML-Dateien abspeichern, die so bedenkenlos ins Internet gestellt werden können.
Wer mag, kann diese erweiterte Variante als ein persönliches, leicht gewichtiges Content-Management-System einsetzen, zum Beispiel in Form eines URL-Managers oder als Zettelkasten und Schnittstelle zu entfernten Dienstleistungen im Internet, wie am Slashdot-Beispiel gezeigt wurde. (uwo)
|
Listing 2: Codeschnipsel für Zitat |
01 @{
02 import random, wickie4K
03 path = wickie4K.WIKI_DIR + "quotes.text"
04 quotes = open(path).read().split('n')
05 print "*%s*" % random.choice(quotes)
06 }
|
|
Listing 3: Code für »SlashdotNews« |
01 @{
02 import time, urllib, xml2obj
03
04 def printHeadlineLinks(root):
05 for story in root.children:
06 for c in story.children:
07 name = c.name
08 if name == "title":
09 title = c.getData()
10 elif name == "url":
11 url = c.getData()
12 print "- `%s <%s>`__ n" % (title, url)
13
14 try:
15 url = "http:slashdot.org/slashdot.xml"
16 xmlData = urllib.urlopen(url).read()
17 parser = xml2obj.Xml2Obj()
18 root = parser.ParseString(xmlData)
19 except IOError:
20 root = None
21 }
22
23 @[if root]
24 Below you can see the list of the very current Slashdot news
25 (as of @time.ctime()).
26 (Don't reload this too often or Slashdot might temporarily
27 ban your IP!)
28
29 @printHeadlineLinks(root)
30 @[else]
31 Sorry, but there seems to be no Internet connectivity!
32 @[end if]
33
|
|
Listing 4: »wickie4K.py« |
001 #!/usr/bin/env python
002
003 from os.path import join, basename, dirname,
004 splitext, exists
005 import glob, sys, re
006 import cgi, cgitb
007
008 from docutils.core import publish_string
009 import em
010
011
012 __version__ = '0.1.0'
013 __license__ = 'GPL'
014 __author__ = 'Dinu Gherman'
015
016 PROG = basename(sys.argv[0])
017 WIKI_DIR = "/Library/WebServer/Documents/wickiedata/"
018 WIKI_EXT = ".txt"
019 PAGES = {}
020
021
022 # HTML snippets (should go into some styles module)
023
024 HEADER = """
025 <html>
026 <head>
027 <title>%%(name)s</title>
028 </head>
029 <body>
030 <img src="%(image)s">
031 <h1>%%(name)s</h1>
032 """ % {'image': 'file://'+join(WIKI_DIR, "wickie.jpg")}
033
034 EDIT_THIS = 'n<a href="%(PROG)s?edit=%(name)s">Edit</a>n'
035
036 BUTTONS = """
037 <a href="%(PROG)s?show=RecentChanges">RecentChanges</a>
038 <a href="%(PROG)s?show=FrontPage">FrontPage</a>
039 """
040
041 EDIT_AREA = """
042 <form action="%(PROG)s" method="post">
043 <textarea name="area" cols="80" rows="20"
044 wrap="virtual" style="width:100%%">
045 %(content)s
046 </textarea>
047
048 <input type="hidden" name="save" value="%(name)s">
049 <input type="submit" value="Save">
050 </form>
051 """
052
053 FOOTER = "n</body>n</html>n"
054
055 NEW_PAGE = "Please edit some content for this new page
056 in order to save it!"
057
058
059 def getWikiPaths():
060 pat = join(WIKI_DIR, "*" + WIKI_EXT)
061 return glob.glob(pat)
062
063 def wikiPath2name(path):
064 base = basename(path)
065 return splitext(base)[0], exists(path)
066
067 def wikiName2path(name):
068 base = name + WIKI_EXT
069 path = join(WIKI_DIR, base)
070 return path, exists(path)
071
072 def wikiName2link(name, action="show"):
073 anchor = name
074 if action == "new": anchor = anchor + '?'
075 return '`%s <%s?%s=%s>`__' % (anchor, PROG, action, name)
076
077 def makeButtonsLine(path, edit):
078 name, ex = wikiPath2name(path)
079 d = {'name': name, 'PROG': PROG}
080 s = "n<p>n"
081 if not edit: s = s + EDIT_THIS % d
082 s = s + BUTTONS % d + "</p>n"
083 return s
084
085 def loadPage(path):
086 name, ex = wikiPath2name(path)
087 if ex: return open(path).read()
088 return NEW_PAGE
089
090 def empyfy(text):
091 wikiNames = re.findall("@[a-zA-z0-9_]+", text)
092 wikiNames = map(lambda name:name[1:], wikiNames)
093 wikiNames = filter(lambda name:
094 name not in PAGES.keys(), wikiNames)
095 for name in wikiNames:
096 PAGES[name] = wikiName2link(name, action="new")
097 return em.expand(text, PAGES)
098
099 def reSTify(content):
100 settings = {'input_encoding': 'latin-1',
101 'output_encoding': 'latin-1'}
102 rest = publish_string(content, writer_name='html',
103 settings_overrides=settings)
104 start = rest.find("<body>")
105 p = rest[start+len("<body>"):].find(">")
106 snip = rest[start+len("<body>")+p+2:-23] # magic
107 snip = snip.replace("@", "@") # hack
108 return snip
109
110 def addPageNames():
111 for path in getWikiPaths():
112 name, ex = wikiPath2name(path)
113 if ex:
114 link = wikiName2link(name)
115 PAGES[name] = link
116
117 def outputHTML(name, path, edit, html, textarea=""):
118 out = sys.stdout.write
119 out("Content-type: text/htmlnn")
120 out(HEADER % {'name': name})
121 out(makeButtonsLine(path, edit))
122 out(html)
123 if textarea: out(textarea)
124 out(makeButtonsLine(path, edit))
125 out(FOOTER)
126
127 def mainCGI():
128 cgitb.enable()
129
130 query = cgi.FieldStorage()
131 qgetval = lambda n: query.getvalue(n, '')
132 show, edit, save, new =
133 map(qgetval, "show edit save new".split())
134 name = show or edit or save or new or "FrontPage"
135 path, ex = wikiName2path(name)
136 textarea = ''
137
138 page = loadPage(path)
139
140 if edit:
141 args = {'PROG': PROG,
142 'content': loadPage(path),
143 'name': wikiPath2name(path)[0]}
144 textarea = EDIT_AREA % args
145 elif save:
146 area = qgetval('area')
147 open(path, 'w').write(area)
148
149 addPageNames()
150 mpPage = empyfy(page)
151 html = reSTify(mpPage)
152 outputHTML(name, path, edit, html, textarea)
153
154 if __name__ == '__main__':
155 mainCGI()
|
|
Infos: |
|
[1] Wikipedia: [http://www.wikipedia.org ] [2] HeyHeyWickie: [http://python.net/~gherman/#heyheywickie] [3] Empy: [www.alcyone.com/pyos/empy] [4] Python: [http://www.python.org] [5] Empy-Präsentation: [http://europython.org/Talks/Slides/empy.pdf] [6] Docutils: [http://docutils.sourceforge.net] [7] Docutils-Tutorial:[http://www.ocf.berkeley.edu/~bac/rest_tutorial.html] [8] Rest-Spezifikation: [http://docutils.sourceforge.net/spec/rst/ [9] Xml2obj:[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/149368] [10] ZWiki: [http://www.zwiki.org] [11] Listings zu diesem Artikel: [http://www.linux/magazin.de/listings/Service/Listings/2003/08/Empy-Wiki/] |
|
Der Autor |
|
Dinu Gherman ist freier IT-Entwickler, -Berater, -Autor und Übersetzer. Er hat vier Bücher zum Thema Python übersetzt. Auf seinen Python-Webseiten [2] finden sich neben Wickie noch andere interessante Python-Projekte. |







