From 331f909d09347500b3a0408416a7fd57d78a6ae5 Mon Sep 17 00:00:00 2001 From: alexhudson Date: Sun, 29 Apr 2007 16:48:45 +0000 Subject: [PATCH] Initial Hawkeye work. This contains a new template system for creating HTML, some basic admin ability, and serves as the basis for further work. You may need to bongo-config install again, if upgrading. --- src/apps/config/config.h | 28 +- src/libs/python/Bongo.rules | 17 +- src/libs/python/bongo/Template.py | 97 ++ .../bongo/external/simpletal/DummyLogger.py | 66 + .../external/simpletal/FixedHTMLParser.py | 45 + .../bongo/external/simpletal/__init__.py | 1 + .../external/simpletal/sgmlentitynames.py | 284 ++++ .../external/simpletal/simpleElementTree.py | 106 ++ .../bongo/external/simpletal/simpleTAL.py | 1514 +++++++++++++++++ .../bongo/external/simpletal/simpleTALES.py | 651 +++++++ .../external/simpletal/simpleTALUtils.py | 290 ++++ src/libs/python/bongo/store/StoreClient.py | 1 + src/www/Bongo.rules | 46 +- src/www/bongo-standalone.py | 15 +- src/www/bongo/dragonfly/Standalone.py | 13 +- src/www/bongo/hawkeye/AgentAttributes.py | 210 --- src/www/bongo/hawkeye/Auth.py | 36 +- src/www/bongo/hawkeye/BackupView.py | 64 + src/www/bongo/hawkeye/Hawkeye.py | 15 - src/www/bongo/hawkeye/HawkeyeHandler.py | 57 +- src/www/bongo/hawkeye/HawkeyePath.py | 12 +- src/www/bongo/hawkeye/RootView.py | 21 +- src/www/bongo/hawkeye/Server.py | 1 - src/www/bongo/hawkeye/ServerView.py | 86 + src/www/bongo/hawkeye/SummaryView.py | 4 +- src/www/bongo/hawkeye/UserAttributes.py | 113 -- src/www/css/admin.css | 74 + src/www/hawkeye/backup/index.tpl | 18 + .../hawkeye/{psp/footer.psp => footer.tpl} | 3 +- src/www/hawkeye/header.tpl | 22 + src/www/hawkeye/psp/agents/agents.psp | 16 - src/www/hawkeye/psp/agents/edit.psp | 57 - src/www/hawkeye/psp/agents/index.psp | 3 - src/www/hawkeye/psp/header.psp | 47 - src/www/hawkeye/psp/root/index.psp | 15 - src/www/hawkeye/psp/root/login.psp | 16 - src/www/hawkeye/psp/summary/index.psp | 20 - src/www/hawkeye/psp/users/edit.psp | 59 - src/www/hawkeye/psp/users/index.psp | 3 - src/www/hawkeye/psp/users/users.psp | 19 - src/www/hawkeye/root/index.tpl | 7 + src/www/hawkeye/root/login.tpl | 28 + src/www/hawkeye/root/test-hello.tpl | 1 + src/www/hawkeye/root/test.tpl | 63 + src/www/hawkeye/server/index.tpl | 31 + 45 files changed, 3586 insertions(+), 709 deletions(-) create mode 100644 src/libs/python/bongo/Template.py create mode 100644 src/libs/python/bongo/external/simpletal/DummyLogger.py create mode 100644 src/libs/python/bongo/external/simpletal/FixedHTMLParser.py create mode 100644 src/libs/python/bongo/external/simpletal/__init__.py create mode 100644 src/libs/python/bongo/external/simpletal/sgmlentitynames.py create mode 100644 src/libs/python/bongo/external/simpletal/simpleElementTree.py create mode 100644 src/libs/python/bongo/external/simpletal/simpleTAL.py create mode 100644 src/libs/python/bongo/external/simpletal/simpleTALES.py create mode 100644 src/libs/python/bongo/external/simpletal/simpleTALUtils.py delete mode 100644 src/www/bongo/hawkeye/AgentAttributes.py create mode 100644 src/www/bongo/hawkeye/BackupView.py delete mode 100644 src/www/bongo/hawkeye/Hawkeye.py create mode 100644 src/www/bongo/hawkeye/ServerView.py delete mode 100644 src/www/bongo/hawkeye/UserAttributes.py create mode 100644 src/www/css/admin.css create mode 100644 src/www/hawkeye/backup/index.tpl rename src/www/hawkeye/{psp/footer.psp => footer.tpl} (96%) create mode 100644 src/www/hawkeye/header.tpl delete mode 100644 src/www/hawkeye/psp/agents/agents.psp delete mode 100644 src/www/hawkeye/psp/agents/edit.psp delete mode 100644 src/www/hawkeye/psp/agents/index.psp delete mode 100644 src/www/hawkeye/psp/header.psp delete mode 100644 src/www/hawkeye/psp/root/index.psp delete mode 100644 src/www/hawkeye/psp/root/login.psp delete mode 100644 src/www/hawkeye/psp/summary/index.psp delete mode 100644 src/www/hawkeye/psp/users/edit.psp delete mode 100644 src/www/hawkeye/psp/users/index.psp delete mode 100644 src/www/hawkeye/psp/users/users.psp create mode 100644 src/www/hawkeye/root/index.tpl create mode 100644 src/www/hawkeye/root/login.tpl create mode 100644 src/www/hawkeye/root/test-hello.tpl create mode 100644 src/www/hawkeye/root/test.tpl create mode 100644 src/www/hawkeye/server/index.tpl diff --git a/src/apps/config/config.h b/src/apps/config/config.h index 6492680..7882fe1 100644 --- a/src/apps/config/config.h +++ b/src/apps/config/config.h @@ -17,20 +17,20 @@ void InitialStoreConfiguration(); */ static char *bongo_manager_config = -"{ version: 1, \n" -" agents: [ \n" -" { name: 'bongoqueue', pri: 3, enabled: true },\n" -" { name: 'bongosmtp', pri: 3, enabled: true },\n" -" { name: 'bongoantispam', pri: 5, enabled: false },\n" -" { name: 'bongoavirus', pri: 5, enabled: true, config:'/config/avirus'},\n" -" { name: 'bongocollector', pri: 7, enabled: true },\n" -" { name: 'bongomailprox', pri: 7, enabled: true },\n" -" { name: 'bongoconnmgr', pri: 2, enabled: true },\n" -" { name: 'bongopluspack', pri: 5, enabled: true },\n" -" { name: 'bongorules', pri: 2, enabled: true },\n" -" { name: 'bongoimap', pri: 10, enabled: true },\n" -" { name: 'bongopop3', pri: 10, enabled: true },\n" -" { name: 'bongocalcmd', pri: 7, enabled: true },\n" +"{ \"version\": 1, \n" +" \"agents\": [ \n" +" { \"name\": \"bongoqueue\", \"pri\": 3, \"enabled\": true },\n" +" { \"name\": \"bongosmtp\", \"pri\": 3, \"enabled\": true },\n" +" { \"name\": \"bongoantispam\", \"pri\": 5, \"enabled\": false },\n" +" { \"name\": \"bongoavirus\", \"pri\": 5, \"enabled\": true, \"config\":\"/config/avirus\"},\n" +" { \"name\": \"bongocollector\", \"pri\": 7, \"enabled\": true },\n" +" { \"name\": \"bongomailprox\", \"pri\": 7, \"enabled\": true },\n" +" { \"name\": \"bongoconnmgr\", \"pri\": 2, \"enabled\": true },\n" +" { \"name\": \"bongopluspack\", \"pri\": 5, \"enabled\": true },\n" +" { \"name\": \"bongorules\", \"pri\": 2, \"enabled\": true },\n" +" { \"name\": \"bongoimap\", \"pri\": 10, \"enabled\": true },\n" +" { \"name\": \"bongopop3\", \"pri\": 10, \"enabled\": true },\n" +" { \"name\": \"bongocalcmd\", \"pri\": 7, \"enabled\": true }\n" " ]\n" "}\n"; diff --git a/src/libs/python/Bongo.rules b/src/libs/python/Bongo.rules index 0647dec..d77f4b1 100644 --- a/src/libs/python/Bongo.rules +++ b/src/libs/python/Bongo.rules @@ -11,6 +11,7 @@ pkgpythonexternalemaildir = $(pythondir)/bongo/external/email pkgpythonexternalemailmimedir = $(pythondir)/bongo/external/email/mime pkgpythonexternaldateutilzoneinfodir = $(pythondir)/bongo/external/dateutil/zoneinfo pkgpythonexternalsimplejsondir = $(pythondir)/bongo/external/simplejson +pkgpythonexternalsimpletaldir = $(pythondir)/bongo/external/simpletal pkgpythonexternalvobjectdir = $(pythondir)/bongo/external/vobject pkgpythonnmapdir = $(pythondir)/bongo/nmap pkgpythonstoredir = $(pythondir)/bongo/store @@ -19,10 +20,11 @@ pkgpython_PYTHON := \ src/libs/python/bongo/__init__.py \ src/libs/python/bongo/CalCmd.py \ src/libs/python/bongo/cmdparse.py \ - src/libs/python/bongo/Console.py \ - src/libs/python/bongo/Contact.py \ + src/libs/python/bongo/Console.py \ + src/libs/python/bongo/Contact.py \ src/libs/python/bongo/BongoError.py \ src/libs/python/bongo/MDB.py \ + src/libs/python/bongo/Template.py \ src/libs/python/bongo/Privs.py \ src/libs/python/bongo/StreamIO.py \ src/libs/python/bongo/table.py @@ -110,6 +112,17 @@ pkgpythonexternalsimplejson_PYTHON := \ src/libs/python/bongo/external/simplejson/jsonfilter.py \ src/libs/python/bongo/external/simplejson/scanner.py +pkgpythonexternalsimpletal_PYTHON := \ + src/libs/python/bongo/external/simpletal/DummyLogger.py \ + src/libs/python/bongo/external/simpletal/__init__.py \ + src/libs/python/bongo/external/simpletal/simpleElementTree.py \ + src/libs/python/bongo/external/simpletal/simpleTAL.py \ + src/libs/python/bongo/external/simpletal/FixedHTMLParser.py \ + src/libs/python/bongo/external/simpletal/sgmlentitynames.py \ + src/libs/python/bongo/external/simpletal/simpleTALES.py \ + src/libs/python/bongo/external/simpletal/simpleTALUtils.py + + pkgpythonexternalvobject_PYTHON := \ src/libs/python/bongo/external/vobject/__init__.py \ src/libs/python/bongo/external/vobject/base.py \ diff --git a/src/libs/python/bongo/Template.py b/src/libs/python/bongo/Template.py new file mode 100644 index 0000000..bd0e118 --- /dev/null +++ b/src/libs/python/bongo/Template.py @@ -0,0 +1,97 @@ +import os, sys, copy +from bongo.external.simpletal import simpleTAL, simpleTALES + +class Template: + def __init__(self, depth=0): + self._context = simpleTALES.Context() + self._depth = depth + self._templatefile = None + self._preprocessor = TemplatePreprocessor() + + def Run(self, req): + if self._templatefile == None: + raise RuntimeError + + self._preprocessor.setTemplateFile(self._templatefile) + template = simpleTAL.compileHTMLTemplate(self._preprocessor) + template.expand(self._context, req) + + def setVariable(self, name, obj): + self._context.addGlobal(name, obj) + self._preprocessor[name] = obj + + def setTemplateFile(self, filename): + self._templatefile = filename + + def setTemplatePath(self, path): + self._templatepath = path + self._preprocessor.setTemplatePath(path) + + def setTemplateUriRoot(self, root): + self._templateuriroot = root + self._preprocessor.setUriRoot(root) + +class TemplatePreprocessor: + def __init__(self, dict={}): + self.dict = dict + + def read(self): + return "%s" % self + + def setTemplateFile(self, filename): + content = self._filecontent(filename) + if content != "": + self._template = content + else: + self._template = "

Error

No template %s

" % filename + + def __str__(self): + # rely on string substitutions + return self._template % self + + def __getitem__(self, key): + return self._process(key.split("|")) + + def __setitem__(self, key, value): + self.dict[key] = value + + def _process(self, args): + arg = args.pop(0) + if len(args) == 0: + if arg in self.dict: + return self.dict[arg] + elif hasattr(self, "call_" + arg) and callable(getattr(self, "call_" + arg)): + return getattr(self, "call_" + arg)() + else: + return "Bad attribute %s" % arg + else: + return getattr(self, "call_" + arg)(args) + + def _filecontent(self, filename): + content = "" + try: + f = open (filename, "r") + content = f.read() + f.close() + except: + print "Couldn't open file %s" % filename + return content + + def setTemplatePath(self, path): + self._templatepath = path + + def setUriRoot(self, root): + self._uriroot = root + + # template callable functions + def call_url(self, args): + # Pad out URLs with our web root, so the app can be moved in terms of + # URL and the links all still work + return self._uriroot + args[0] + + def call_include(self, args): + # Include a copy of another template here. This is also processed. + template = self._templatepath + '/' + args[0] + pp = copy.copy(self) + pp.setTemplateFile(template) + return pp.read() diff --git a/src/libs/python/bongo/external/simpletal/DummyLogger.py b/src/libs/python/bongo/external/simpletal/DummyLogger.py new file mode 100644 index 0000000..b119750 --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/DummyLogger.py @@ -0,0 +1,66 @@ +""" simpleTALES Implementation + + Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + Dummy logging module, used when logging (http://www.red-dove.com/python_logging.html) + is not installed. +""" + +class DummyLogger: + def debug (self, *args): + pass + + def info (self, *args): + pass + + def warn (self, *args): + pass + + def error (self, *args): + pass + + def critical (self, *args): + pass + +def getLogger (*params): + return DummyLogger() + +def debug (*args): + pass + +def info (*args): + pass + +def warn (*args): + pass + +def error (*args): + pass + +def critical (*args): + pass diff --git a/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py b/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py new file mode 100644 index 0000000..edb2c1a --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/FixedHTMLParser.py @@ -0,0 +1,45 @@ +""" Fixed up HTMLParser + + Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + + The classes in this module implement the TAL language, expanding + both XML and HTML templates. + + Module Dependencies: logging, simpleTALES, simpleTALTemplates +""" + +import bongo.external.simpletal +import HTMLParser + + +class HTMLParser (HTMLParser.HTMLParser): + def unescape(self, s): + # Just return the data - we don't partially unescaped data! + return s + diff --git a/src/libs/python/bongo/external/simpletal/__init__.py b/src/libs/python/bongo/external/simpletal/__init__.py new file mode 100644 index 0000000..e5775fd --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/__init__.py @@ -0,0 +1 @@ +__version__ = "4.1" diff --git a/src/libs/python/bongo/external/simpletal/sgmlentitynames.py b/src/libs/python/bongo/external/simpletal/sgmlentitynames.py new file mode 100644 index 0000000..7bb051a --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/sgmlentitynames.py @@ -0,0 +1,284 @@ +""" simpleTAL Implementation + + Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + A dictionary of HTML entity names to their decimal value. +""" + +htmlNameToUnicodeNumber = {"nbsp": 160 + ,"iexcl": 161 + ,"cent": 162 + ,"pound": 163 + ,"curren": 164 + ,"yen": 165 + ,"brvbar": 166 + ,"sect": 167 + ,"uml": 168 + ,"copy": 169 + ,"ordf": 170 + ,"laquo": 171 + ,"not": 172 + ,"shy": 173 + ,"reg": 174 + ,"macr": 175 + ,"deg": 176 + ,"plusmn": 177 + ,"sup2": 178 + ,"sup3": 179 + ,"acute": 180 + ,"micro": 181 + ,"para": 182 + ,"middot": 183 + ,"cedil": 184 + ,"sup1": 185 + ,"ordm": 186 + ,"raquo": 187 + ,"frac14": 188 + ,"frac12": 189 + ,"frac34": 190 + ,"iquest": 191 + ,"Agrave": 192 + ,"Aacute": 193 + ,"Acirc": 194 + ,"Atilde": 195 + ,"Auml": 196 + ,"Aring": 197 + ,"AElig": 198 + ,"Ccedil": 199 + ,"Egrave": 200 + ,"Eacute": 201 + ,"Ecirc": 202 + ,"Euml": 203 + ,"Igrave": 204 + ,"Iacute": 205 + ,"Icirc": 206 + ,"Iuml": 207 + ,"ETH": 208 + ,"Ntilde": 209 + ,"Ograve": 210 + ,"Oacute": 211 + ,"Ocirc": 212 + ,"Otilde": 213 + ,"Ouml": 214 + ,"times": 215 + ,"Oslash": 216 + ,"Ugrave": 217 + ,"Uacute": 218 + ,"Ucirc": 219 + ,"Uuml": 220 + ,"Yacute": 221 + ,"THORN": 222 + ,"szlig": 223 + ,"agrave": 224 + ,"aacute": 225 + ,"acirc": 226 + ,"atilde": 227 + ,"auml": 228 + ,"aring": 229 + ,"aelig": 230 + ,"ccedil": 231 + ,"egrave": 232 + ,"eacute": 233 + ,"ecirc": 234 + ,"euml": 235 + ,"igrave": 236 + ,"iacute": 237 + ,"icirc": 238 + ,"iuml": 239 + ,"eth": 240 + ,"ntilde": 241 + ,"ograve": 242 + ,"oacute": 243 + ,"ocirc": 244 + ,"otilde": 245 + ,"ouml": 246 + ,"divide": 247 + ,"oslash": 248 + ,"ugrave": 249 + ,"uacute": 250 + ,"ucirc": 251 + ,"uuml": 252 + ,"yacute": 253 + ,"thorn": 254 + ,"yuml": 255 + ,"fnof": 402 + ,"Alpha": 913 + ,"Beta": 914 + ,"Gamma": 915 + ,"Delta": 916 + ,"Epsilon": 917 + ,"Zeta": 918 + ,"Eta": 919 + ,"Theta": 920 + ,"Iota": 921 + ,"Kappa": 922 + ,"Lambda": 923 + ,"Mu": 924 + ,"Nu": 925 + ,"Xi": 926 + ,"Omicron": 927 + ,"Pi": 928 + ,"Rho": 929 + ,"Sigma": 931 + ,"Tau": 932 + ,"Upsilon": 933 + ,"Phi": 934 + ,"Chi": 935 + ,"Psi": 936 + ,"Omega": 937 + ,"alpha": 945 + ,"beta": 946 + ,"gamma": 947 + ,"delta": 948 + ,"epsilon": 949 + ,"zeta": 950 + ,"eta": 951 + ,"theta": 952 + ,"iota": 953 + ,"kappa": 954 + ,"lambda": 955 + ,"mu": 956 + ,"nu": 957 + ,"xi": 958 + ,"omicron": 959 + ,"pi": 960 + ,"rho": 961 + ,"sigmaf": 962 + ,"sigma": 963 + ,"tau": 964 + ,"upsilon": 965 + ,"phi": 966 + ,"chi": 967 + ,"psi": 968 + ,"omega": 969 + ,"thetasym": 977 + ,"upsih": 978 + ,"piv": 982 + ,"bull": 8226 + ,"hellip": 8230 + ,"prime": 8242 + ,"Prime": 8243 + ,"oline": 8254 + ,"frasl": 8260 + ,"weierp": 8472 + ,"image": 8465 + ,"real": 8476 + ,"trade": 8482 + ,"alefsym": 8501 + ,"larr": 8592 + ,"uarr": 8593 + ,"rarr": 8594 + ,"darr": 8595 + ,"harr": 8596 + ,"crarr": 8629 + ,"lArr": 8656 + ,"uArr": 8657 + ,"rArr": 8658 + ,"dArr": 8659 + ,"hArr": 8660 + ,"forall": 8704 + ,"part": 8706 + ,"exist": 8707 + ,"empty": 8709 + ,"nabla": 8711 + ,"isin": 8712 + ,"notin": 8713 + ,"ni": 8715 + ,"prod": 8719 + ,"sum": 8721 + ,"minus": 8722 + ,"lowast": 8727 + ,"radic": 8730 + ,"prop": 8733 + ,"infin": 8734 + ,"ang": 8736 + ,"and": 8743 + ,"or": 8744 + ,"cap": 8745 + ,"cup": 8746 + ,"int": 8747 + ,"there4": 8756 + ,"sim": 8764 + ,"cong": 8773 + ,"asymp": 8776 + ,"ne": 8800 + ,"equiv": 8801 + ,"le": 8804 + ,"ge": 8805 + ,"sub": 8834 + ,"sup": 8835 + ,"nsub": 8836 + ,"sube": 8838 + ,"supe": 8839 + ,"oplus": 8853 + ,"otimes": 8855 + ,"perp": 8869 + ,"sdot": 8901 + ,"lceil": 8968 + ,"rceil": 8969 + ,"lfloor": 8970 + ,"rfloor": 8971 + ,"lang": 9001 + ,"rang": 9002 + ,"loz": 9674 + ,"spades": 9824 + ,"clubs": 9827 + ,"hearts": 9829 + ,"diams": 9830 + ,"quot": 34 + ,"amp": 38 + ,"lt": 60 + ,"gt": 62 + ,"OElig": 338 + ,"oelig": 339 + ,"Scaron": 352 + ,"scaron": 353 + ,"Yuml": 376 + ,"circ": 710 + ,"tilde": 732 + ,"ensp": 8194 + ,"emsp": 8195 + ,"thinsp": 8201 + ,"zwnj": 8204 + ,"zwj": 8205 + ,"lrm": 8206 + ,"rlm": 8207 + ,"ndash": 8211 + ,"mdash": 8212 + ,"lsquo": 8216 + ,"rsquo": 8217 + ,"sbquo": 8218 + ,"ldquo": 8220 + ,"rdquo": 8221 + ,"bdquo": 8222 + ,"dagger": 8224 + ,"Dagger": 8225 + ,"permil": 8240 + ,"lsaquo": 8249 + ,"rsaquo": 8250 + ,"euro": 8364} diff --git a/src/libs/python/bongo/external/simpletal/simpleElementTree.py b/src/libs/python/bongo/external/simpletal/simpleElementTree.py new file mode 100644 index 0000000..f60de3c --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/simpleElementTree.py @@ -0,0 +1,106 @@ +""" ElementTree integration for SimpleTAL + + Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + The parseFile function in this module will return a Element object that + implements simpleTALES.ContextVariable and makes XML documents available + with the following path logic: + - Accessing Element directly returns the Element.text value + - Accessing Element/find and Element/findall passes the text + (up to attribute accessor) to the corresponding Element function + - Accessing the Element@name access the attribute "name" + - Accessing Element/anotherElement is a short-cut for + Element/find/anotherElement + + Module Dependencies: simpleTALES, elementtree +""" + +from bongo.external.elementtree import ElementTree +import bongo.external.simpleTALES + +class SimpleElementTreeVar (ElementTree._ElementInterface, simpleTALES.ContextVariable): + def __init__(self, tag, attrib): + ElementTree._ElementInterface.__init__(self, tag, attrib) + simpleTALES.ContextVariable.__init__(self) + + def value (self, pathInfo = None): + if (pathInfo is not None): + pathIndex, paths = pathInfo + ourParams = paths[pathIndex:] + attributeName = None + if (len (ourParams) > 0): + # Look for attribute index + if (ourParams[-1].startswith ('@')): + # Attribute lookup + attributeName = ourParams [-1][1:] + ourParams = ourParams [:-1] + # Do we do a find? + activeElement = self + if len (ourParams) > 0: + # Look for a find or findall first + if (ourParams [0] == 'find'): + # Find the element if possible + activeElement = self.find ("/".join (ourParams [1:])) + elif (ourParams [0] == 'findall'): + # Short cut this + raise simpleTALES.ContextVariable (self.findall ("/".join (ourParams[1:]))) + else: + # Assume that we wanted to use find + activeElement = self.find ("/".join (ourParams)) + # Did we find an element and are we looking for an attribute? + if (attributeName is not None and activeElement is not None): + attrValue = activeElement.attrib.get (attributeName, None) + raise simpleTALES.ContextVariable (attrValue) + + # Just return the element + if (activeElement is None): + # Wrap it + raise simpleTALES.ContextVariable (None) + raise activeElement + else: + return self + + def __unicode__ (self): + return self.text + + def __str__ (self): + return str (self.text) + +def parseFile (file): + treeBuilder = ElementTree.TreeBuilder (element_factory = SimpleElementTreeVar) + xmlTreeBuilder = ElementTree.XMLTreeBuilder (target=treeBuilder) + + if (not hasattr (file, 'read')): + ourFile = open (file) + xmlTreeBuilder.feed (ourFile.read()) + ourFile.close() + else: + xmlTreeBuilder.feed (file.read()) + + return xmlTreeBuilder.close() + diff --git a/src/libs/python/bongo/external/simpletal/simpleTAL.py b/src/libs/python/bongo/external/simpletal/simpleTAL.py new file mode 100644 index 0000000..0c2690d --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/simpleTAL.py @@ -0,0 +1,1514 @@ +""" simpleTAL Interpreter + + Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + + The classes in this module implement the TAL language, expanding + both XML and HTML templates. + + Module Dependencies: logging, simpleTALES, simpleTALTemplates +""" + +try: + import logging +except: + import bongo.external.simpletal.DummyLogger as logging + +import xml.sax, cgi, StringIO, codecs, re, types, copy, sys + +import bongo.external.simpletal.sgmlentitynames as sgmlentitynames +import bongo.external.simpletal as simpletal +import bongo.external.simpletal.FixedHTMLParser as FixedHTMLParser + +__version__ = simpletal.__version__ + +try: + # Is PyXML's LexicalHandler available? + from xml.sax.saxlib import LexicalHandler + use_lexical_handler = 1 +except ImportError: + use_lexical_handler = 0 + class LexicalHandler: + pass + +try: + # Is PyXML's DOM2SAX available? + import xml.dom.ext.Dom2Sax + use_dom2sax = 1 +except ImportError: + use_dom2sax = 0 + +import bongo.external.simpletal.simpleTALES as simpleTALES + +# Name-space URIs +METAL_NAME_URI="http://xml.zope.org/namespaces/metal" +TAL_NAME_URI="http://xml.zope.org/namespaces/tal" + +# All commands are of the form (opcode, args, commandList) +# The numbers are the opcodes, and also the order of priority + +# Argument: [(isLocalFlag (Y/n), variableName, variablePath),...] +TAL_DEFINE = 1 +# Argument: expression, endTagSymbol +TAL_CONDITION = 2 +# Argument: (varname, expression, endTagSymbol) +TAL_REPEAT = 3 +# Argument: (replaceFlag, type, expression) +TAL_CONTENT = 4 +# Not used in byte code, only ordering. +TAL_REPLACE = 5 +# Argument: [(attributeName, expression)] +TAL_ATTRIBUTES = 6 +# Argument: expression +TAL_OMITTAG = 7 +# Argument: (originalAttributeList, currentAttributeList) +TAL_START_SCOPE = 8 +# Argument: String to output +TAL_OUTPUT = 9 +# Argument: None +TAL_STARTTAG = 10 +# Argument: Tag, omitTagFlag +TAL_ENDTAG_ENDSCOPE = 11 +# Argument: None +TAL_NOOP = 13 + +# METAL Starts here +# Argument: expression, slotParams, endTagSymbol +METAL_USE_MACRO = 14 +# Argument: macroName, endTagSymbol +METAL_DEFINE_SLOT=15 +# Only used for parsing +METAL_FILL_SLOT=16 +METAL_DEFINE_MACRO=17 + +METAL_NAME_REGEX = re.compile ("[a-zA-Z_][a-zA-Z0-9_]*") +SINGLETON_XML_REGEX = re.compile ('^<[^\s/>]+(?:\s*[^=>]+="[^">]+")*\s*/>') +ENTITY_REF_REGEX = re.compile (r'(?:&[a-zA-Z][\-\.a-zA-Z0-9]*[^\-\.a-zA-Z0-9])|(?:&#[xX]?[a-eA-E0-9]*[^0-9a-eA-E])') + +# The list of elements in HTML that can not have end tags - done as a dictionary for fast +# lookup. +HTML_FORBIDDEN_ENDTAG = {'AREA': 1, 'BASE': 1, 'BASEFONT': 1, 'BR': 1, 'COL': 1 + ,'FRAME': 1, 'HR': 1, 'IMG': 1, 'INPUT': 1, 'ISINDEX': 1 + ,'LINK': 1, 'META': 1, 'PARAM': 1} + +# List of element:attribute pairs that can use minimized form in HTML +HTML_BOOLEAN_ATTS = {'AREA:NOHREF': 1, 'IMG:ISMAP': 1, 'OBJECT:DECLARE': 1 + , 'INPUT:CHECKED': 1, 'INPUT:DISABLED': 1, 'INPUT:READONLY': 1, 'INPUT:ISMAP': 1 + , 'SELECT:MULTIPLE': 1, 'SELECT:DISABLED': 1 + , 'OPTGROUP:DISABLED': 1 + , 'OPTION:SELECTED': 1, 'OPTION:DISABLED': 1 + , 'TEXTAREA:DISABLED': 1, 'TEXTAREA:READONLY': 1 + , 'BUTTON:DISABLED': 1, 'SCRIPT:DEFER': 1} + +class TemplateInterpreter: + def __init__ (self): + self.programStack = [] + self.commandList = None + self.symbolTable = None + self.slotParameters = {} + self.commandHandler = {} + self.commandHandler [TAL_DEFINE] = self.cmdDefine + self.commandHandler [TAL_CONDITION] = self.cmdCondition + self.commandHandler [TAL_REPEAT] = self.cmdRepeat + self.commandHandler [TAL_CONTENT] = self.cmdContent + self.commandHandler [TAL_ATTRIBUTES] = self.cmdAttributes + self.commandHandler [TAL_OMITTAG] = self.cmdOmitTag + self.commandHandler [TAL_START_SCOPE] = self.cmdStartScope + self.commandHandler [TAL_OUTPUT] = self.cmdOutput + self.commandHandler [TAL_STARTTAG] = self.cmdOutputStartTag + self.commandHandler [TAL_ENDTAG_ENDSCOPE] = self.cmdEndTagEndScope + self.commandHandler [METAL_USE_MACRO] = self.cmdUseMacro + self.commandHandler [METAL_DEFINE_SLOT] = self.cmdDefineSlot + self.commandHandler [TAL_NOOP] = self.cmdNoOp + + def tagAsText (self, (tag,atts), singletonFlag=0): + """ This returns a tag as text. + """ + result = ["<"] + result.append (tag) + for attName, attValue in atts: + result.append (' ') + result.append (attName) + result.append ('="') + result.append (cgi.escape (attValue, quote=1)) + result.append ('"') + if (singletonFlag): + result.append (" />") + else: + result.append (">") + return "".join (result) + + def initialise (self, context, outputFile): + self.context = context + self.file = outputFile + + def cleanState (self): + self.scopeStack = [] + self.programCounter = 0 + self.movePCForward = None + self.movePCBack = None + self.outputTag = 1 + self.originalAttributes = {} + self.currentAttributes = [] + # Used in repeat only. + self.repeatAttributesCopy = [] + self.currentSlots = {} + self.repeatVariable = None + self.tagContent = None + # tagState flag as to whether there are any local variables to pop + self.localVarsDefined = 0 + # Pass in the parameters + self.currentSlots = self.slotParameters + + def popProgram (self): + vars, self.commandList, self.symbolTable = self.programStack.pop() + self.programCounter,self.scopeStack,self.slotParameters,self.currentSlots, self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = vars + + def pushProgram (self): + vars = (self.programCounter + ,self.scopeStack + ,self.slotParameters + ,self.currentSlots + ,self.movePCForward + ,self.movePCBack + ,self.outputTag + ,self.originalAttributes + ,self.currentAttributes + ,self.repeatVariable + ,self.tagContent + ,self.localVarsDefined) + self.programStack.append ((vars,self.commandList, self.symbolTable)) + + def execute (self, template): + self.cleanState() + self.commandList, self.programCounter, programLength, self.symbolTable = template.getProgram() + cmndList = self.commandList + while (self.programCounter < programLength): + cmnd = cmndList [self.programCounter] + #print "PC: %s - Executing command: %s" % (str (self.programCounter), str (cmnd)) + self.commandHandler[cmnd[0]] (cmnd[0], cmnd[1]) + + def cmdDefine (self, command, args): + """ args: [(isLocalFlag (Y/n), variableName, variablePath),...] + Define variables in either the local or global context + """ + foundLocals = 0 + for isLocal, varName, varPath in args: + result = self.context.evaluate (varPath, self.originalAttributes) + if (isLocal): + if (not foundLocals): + foundLocals = 1 + self.context.pushLocals () + self.context.setLocal (varName, result) + else: + self.context.addGlobal (varName, result) + self.localVarsDefined = foundLocals + self.programCounter += 1 + + def cmdCondition (self, command, args): + """ args: expression, endTagSymbol + Conditionally continues with execution of all content contained + by it. + """ + result = self.context.evaluate (args[0], self.originalAttributes) + #~ if (result is None or (not result)): + conditionFalse = 0 + if (result is None): + conditionFalse = 1 + else: + if (not result): conditionFalse = 1 + try: + temp = len (result) + if (temp == 0): conditionFalse = 1 + except: + # Result is not a sequence. + pass + if (conditionFalse): + # Nothing to output - evaluated to false. + self.outputTag = 0 + self.tagContent = None + self.programCounter = self.symbolTable[args[1]] + return + self.programCounter += 1 + + def cmdRepeat (self, command, args): + """ args: (varName, expression, endTagSymbol) + Repeats anything in the cmndList + """ + if (self.repeatVariable is not None): + # We are already part way through a repeat + # Restore any attributes that might have been changed. + if (self.currentAttributes != self.repeatAttributesCopy): + self.currentAttributes = copy.copy (self.repeatAttributesCopy) + self.outputTag = 1 + self.tagContent = None + self.movePCForward = None + + try: + self.repeatVariable.increment() + self.context.setLocal (args[0], self.repeatVariable.getCurrentValue()) + self.programCounter += 1 + return + except IndexError, e: + # We have finished the repeat + self.repeatVariable = None + self.context.removeRepeat (args[0]) + # The locals were pushed in context.addRepeat + self.context.popLocals() + self.movePCBack = None + # Suppress the final close tag and content + self.tagContent = None + self.outputTag = 0 + self.programCounter = self.symbolTable [args[2]] + # Restore the state of repeatAttributesCopy in case we are nested. + self.repeatAttributesCopy = self.scopeStack.pop() + return + + # The first time through this command + result = self.context.evaluate (args[1], self.originalAttributes) + if (result is not None and result == simpleTALES.DEFAULTVALUE): + # Leave everything un-touched. + self.programCounter += 1 + return + try: + # We have three options, either the result is a natural sequence, an iterator., or something that can produce an iterator. + isSequence = len (result) + if (isSequence): + # Only setup if we have a sequence with length + self.repeatVariable = simpleTALES.RepeatVariable (result) + else: + # Delete the tags and their contents + self.outputTag = 0 + self.programCounter = self.symbolTable [args[2]] + return + except: + # Not a natural sequence, can it produce an iterator? + if (hasattr (result, "__iter__") and callable (result.__iter__)): + # We can get an iterator! + self.repeatVariable = simpleTALES.IteratorRepeatVariable (result.__iter__()) + elif (hasattr (result, "next") and callable (result.next)): + # Treat as an iterator + self.repeatVariable = simpleTALES.IteratorRepeatVariable (result) + else: + # Just a plain object, let's not loop + # Delete the tags and their contents + self.outputTag = 0 + self.programCounter = self.symbolTable [args[2]] + return + + try: + curValue = self.repeatVariable.getCurrentValue() + except IndexError, e: + # The iterator ran out of values before we started - treat as an empty list + self.outputTag = 0 + self.repeatVariable = None + self.programCounter = self.symbolTable [args[2]] + return + # We really do want to repeat - so lets do it + self.movePCBack = self.programCounter + self.context.addRepeat (args[0], self.repeatVariable, curValue) + # We keep the old state of the repeatAttributesCopy for nested loops + self.scopeStack.append (self.repeatAttributesCopy) + # Keep a copy of the current attributes for this tag + self.repeatAttributesCopy = copy.copy (self.currentAttributes) + self.programCounter += 1 + + def cmdContent (self, command, args): + """ args: (replaceFlag, structureFlag, expression, endTagSymbol) + Expands content + """ + result = self.context.evaluate (args[2], self.originalAttributes) + if (result is None): + if (args[0]): + # Only output tags if this is a content not a replace + self.outputTag = 0 + # Output none of our content or the existing content, but potentially the tags + self.movePCForward = self.symbolTable [args[3]] + self.programCounter += 1 + return + elif (not result == simpleTALES.DEFAULTVALUE): + # We have content, so let's suppress the natural content and output this! + if (args[0]): + self.outputTag = 0 + self.tagContent = (args[1], result) + self.movePCForward = self.symbolTable [args[3]] + self.programCounter += 1 + return + else: + # Default, let's just run through as normal + self.programCounter += 1 + return + + def cmdAttributes (self, command, args): + """ args: [(attributeName, expression)] + Add, leave, or remove attributes from the start tag + """ + attsToRemove = {} + newAtts = [] + for attName, attExpr in args: + resultVal = self.context.evaluate (attExpr, self.originalAttributes) + if (resultVal is None): + # Remove this attribute from the current attributes + attsToRemove [attName]=1 + elif (not resultVal == simpleTALES.DEFAULTVALUE): + # We have a value - let's use it! + attsToRemove [attName]=1 + if (isinstance (resultVal, types.UnicodeType)): + escapedAttVal = resultVal + elif (isinstance (resultVal, types.StringType)): + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + escapedAttVal = unicode (resultVal, 'ascii') + else: + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + escapedAttVal = unicode (resultVal) + newAtts.append ((attName, escapedAttVal)) + # Copy over the old attributes + for oldAttName, oldAttValue in self.currentAttributes: + if (not attsToRemove.has_key (oldAttName)): + newAtts.append ((oldAttName, oldAttValue)) + self.currentAttributes = newAtts + # Evaluate all other commands + self.programCounter += 1 + + def cmdOmitTag (self, command, args): + """ args: expression + Conditionally turn off tag output + """ + result = self.context.evaluate (args, self.originalAttributes) + if (result is not None and result): + # Turn tag output off + self.outputTag = 0 + self.programCounter += 1 + + def cmdOutputStartTag (self, command, args): + # Args: tagName + tagName, singletonTag = args + if (self.outputTag): + if (self.tagContent is None and singletonTag): + self.file.write (self.tagAsText ((tagName, self.currentAttributes), 1)) + else: + self.file.write (self.tagAsText ((tagName, self.currentAttributes))) + + if (self.movePCForward is not None): + self.programCounter = self.movePCForward + return + self.programCounter += 1 + return + + def cmdEndTagEndScope (self, command, args): + # Args: tagName, omitFlag, singletonTag + if (self.tagContent is not None): + contentType, resultVal = self.tagContent + if (contentType): + if (isinstance (resultVal, Template)): + # We have another template in the context, evaluate it! + # Save our state! + self.pushProgram() + resultVal.expandInline (self.context, self.file, self) + # Restore state + self.popProgram() + # End of the macro expansion (if any) so clear the parameters + self.slotParameters = {} + else: + if (isinstance (resultVal, types.UnicodeType)): + self.file.write (resultVal) + elif (isinstance (resultVal, types.StringType)): + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + self.file.write (unicode (resultVal, 'ascii')) + else: + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + self.file.write (unicode (resultVal)) + else: + if (isinstance (resultVal, types.UnicodeType)): + self.file.write (cgi.escape (resultVal)) + elif (isinstance (resultVal, types.StringType)): + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + self.file.write (cgi.escape (unicode (resultVal, 'ascii'))) + else: + # THIS IS NOT A BUG! + # Use Unicode in the Context object if you are not using Ascii + self.file.write (cgi.escape (unicode (resultVal))) + + if (self.outputTag and not args[1]): + # Do NOT output end tag if a singleton with no content + if not (args[2] and self.tagContent is None): + self.file.write ('') + + if (self.movePCBack is not None): + self.programCounter = self.movePCBack + return + + if (self.localVarsDefined): + self.context.popLocals() + + self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = self.scopeStack.pop() + self.programCounter += 1 + + def cmdOutput (self, command, args): + self.file.write (args) + self.programCounter += 1 + + def cmdStartScope (self, command, args): + """ args: (originalAttributes, currentAttributes) + Pushes the current state onto the stack, and sets up the new state + """ + self.scopeStack.append ((self.movePCForward + ,self.movePCBack + ,self.outputTag + ,self.originalAttributes + ,self.currentAttributes + ,self.repeatVariable + ,self.tagContent + ,self.localVarsDefined)) + + self.movePCForward = None + self.movePCBack = None + self.outputTag = 1 + self.originalAttributes = args[0] + self.currentAttributes = args[1] + self.repeatVariable = None + self.tagContent = None + self.localVarsDefined = 0 + + self.programCounter += 1 + + def cmdNoOp (self, command, args): + self.programCounter += 1 + + def cmdUseMacro (self, command, args): + """ args: (macroExpression, slotParams, endTagSymbol) + Evaluates the expression, if it resolves to a SubTemplate it then places + the slotParams into currentSlots and then jumps to the end tag + """ + value = self.context.evaluate (args[0], self.originalAttributes) + if (value is None): + # Don't output anything + self.outputTag = 0 + # Output none of our content or the existing content + self.movePCForward = self.symbolTable [args[2]] + self.programCounter += 1 + return + if (not value == simpleTALES.DEFAULTVALUE and isinstance (value, SubTemplate)): + # We have a macro, so let's use it + self.outputTag = 0 + self.slotParameters = args[1] + self.tagContent = (1, value) + # NOTE: WE JUMP STRAIGHT TO THE END TAG, NO OTHER TAL/METAL COMMANDS ARE EVALUATED. + self.programCounter = self.symbolTable [args[2]] + return + else: + # Default, let's just run through as normal + self.programCounter += 1 + return + + def cmdDefineSlot (self, command, args): + """ args: (slotName, endTagSymbol) + If the slotName is filled then that is used, otherwise the original conent + is used. + """ + if (self.currentSlots.has_key (args[0])): + # This slot is filled, so replace us with that content + self.outputTag = 0 + self.tagContent = (1, self.currentSlots [args[0]]) + # Output none of our content or the existing content + # NOTE: NO FURTHER TAL/METAL COMMANDS ARE EVALUATED + self.programCounter = self.symbolTable [args[1]] + return + # Slot isn't filled, so just use our own content + self.programCounter += 1 + return + + +class HTMLTemplateInterpreter (TemplateInterpreter): + def __init__ (self, minimizeBooleanAtts = 0): + TemplateInterpreter.__init__ (self) + self.minimizeBooleanAtts = minimizeBooleanAtts + if (minimizeBooleanAtts): + # Override the tagAsText method for this instance + self.tagAsText = self.tagAsTextMinimizeAtts + + def tagAsTextMinimizeAtts (self, (tag,atts), singletonFlag=0): + """ This returns a tag as text. + """ + result = ["<"] + result.append (tag) + upperTag = tag.upper() + for attName, attValue in atts: + if (HTML_BOOLEAN_ATTS.has_key ('%s:%s' % (upperTag, attName.upper()))): + # We should output a minimised boolean value + result.append (' ') + result.append (attName) + else: + result.append (' ') + result.append (attName) + result.append ('="') + result.append (cgi.escape (attValue, quote=1)) + result.append ('"') + if (singletonFlag): + result.append (" />") + else: + result.append (">") + return "".join (result) + +class Template: + def __init__ (self, commands, macros, symbols, doctype = None): + self.commandList = commands + self.macros = macros + self.symbolTable = symbols + self.doctype = doctype + + # Setup the macros + for macro in self.macros.values(): + macro.setParentTemplate (self) + + # Setup the slots + for cmnd, arg in self.commandList: + if (cmnd == METAL_USE_MACRO): + # Set the parent of each slot + slotMap = arg[1] + for slot in slotMap.values(): + slot.setParentTemplate (self) + + def expand (self, context, outputFile, outputEncoding=None, interpreter=None): + """ This method will write to the outputFile, using the encoding specified, + the expanded version of this template. The context passed in is used to resolve + all expressions with the template. + """ + # This method must wrap outputFile if required by the encoding, and write out + # any template pre-amble (DTD, Encoding, etc) + self.expandInline (context, outputFile, interpreter) + + def expandInline (self, context, outputFile, interpreter=None): + """ Internally used when expanding a template that is part of a context.""" + if (interpreter is None): + ourInterpreter = TemplateInterpreter() + ourInterpreter.initialise (context, outputFile) + else: + ourInterpreter = interpreter + try: + ourInterpreter.execute (self) + except UnicodeError, unierror: + logging.error ("UnicodeError caused by placing a non-Unicode string in the Context object.") + raise simpleTALES.ContextContentException ("Found non-unicode string in Context!") + + def getProgram (self): + """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """ + return (self.commandList, 0, len (self.commandList), self.symbolTable) + + def __str__ (self): + result = "Commands:\n" + index = 0 + for cmd in self.commandList: + if (cmd[0] != METAL_USE_MACRO): + result = result + "\n[%s] %s" % (str (index), str (cmd)) + else: + result = result + "\n[%s] %s, (%s{" % (str (index), str (cmd[0]), str (cmd[1][0])) + for slot in cmd[1][1].keys(): + result = result + "%s: %s" % (slot, str (cmd[1][1][slot])) + result = result + "}, %s)" % str (cmd[1][2]) + index += 1 + result = result + "\n\nSymbols:\n" + for symbol in self.symbolTable.keys(): + result = result + "Symbol: " + str (symbol) + " points to: " + str (self.symbolTable[symbol]) + ", which is command: " + str (self.commandList[self.symbolTable[symbol]]) + "\n" + + result = result + "\n\nMacros:\n" + for macro in self.macros.keys(): + result = result + "Macro: " + str (macro) + " value of: " + str (self.macros[macro]) + return result + +class SubTemplate (Template): + """ A SubTemplate is part of another template, and is used for the METAL implementation. + The two uses for this class are: + 1 - metal:define-macro results in a SubTemplate that is the macro + 2 - metal:fill-slot results in a SubTemplate that is a parameter to metal:use-macro + """ + def __init__ (self, startRange, endRangeSymbol): + """ The parentTemplate is the template for which we are a sub-template. + The startRange and endRange are indexes into the parent templates command list, + and defines the range of commands that we can execute + """ + Template.__init__ (self, [], {}, {}) + self.startRange = startRange + self.endRangeSymbol = endRangeSymbol + + def setParentTemplate (self, parentTemplate): + self.parentTemplate = parentTemplate + self.commandList = parentTemplate.commandList + self.symbolTable = parentTemplate.symbolTable + + def getProgram (self): + """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """ + return (self.commandList, self.startRange, self.symbolTable[self.endRangeSymbol]+1, self.symbolTable) + + def __str__ (self): + endRange = self.symbolTable [self.endRangeSymbol] + result = "SubTemplate from %s to %s\n" % (str (self.startRange), str (endRange)) + return result + +class HTMLTemplate (Template): + """A specialised form of a template that knows how to output HTML + """ + def __init__ (self, commands, macros, symbols, doctype = None, minimizeBooleanAtts = 0): + self.minimizeBooleanAtts = minimizeBooleanAtts + Template.__init__ (self, commands, macros, symbols, doctype = None) + + def expand (self, context, outputFile, outputEncoding="ISO-8859-1",interpreter=None): + """ This method will write to the outputFile, using the encoding specified, + the expanded version of this template. The context passed in is used to resolve + all expressions with the template. + """ + # This method must wrap outputFile if required by the encoding, and write out + # any template pre-amble (DTD, Encoding, etc) + + encodingFile = codecs.lookup (outputEncoding)[3](outputFile, 'replace') + self.expandInline (context, encodingFile, interpreter) + + def expandInline (self, context, outputFile, interpreter=None): + """ Ensure we use the HTMLTemplateInterpreter""" + if (interpreter is None): + ourInterpreter = HTMLTemplateInterpreter(minimizeBooleanAtts = self.minimizeBooleanAtts) + ourInterpreter.initialise (context, outputFile) + else: + ourInterpreter = interpreter + Template.expandInline (self, context, outputFile, ourInterpreter) + +class XMLTemplate (Template): + """A specialised form of a template that knows how to output XML + """ + + def __init__ (self, commands, macros, symbols, doctype = None): + Template.__init__ (self, commands, macros, symbols) + self.doctype = doctype + + def expand (self, context, outputFile, outputEncoding="iso-8859-1", docType=None, suppressXMLDeclaration=0,interpreter=None): + """ This method will write to the outputFile, using the encoding specified, + the expanded version of this template. The context passed in is used to resolve + all expressions with the template. + """ + # This method must wrap outputFile if required by the encoding, and write out + # any template pre-amble (DTD, Encoding, etc) + + # Write out the XML prolog + encodingFile = codecs.lookup (outputEncoding)[3](outputFile, 'replace') + if (not suppressXMLDeclaration): + if (outputEncoding.lower() != "utf-8"): + encodingFile.write ('\n' % outputEncoding.lower()) + else: + encodingFile.write ('\n') + if not docType and self.doctype: + docType = self.doctype + if docType: + encodingFile.write (docType) + encodingFile.write ('\n') + self.expandInline (context, encodingFile, interpreter) + +class TemplateCompiler: + def __init__ (self): + """ Initialise a template compiler. + """ + self.commandList = [] + self.tagStack = [] + self.symbolLocationTable = {} + self.macroMap = {} + self.endTagSymbol = 1 + + self.commandHandler = {} + self.commandHandler [TAL_DEFINE] = self.compileCmdDefine + self.commandHandler [TAL_CONDITION] = self.compileCmdCondition + self.commandHandler [TAL_REPEAT] = self.compileCmdRepeat + self.commandHandler [TAL_CONTENT] = self.compileCmdContent + self.commandHandler [TAL_REPLACE] = self.compileCmdReplace + self.commandHandler [TAL_ATTRIBUTES] = self.compileCmdAttributes + self.commandHandler [TAL_OMITTAG] = self.compileCmdOmitTag + + # Metal commands + self.commandHandler [METAL_USE_MACRO] = self.compileMetalUseMacro + self.commandHandler [METAL_DEFINE_SLOT] = self.compileMetalDefineSlot + self.commandHandler [METAL_FILL_SLOT] = self.compileMetalFillSlot + self.commandHandler [METAL_DEFINE_MACRO] = self.compileMetalDefineMacro + + # Default namespaces + self.setTALPrefix ('tal') + self.tal_namespace_prefix_stack = [] + self.metal_namespace_prefix_stack = [] + self.tal_namespace_prefix_stack.append ('tal') + self.setMETALPrefix ('metal') + self.metal_namespace_prefix_stack.append ('metal') + + self.log = logging.getLogger ("simpleTAL.TemplateCompiler") + + def setTALPrefix (self, prefix): + self.tal_namespace_prefix = prefix + self.tal_namespace_omittag = '%s:omit-tag' % self.tal_namespace_prefix + self.tal_attribute_map = {} + self.tal_attribute_map ['%s:attributes'%prefix] = TAL_ATTRIBUTES + self.tal_attribute_map ['%s:content'%prefix]= TAL_CONTENT + self.tal_attribute_map ['%s:define'%prefix] = TAL_DEFINE + self.tal_attribute_map ['%s:replace'%prefix] = TAL_REPLACE + self.tal_attribute_map ['%s:omit-tag'%prefix] = TAL_OMITTAG + self.tal_attribute_map ['%s:condition'%prefix] = TAL_CONDITION + self.tal_attribute_map ['%s:repeat'%prefix] = TAL_REPEAT + + def setMETALPrefix (self, prefix): + self.metal_namespace_prefix = prefix + self.metal_attribute_map = {} + self.metal_attribute_map ['%s:define-macro'%prefix] = METAL_DEFINE_MACRO + self.metal_attribute_map ['%s:use-macro'%prefix] = METAL_USE_MACRO + self.metal_attribute_map ['%s:define-slot'%prefix] = METAL_DEFINE_SLOT + self.metal_attribute_map ['%s:fill-slot'%prefix] = METAL_FILL_SLOT + + def popTALNamespace (self): + newPrefix = self.tal_namespace_prefix_stack.pop() + self.setTALPrefix (newPrefix) + + def popMETALNamespace (self): + newPrefix = self.metal_namespace_prefix_stack.pop() + self.setMETALPrefix (newPrefix) + + def tagAsText (self, (tag,atts), singletonFlag=0): + """ This returns a tag as text. + """ + result = ["<"] + result.append (tag) + for attName, attValue in atts: + result.append (' ') + result.append (attName) + result.append ('="') + result.append (cgi.escape (attValue, quote=1)) + result.append ('"') + if (singletonFlag): + result.append (" />") + else: + result.append (">") + return "".join (result) + + def getTemplate (self): + template = Template (self.commandList, self.macroMap, self.symbolLocationTable) + return template + + def addCommand (self, command): + if (command[0] == TAL_OUTPUT and (len (self.commandList) > 0) and self.commandList[-1][0] == TAL_OUTPUT): + # We can combine output commands + self.commandList[-1] = (TAL_OUTPUT, self.commandList[-1][1] + command[1]) + else: + self.commandList.append (command) + + def addTag (self, tag, tagProperties={}): + """ Used to add a tag to the stack. Various properties can be passed in the dictionary + as being information required by the tag. + Currently supported properties are: + 'command' - The (command,args) tuple associated with this command + 'originalAtts' - The original attributes that include any metal/tal attributes + 'endTagSymbol' - The symbol associated with the end tag for this element + 'popFunctionList' - A list of functions to execute when this tag is popped + 'singletonTag' - A boolean to indicate that this is a singleton flag + """ + # Add the tag to the tagStack (list of tuples (tag, properties, useMacroLocation)) + self.log.debug ("Adding tag %s to stack" % tag[0]) + command = tagProperties.get ('command',None) + originalAtts = tagProperties.get ('originalAtts', None) + singletonTag = tagProperties.get ('singletonTag', 0) + if (command is not None): + if (command[0] == METAL_USE_MACRO): + self.tagStack.append ((tag, tagProperties, len (self.commandList)+1)) + else: + self.tagStack.append ((tag, tagProperties, None)) + else: + self.tagStack.append ((tag, tagProperties, None)) + if (command is not None): + # All tags that have a TAL attribute on them start with a 'start scope' + self.addCommand((TAL_START_SCOPE, (originalAtts, tag[1]))) + # Now we add the TAL command + self.addCommand(command) + else: + # It's just a straight output, so create an output command and append it + self.addCommand((TAL_OUTPUT, self.tagAsText (tag, singletonTag))) + + def popTag (self, tag, omitTagFlag=0): + """ omitTagFlag is used to control whether the end tag should be included in the + output or not. In HTML 4.01 there are several tags which should never have + end tags, this flag allows the template compiler to specify that these + should not be output. + """ + while (len (self.tagStack) > 0): + oldTag, tagProperties, useMacroLocation = self.tagStack.pop() + endTagSymbol = tagProperties.get ('endTagSymbol', None) + popCommandList = tagProperties.get ('popFunctionList', []) + singletonTag = tagProperties.get ('singletonTag', 0) + for func in popCommandList: + apply (func, ()) + self.log.debug ("Popped tag %s off stack" % oldTag[0]) + if (oldTag[0] == tag[0]): + # We've found the right tag, now check to see if we have any TAL commands on it + if (endTagSymbol is not None): + # We have a command (it's a TAL tag) + # Note where the end tag symbol should point (i.e. the next command) + self.symbolLocationTable [endTagSymbol] = len (self.commandList) + + # We need a "close scope and tag" command + self.addCommand((TAL_ENDTAG_ENDSCOPE, (tag[0], omitTagFlag, singletonTag))) + return + elif (omitTagFlag == 0 and singletonTag == 0): + # We are popping off an un-interesting tag, just add the close as text + self.addCommand((TAL_OUTPUT, '')) + return + else: + # We are suppressing the output of this tag, so just return + return + else: + # We have a different tag, which means something like
which never closes is in + # between us and the real tag. + + # If the tag that we did pop off has a command though it means un-balanced TAL tags! + if (endTagSymbol is not None): + # ERROR + msg = "TAL/METAL Elements must be balanced - found close tag %s expecting %s" % (tag[0], oldTag[0]) + self.log.error (msg) + raise TemplateParseException (self.tagAsText(oldTag), msg) + self.log.error ("Close tag %s found with no corresponding open tag." % tag[0]) + raise TemplateParseException ("" % tag[0], "Close tag encountered with no corresponding open tag.") + + def parseStartTag (self, tag, attributes, singletonElement=0): + # Note down the tag we are handling, it will be used for error handling during + # compilation + self.currentStartTag = (tag, attributes) + + # Look for tal/metal attributes + foundTALAtts = [] + foundMETALAtts = [] + foundCommandsArgs = {} + cleanAttributes = [] + originalAttributes = {} + tagProperties = {} + popTagFuncList = [] + TALElementNameSpace = 0 + prefixToAdd = "" + tagProperties ['singletonTag'] = singletonElement + + # Determine whether this element is in either the METAL or TAL namespace + if (tag.find (':') > 0): + # We have a namespace involved, so let's look to see if its one of ours + namespace = tag[0:tag.find (':')] + if (namespace == self.metal_namespace_prefix): + TALElementNameSpace = 1 + prefixToAdd = self.metal_namespace_prefix +":" + elif (namespace == self.tal_namespace_prefix): + TALElementNameSpace = 1 + prefixToAdd = self.tal_namespace_prefix +":" + + if (TALElementNameSpace): + # We should treat this an implicit omit-tag + foundTALAtts.append (TAL_OMITTAG) + # Will go to default, i.e. yes + foundCommandsArgs [TAL_OMITTAG] = "" + + for att, value in attributes: + originalAttributes [att] = value + if (TALElementNameSpace and not att.find (':') > 0): + # This means that the attribute name does not have a namespace, so use the prefix for this tag. + commandAttName = prefixToAdd + att + else: + commandAttName = att + self.log.debug ("Command name is now %s" % commandAttName) + if (att[0:5] == "xmlns"): + # We have a namespace declaration. + prefix = att[6:] + if (value == METAL_NAME_URI): + # It's a METAL namespace declaration + if (len (prefix) > 0): + self.metal_namespace_prefix_stack.append (self.metal_namespace_prefix) + self.setMETALPrefix (prefix) + # We want this function called when the scope ends + popTagFuncList.append (self.popMETALNamespace) + else: + # We don't allow METAL/TAL to be declared as a default + msg = "Can not use METAL name space by default, a prefix must be provided." + raise TemplateParseException (self.tagAsText (self.currentStartTag), msg) + elif (value == TAL_NAME_URI): + # TAL this time + if (len (prefix) > 0): + self.tal_namespace_prefix_stack.append (self.tal_namespace_prefix) + self.setTALPrefix (prefix) + # We want this function called when the scope ends + popTagFuncList.append (self.popTALNamespace) + else: + # We don't allow METAL/TAL to be declared as a default + msg = "Can not use TAL name space by default, a prefix must be provided." + raise TemplateParseException (self.tagAsText (self.currentStartTag), msg) + else: + # It's nothing special, just an ordinary namespace declaration + cleanAttributes.append ((att, value)) + elif (self.tal_attribute_map.has_key (commandAttName)): + # It's a TAL attribute + cmnd = self.tal_attribute_map [commandAttName] + if (cmnd == TAL_OMITTAG and TALElementNameSpace): + self.log.warn ("Supressing omit-tag command present on TAL or METAL element") + else: + foundCommandsArgs [cmnd] = value + foundTALAtts.append (cmnd) + elif (self.metal_attribute_map.has_key (commandAttName)): + # It's a METAL attribute + cmnd = self.metal_attribute_map [commandAttName] + foundCommandsArgs [cmnd] = value + foundMETALAtts.append (cmnd) + else: + cleanAttributes.append ((att, value)) + tagProperties ['popFunctionList'] = popTagFuncList + + # This might be just content + if ((len (foundTALAtts) + len (foundMETALAtts)) == 0): + # Just content, add it to the various stacks + self.addTag ((tag, cleanAttributes), tagProperties) + return + + # Create a symbol for the end of the tag - we don't know what the offset is yet + self.endTagSymbol += 1 + tagProperties ['endTagSymbol'] = self.endTagSymbol + + # Sort the METAL commands + foundMETALAtts.sort() + # Sort the tags by priority + foundTALAtts.sort() + + # We handle the METAL before the TAL + allCommands = foundMETALAtts + foundTALAtts + firstTag = 1 + for talAtt in allCommands: + # Parse and create a command for each + cmnd = self.commandHandler [talAtt](foundCommandsArgs[talAtt]) + if (cmnd is not None): + if (firstTag): + # The first one needs to add the tag + firstTag = 0 + tagProperties ['originalAtts'] = originalAttributes + tagProperties ['command'] = cmnd + self.addTag ((tag, cleanAttributes), tagProperties) + else: + # All others just append + self.addCommand(cmnd) + + if (firstTag): + tagProperties ['originalAtts'] = originalAttributes + tagProperties ['command'] = (TAL_STARTTAG, (tag, singletonElement)) + self.addTag ((tag, cleanAttributes), tagProperties) + else: + # Add the start tag command in as a child of the last TAL command + self.addCommand((TAL_STARTTAG, (tag,singletonElement))) + + def parseEndTag (self, tag): + """ Just pop the tag and related commands off the stack. """ + self.popTag ((tag,None)) + + def parseData (self, data): + # Just add it as an output + self.addCommand((TAL_OUTPUT, data)) + + def compileCmdDefine (self, argument): + # Compile a define command, resulting argument is: + # [(isLocalFlag (Y/n), variableName, variablePath),...] + # Break up the list of defines first + commandArgs = [] + # We only want to match semi-colons that are not escaped + argumentSplitter = re.compile ('(?2 elements means a local|global flag + if (len (stmtBits) > 2): + if (stmtBits[0] == 'global'): + isLocal = 0 + varName = stmtBits[1] + expression = ' '.join (stmtBits[2:]) + elif (stmtBits[0] == 'local'): + varName = stmtBits[1] + expression = ' '.join (stmtBits[2:]) + else: + # Must be a space in the expression that caused the >3 thing + varName = stmtBits[0] + expression = ' '.join (stmtBits[1:]) + else: + # Only two bits + varName = stmtBits[0] + expression = ' '.join (stmtBits[1:]) + + commandArgs.append ((isLocal, varName, expression)) + return (TAL_DEFINE, commandArgs) + + def compileCmdCondition (self, argument): + # Compile a condition command, resulting argument is: + # path, endTagSymbol + # Sanity check + if (len (argument) == 0): + # No argument passed + msg = "No argument passed! condition commands must be of the form: 'path'" + self.log.error (msg) + raise TemplateParseException (self.tagAsText (self.currentStartTag), msg) + + return (TAL_CONDITION, (argument, self.endTagSymbol)) + + def compileCmdRepeat (self, argument): + # Compile a repeat command, resulting argument is: + # (varname, expression, endTagSymbol) + attProps = argument.split (' ') + if (len (attProps) < 2): + # Error, badly formed repeat command + msg = "Badly formed repeat command '%s'. Repeat commands must be of the form: 'localVariable path'" % argument + self.log.error (msg) + raise TemplateParseException (self.tagAsText (self.currentStartTag), msg) + + varName = attProps [0] + expression = " ".join (attProps[1:]) + return (TAL_REPEAT, (varName, expression, self.endTagSymbol)) + + def compileCmdContent (self, argument, replaceFlag=0): + # Compile a content command, resulting argument is + # (replaceFlag, structureFlag, expression, endTagSymbol) + + # Sanity check + if (len (argument) == 0): + # No argument passed + msg = "No argument passed! content/replace commands must be of the form: 'path'" + self.log.error (msg) + raise TemplateParseException (self.tagAsText (self.currentStartTag), msg) + + structureFlag = 0 + attProps = argument.split (' ') + if (len(attProps) > 1): + if (attProps[0] == "structure"): + structureFlag = 1 + express = " ".join (attProps[1:]) + elif (attProps[1] == "text"): + structureFlag = 0 + express = " ".join (attProps[1:]) + else: + # It's not a type selection after all - assume it's part of the path + express = argument + else: + express = argument + return (TAL_CONTENT, (replaceFlag, structureFlag, express, self.endTagSymbol)) + + def compileCmdReplace (self, argument): + return self.compileCmdContent (argument, replaceFlag=1) + + def compileCmdAttributes (self, argument): + # Compile tal:attributes into attribute command + # Argument: [(attributeName, expression)] + + # Break up the list of attribute settings first + commandArgs = [] + # We only want to match semi-colons that are not escaped + argumentSplitter = re.compile ('(?") + else: + result.append (">") + return "".join (result) + + def handle_startendtag (self, tag, attributes): + self.handle_starttag (tag, attributes) + if not (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())): + self.handle_endtag(tag) + + def handle_starttag (self, tag, attributes): + self.log.debug ("Recieved Start Tag: " + tag + " Attributes: " + str (attributes)) + atts = [] + for att, attValue in attributes: + # We need to spot empty tal:omit-tags + if (attValue is None): + if (att == self.tal_namespace_omittag): + atts.append ((att, u"")) + else: + atts.append ((att, att)) + else: + # Expand any SGML entity references or char references + goodAttValue = [] + last = 0 + match = ENTITY_REF_REGEX.search (attValue) + while (match): + goodAttValue.append (attValue[last:match.start()]) + ref = attValue[match.start():match.end()] + if (ref.startswith ('&#')): + # A char reference + if (ref[2] in ['x', 'X']): + # Hex + refValue = int (ref[3:-1], 16) + else: + refValue = int (ref[2:-1]) + goodAttValue.append (unichr (refValue)) + else: + # A named reference. + goodAttValue.append (unichr (sgmlentitynames.htmlNameToUnicodeNumber.get (ref[1:-1], 65533))) + last = match.end() + match = ENTITY_REF_REGEX.search (attValue, last) + goodAttValue.append (attValue [last:]) + atts.append ((att, u"".join (goodAttValue))) + + if (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())): + # This should have no end tag, so we just do the start and suppress the end + self.parseStartTag (tag, atts) + self.log.debug ("End tag forbidden, generating close tag with no output.") + self.popTag ((tag, None), omitTagFlag=1) + else: + self.parseStartTag (tag, atts) + + def handle_endtag (self, tag): + self.log.debug ("Recieved End Tag: " + tag) + if (HTML_FORBIDDEN_ENDTAG.has_key (tag.upper())): + self.log.warn ("HTML 4.01 forbids end tags for the %s element" % tag) + else: + # Normal end tag + self.popTag ((tag, None)) + + def handle_data (self, data): + self.parseData (cgi.escape (data)) + + # These two methods are required so that we expand all character and entity references prior to parsing the template. + def handle_charref (self, ref): + self.log.debug ("Got Ref: %s", ref) + self.parseData (unichr (int (ref))) + + def handle_entityref (self, ref): + self.log.debug ("Got Ref: %s", ref) + # Use handle_data so that <&> are re-encoded as required. + self.handle_data( unichr (sgmlentitynames.htmlNameToUnicodeNumber.get (ref, 65533))) + + # Handle document type declarations + def handle_decl (self, data): + self.parseData (u'' % data) + + # Pass comments through un-affected. + def handle_comment (self, data): + self.parseData (u'' % data) + + def handle_pi (self, data): + self.log.debug ("Recieved processing instruction.") + self.parseData (u'' % data) + + def report_unbalanced (self, tag): + self.log.warn ("End tag %s present with no corresponding open tag.") + + def getTemplate (self): + template = HTMLTemplate (self.commandList, self.macroMap, self.symbolLocationTable, minimizeBooleanAtts = self.minimizeBooleanAtts) + return template + +class XMLTemplateCompiler (TemplateCompiler, xml.sax.handler.ContentHandler, xml.sax.handler.DTDHandler, LexicalHandler): + def __init__ (self): + TemplateCompiler.__init__ (self) + xml.sax.handler.ContentHandler.__init__ (self) + self.doctype = None + self.log = logging.getLogger ("simpleTAL.XMLTemplateCompiler") + self.singletonElement = 0 + + def parseTemplate (self, file): + self.ourParser = xml.sax.make_parser() + self.log.debug ("Setting features of parser") + try: + self.ourParser.setFeature (xml.sax.handler.feature_external_ges, 0) + except: + pass + if use_lexical_handler: + self.ourParser.setProperty(xml.sax.handler.property_lexical_handler, self) + + self.ourParser.setContentHandler (self) + self.ourParser.setDTDHandler (self) + + self.ourParser.parse (file) + + def parseDOM (self, dom): + if (not use_dom2sax): + self.log.critical ("PyXML is not available, DOM can not be parsed.") + + self.ourParser = xml.dom.ext.Dom2Sax.Dom2SaxParser() + self.log.debug ("Setting features of parser") + if use_lexical_handler: + self.ourParser.setProperty(xml.sax.handler.property_lexical_handler, self) + + self.ourParser.setContentHandler (self) + self.ourParser.setDTDHandler (self) + + self.ourParser.parse (dom) + + def startDTD(self, name, public_id, system_id): + self.log.debug ("Recieved DOCTYPE: " + name + " public_id: " + public_id + " system_id: " + system_id) + if public_id: + self.doctype = '' % (name, public_id, system_id,) + else: + self.doctype = '' % (name, system_id,) + + def startElement (self, tag, attributes): + self.log.debug ("Recieved Real Start Tag: " + tag + " Attributes: " + str (attributes)) + try: + xmlText = self.ourParser.getProperty (xml.sax.handler.property_xml_string) + if (SINGLETON_XML_REGEX.match (xmlText)): + # This is a singleton! + self.singletonElement=1 + except xml.sax.SAXException, e: + # Parser doesn't support this property + pass + # Convert attributes into a list of tuples + atts = [] + for att in attributes.getNames(): + self.log.debug ("Attribute name %s has value %s" % (att, attributes[att])) + atts.append ((att, attributes [att])) + self.parseStartTag (tag, atts, singletonElement=self.singletonElement) + + def endElement (self, tag): + self.log.debug ("Recieved Real End Tag: " + tag) + self.parseEndTag (tag) + self.singletonElement = 0 + + def characters (self, data): + #self.log.debug ("Recieved Real Data: " + data) + # Escape any data we recieve - we don't want any: <&> in there. + self.parseData (cgi.escape (data)) + + def processingInstruction (self, target, data): + self.log.debug ("Recieved processing instruction.") + self.parseData (u'' % (target, data)) + + def comment (self, data): + # This is only called if your XML parser supports the LexicalHandler interface. + self.parseData (u'' % data) + + def getTemplate (self): + template = XMLTemplate (self.commandList, self.macroMap, self.symbolLocationTable, self.doctype) + return template + +def compileHTMLTemplate (template, inputEncoding="ISO-8859-1", minimizeBooleanAtts = 0): + """ Reads the templateFile and produces a compiled template. + To use the resulting template object call: + template.expand (context, outputFile) + """ + if (isinstance (template, types.StringType) or isinstance (template, types.UnicodeType)): + # It's a string! + templateFile = StringIO.StringIO (template) + else: + templateFile = template + compiler = HTMLTemplateCompiler() + compiler.parseTemplate (templateFile, inputEncoding, minimizeBooleanAtts) + return compiler.getTemplate() + +def compileXMLTemplate (template): + """ Reads the templateFile and produces a compiled template. + To use the resulting template object call: + template.expand (context, outputFile) + """ + if (isinstance (template, types.StringType)): + # It's a string! + templateFile = StringIO.StringIO (template) + else: + templateFile = template + compiler = XMLTemplateCompiler() + compiler.parseTemplate (templateFile) + return compiler.getTemplate() + +def compileDOMTemplate (template): + """ Traverses the DOM and produces a compiled template. + To use the resulting template object call: + template.expand (context, outputFile) + """ + compiler = XMLTemplateCompiler () + compiler.parseDOM (template) + return compiler.getTemplate() + diff --git a/src/libs/python/bongo/external/simpletal/simpleTALES.py b/src/libs/python/bongo/external/simpletal/simpleTALES.py new file mode 100644 index 0000000..9a73a72 --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/simpleTALES.py @@ -0,0 +1,651 @@ +""" simpleTALES Implementation + + Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + The classes in this module implement the TALES specification, used + by the simpleTAL module. + + Module Dependencies: logging +""" + +import types, sys + +try: + import logging +except: + import DummyLogger as logging + +import bongo.external.simpletal as simpletal + +__version__ = simpletal.__version__ + +DEFAULTVALUE = "This represents a Default value." + +class PathNotFoundException (Exception): + pass + +class ContextContentException (Exception): + """ This is raised when invalid content has been placed into the Context object. + For example using non-ascii characters instead of Unicode strings. + """ + pass + +PATHNOTFOUNDEXCEPTION = PathNotFoundException() + +class ContextVariable: + def __init__ (self, value = None): + self.ourValue = value + + def value (self, currentPath=None): + if (callable (self.ourValue)): + return apply (self.ourValue, ()) + return self.ourValue + + def rawValue (self): + return self.ourValue + + def __str__ (self): + return repr (self.ourValue) + +class RepeatVariable (ContextVariable): + """ To be written""" + def __init__ (self, sequence): + ContextVariable.__init__ (self, 1) + self.sequence = sequence + self.position = 0 + self.map = None + + def value (self, currentPath=None): + if (self.map is None): + self.createMap() + return self.map + + def rawValue (self): + return self.value() + + def getCurrentValue (self): + return self.sequence [self.position] + + def increment (self): + self.position += 1 + if (self.position == len (self.sequence)): + raise IndexError ("Repeat Finished") + + def createMap (self): + self.map = {} + self.map ['index'] = self.getIndex + self.map ['number'] = self.getNumber + self.map ['even'] = self.getEven + self.map ['odd'] = self.getOdd + self.map ['start'] = self.getStart + self.map ['end'] = self.getEnd + # TODO: first and last need to be implemented. + self.map ['length'] = len (self.sequence) + self.map ['letter'] = self.getLowerLetter + self.map ['Letter'] = self.getUpperLetter + self.map ['roman'] = self.getLowerRoman + self.map ['Roman'] = self.getUpperRoman + + # Repeat implementation goes here + def getIndex (self): + return self.position + + def getNumber (self): + return self.position + 1 + + def getEven (self): + if ((self.position % 2) != 0): + return 0 + return 1 + + def getOdd (self): + if ((self.position % 2) == 0): + return 0 + return 1 + + def getStart (self): + if (self.position == 0): + return 1 + return 0 + + def getEnd (self): + if (self.position == len (self.sequence) - 1): + return 1 + return 0 + + def getLowerLetter (self): + result = "" + nextCol = self.position + if (nextCol == 0): + return 'a' + while (nextCol > 0): + nextCol, thisCol = divmod (nextCol, 26) + result = chr (ord ('a') + thisCol) + result + return result + + def getUpperLetter (self): + return self.getLowerLetter().upper() + + def getLowerRoman (self): + romanNumeralList = (('m', 1000) + ,('cm', 900) + ,('d', 500) + ,('cd', 400) + ,('c', 100) + ,('xc', 90) + ,('l', 50) + ,('xl', 40) + ,('x', 10) + ,('ix', 9) + ,('v', 5) + ,('iv', 4) + ,('i', 1) + ) + if (self.position > 3999): + # Roman numbers only supported up to 4000 + return ' ' + num = self.position + 1 + result = "" + for roman, integer in romanNumeralList: + while (num >= integer): + result += roman + num -= integer + return result + + def getUpperRoman (self): + return self.getLowerRoman().upper() + +class IteratorRepeatVariable (RepeatVariable): + def __init__ (self, sequence): + RepeatVariable.__init__ (self, sequence) + self.curValue = None + self.iterStatus = 0 + + def getCurrentValue (self): + if (self.iterStatus == 0): + self.iterStatus = 1 + try: + self.curValue = self.sequence.next() + except StopIteration, e: + self.iterStatus = 2 + raise IndexError ("Repeat Finished") + return self.curValue + + def increment (self): + # Need this for the repeat variable functions. + self.position += 1 + try: + self.curValue = self.sequence.next() + except StopIteration, e: + self.iterStatus = 2 + raise IndexError ("Repeat Finished") + + def createMap (self): + self.map = {} + self.map ['index'] = self.getIndex + self.map ['number'] = self.getNumber + self.map ['even'] = self.getEven + self.map ['odd'] = self.getOdd + self.map ['start'] = self.getStart + self.map ['end'] = self.getEnd + # TODO: first and last need to be implemented. + self.map ['length'] = sys.maxint + self.map ['letter'] = self.getLowerLetter + self.map ['Letter'] = self.getUpperLetter + self.map ['roman'] = self.getLowerRoman + self.map ['Roman'] = self.getUpperRoman + + def getEnd (self): + if (self.iterStatus == 2): + return 1 + return 0 + +class PathFunctionVariable (ContextVariable): + def __init__ (self, func): + ContextVariable.__init__ (self, value = func) + self.func = func + + def value (self, currentPath=None): + if (currentPath is not None): + index, paths = currentPath + result = ContextVariable (apply (self.func, ('/'.join (paths[index:]),))) + # Fast track the result + raise result + +class CachedFuncResult (ContextVariable): + def value (self, currentPath=None): + try: + return self.cachedValue + except: + self.cachedValue = ContextVariable.value (self) + return self.cachedValue + + def clearCache (self): + try: + del self.cachedValue + except: + pass + +class PythonPathFunctions: + def __init__ (self, context): + self.context = context + + def path (self, expr): + return self.context.evaluatePath (expr) + + def string (self, expr): + return self.context.evaluateString (expr) + + def exists (self, expr): + return self.context.evaluateExists (expr) + + def nocall (self, expr): + return self.context.evaluateNoCall (expr) + + def test (self, *arguments): + if (len (arguments) % 2): + # We have an odd number of arguments - which means the last one is a default + pairs = arguments[:-1] + defaultValue = arguments[-1] + else: + # No default - so use None + pairs = arguments + defaultValue = None + + index = 0 + while (index < len (pairs)): + test = pairs[index] + index += 1 + value = pairs[index] + index += 1 + if (test): + return value + + return defaultValue + +class Context: + def __init__ (self, options=None, allowPythonPath=0): + self.allowPythonPath = allowPythonPath + self.globals = {} + self.locals = {} + self.localStack = [] + self.repeatStack = [] + self.populateDefaultVariables (options) + self.log = logging.getLogger ("simpleTALES.Context") + self.true = 1 + self.false = 0 + self.pythonPathFuncs = PythonPathFunctions (self) + + def addRepeat (self, name, var, initialValue): + # Pop the current repeat map onto the stack + self.repeatStack.append (self.repeatMap) + self.repeatMap = self.repeatMap.copy() + self.repeatMap [name] = var + # Map this repeatMap into the global space + self.addGlobal ('repeat', self.repeatMap) + + # Add in the locals + self.pushLocals() + self.setLocal (name, initialValue) + + def removeRepeat (self, name): + # Bring the old repeat map back + self.repeatMap = self.repeatStack.pop() + # Map this repeatMap into the global space + self.addGlobal ('repeat', self.repeatMap) + + def addGlobal (self, name, value): + self.globals[name] = value + + def pushLocals (self): + # Push the current locals onto a stack so that we can safely over-ride them. + self.localStack.append (self.locals) + self.locals = self.locals.copy() + + def setLocal (self, name, value): + # Override the current local if present with the new one + self.locals [name] = value + + def popLocals (self): + self.locals = self.localStack.pop() + + def evaluate (self, expr, originalAtts = None): + # Returns a ContextVariable + #self.log.debug ("Evaluating %s" % expr) + if (originalAtts is not None): + # Call from outside + self.globals['attrs'] = originalAtts + suppressException = 1 + else: + suppressException = 0 + + # Supports path, exists, nocall, not, and string + expr = expr.strip () + try: + if expr.startswith ('path:'): + return self.evaluatePath (expr[5:].lstrip ()) + elif expr.startswith ('exists:'): + return self.evaluateExists (expr[7:].lstrip()) + elif expr.startswith ('nocall:'): + return self.evaluateNoCall (expr[7:].lstrip()) + elif expr.startswith ('not:'): + return self.evaluateNot (expr[4:].lstrip()) + elif expr.startswith ('string:'): + return self.evaluateString (expr[7:].lstrip()) + elif expr.startswith ('python:'): + return self.evaluatePython (expr[7:].lstrip()) + else: + # Not specified - so it's a path + return self.evaluatePath (expr) + except PathNotFoundException, e: + if (suppressException): + return None + raise e + + def evaluatePython (self, expr): + if (not self.allowPythonPath): + self.log.warn ("Parameter allowPythonPath is false. NOT Evaluating python expression %s" % expr) + return self.false + #self.log.debug ("Evaluating python expression %s" % expr) + + globals={} + for name, value in self.globals.items(): + if (isinstance (value, ContextVariable)): value = value.rawValue() + globals [name] = value + globals ['path'] = self.pythonPathFuncs.path + globals ['string'] = self.pythonPathFuncs.string + globals ['exists'] = self.pythonPathFuncs.exists + globals ['nocall'] = self.pythonPathFuncs.nocall + globals ['test'] = self.pythonPathFuncs.test + + locals={} + for name, value in self.locals.items(): + if (isinstance (value, ContextVariable)): value = value.rawValue() + locals [name] = value + + try: + result = eval(expr, globals, locals) + if (isinstance (result, ContextVariable)): + return result.value() + return result + except Exception, e: + # An exception occured evaluating the template, return the exception as text + self.log.warn ("Exception occurred evaluating python path, exception: " + str (e)) + return "Exception: %s" % str (e) + + def evaluatePath (self, expr): + #self.log.debug ("Evaluating path expression %s" % expr) + allPaths = expr.split ('|') + if (len (allPaths) > 1): + for path in allPaths: + # Evaluate this path + try: + return self.evaluate (path.strip ()) + except PathNotFoundException, e: + # Path didn't exist, try the next one + pass + # No paths evaluated - raise exception. + raise PATHNOTFOUNDEXCEPTION + else: + # A single path - so let's evaluate it. + # This *can* raise PathNotFoundException + return self.traversePath (allPaths[0]) + + def evaluateExists (self, expr): + #self.log.debug ("Evaluating %s to see if it exists" % expr) + allPaths = expr.split ('|') + # The first path is for us + # Return true if this first bit evaluates, otherwise test the rest + try: + result = self.traversePath (allPaths[0], canCall = 0) + return self.true + except PathNotFoundException, e: + # Look at the rest of the paths. + pass + + for path in allPaths[1:]: + # Evaluate this path + try: + pathResult = self.evaluate (path.strip ()) + # If this is part of a "exists: path1 | exists: path2" path then we need to look at the actual result. + if (pathResult): + return self.true + except PathNotFoundException, e: + pass + # If we get this far then there are *no* paths that exist. + return self.false + + def evaluateNoCall (self, expr): + #self.log.debug ("Evaluating %s using nocall" % expr) + allPaths = expr.split ('|') + # The first path is for us + try: + return self.traversePath (allPaths[0], canCall = 0) + except PathNotFoundException, e: + # Try the rest of the paths. + pass + + for path in allPaths[1:]: + # Evaluate this path + try: + return self.evaluate (path.strip ()) + except PathNotFoundException, e: + pass + # No path evaluated - raise error + raise PATHNOTFOUNDEXCEPTION + + def evaluateNot (self, expr): + #self.log.debug ("Evaluating NOT value of %s" % expr) + + # Evaluate what I was passed + try: + pathResult = self.evaluate (expr) + except PathNotFoundException, e: + # In SimpleTAL the result of "not: no/such/path" should be TRUE not FALSE. + return self.true + + if (pathResult is None): + # Value was Nothing + return self.true + if (pathResult == DEFAULTVALUE): + return self.false + try: + resultLen = len (pathResult) + if (resultLen > 0): + return self.false + else: + return self.true + except: + # Not a sequence object. + pass + if (not pathResult): + return self.true + # Everything else is true, so we return false! + return self.false + + def evaluateString (self, expr): + #self.log.debug ("Evaluating String %s" % expr) + result = "" + skipCount = 0 + for position in xrange (0,len (expr)): + if (skipCount > 0): + skipCount -= 1 + else: + if (expr[position] == '$'): + try: + if (expr[position + 1] == '$'): + # Escaped $ sign + result += '$' + skipCount = 1 + elif (expr[position + 1] == '{'): + # Looking for a path! + endPos = expr.find ('}', position + 1) + if (endPos > 0): + path = expr[position + 2:endPos] + # Evaluate the path - missing paths raise exceptions as normal. + try: + pathResult = self.evaluate (path) + except PathNotFoundException, e: + # This part of the path didn't evaluate to anything - leave blank + pathResult = u'' + if (pathResult is not None): + if (isinstance (pathResult, types.UnicodeType)): + result += pathResult + else: + # THIS IS NOT A BUG! + # Use Unicode in Context if you aren't using Ascii! + result += unicode (pathResult) + skipCount = endPos - position + else: + # It's a variable + endPos = expr.find (' ', position + 1) + if (endPos == -1): + endPos = len (expr) + path = expr [position + 1:endPos] + # Evaluate the variable - missing paths raise exceptions as normal. + try: + pathResult = self.traversePath (path) + except PathNotFoundException, e: + # This part of the path didn't evaluate to anything - leave blank + pathResult = u'' + if (pathResult is not None): + if (isinstance (pathResult, types.UnicodeType)): + result += pathResult + else: + # THIS IS NOT A BUG! + # Use Unicode in Context if you aren't using Ascii! + result += unicode (pathResult) + skipCount = endPos - position - 1 + except IndexError, e: + # Trailing $ sign - just suppress it + self.log.warn ("Trailing $ detected") + pass + else: + result += expr[position] + return result + + def traversePath (self, expr, canCall=1): + # canCall only applies to the *final* path destination, not points down the path. + # Check for and correct for trailing/leading quotes + if (expr.startswith ('"') or expr.startswith ("'")): + if (expr.endswith ('"') or expr.endswith ("'")): + expr = expr [1:-1] + else: + expr = expr [1:] + elif (expr.endswith ('"') or expr.endswith ("'")): + expr = expr [0:-1] + pathList = expr.split ('/') + + path = pathList[0] + if path.startswith ('?'): + path = path[1:] + if self.locals.has_key(path): + path = self.locals[path] + if (isinstance (path, ContextVariable)): path = path.value() + elif (callable (path)):path = apply (path, ()) + + elif self.globals.has_key(path): + path = self.globals[path] + if (isinstance (path, ContextVariable)): path = path.value() + elif (callable (path)):path = apply (path, ()) + #self.log.debug ("Dereferenced to %s" % path) + if self.locals.has_key(path): + val = self.locals[path] + elif self.globals.has_key(path): + val = self.globals[path] + else: + # If we can't find it then raise an exception + raise PATHNOTFOUNDEXCEPTION + index = 1 + for path in pathList[1:]: + #self.log.debug ("Looking for path element %s" % path) + if path.startswith ('?'): + path = path[1:] + if self.locals.has_key(path): + path = self.locals[path] + if (isinstance (path, ContextVariable)): path = path.value() + elif (callable (path)):path = apply (path, ()) + elif self.globals.has_key(path): + path = self.globals[path] + if (isinstance (path, ContextVariable)): path = path.value() + elif (callable (path)):path = apply (path, ()) + #self.log.debug ("Dereferenced to %s" % path) + try: + if (isinstance (val, ContextVariable)): temp = val.value((index,pathList)) + elif (callable (val)):temp = apply (val, ()) + else: temp = val + except ContextVariable, e: + # Fast path for those functions that return values + return e.value() + + if (hasattr (temp, path)): + val = getattr (temp, path) + else: + try: + try: + val = temp[path] + except TypeError: + val = temp[int(path)] + except: + #self.log.debug ("Not found.") + raise PATHNOTFOUNDEXCEPTION + index = index + 1 + #self.log.debug ("Found value %s" % str (val)) + if (canCall): + try: + if (isinstance (val, ContextVariable)): result = val.value((index,pathList)) + elif (callable (val)):result = apply (val, ()) + else: result = val + except ContextVariable, e: + # Fast path for those functions that return values + return e.value() + else: + if (isinstance (val, ContextVariable)): result = val.realValue + else: result = val + return result + + def __str__ (self): + return "Globals: " + str (self.globals) + "Locals: " + str (self.locals) + + def populateDefaultVariables (self, options): + vars = {} + self.repeatMap = {} + vars['nothing'] = None + vars['default'] = DEFAULTVALUE + vars['options'] = options + # To start with there are no repeats + vars['repeat'] = self.repeatMap + vars['attrs'] = None + + # Add all of these to the global context + for name in vars.keys(): + self.addGlobal (name,vars[name]) + + # Add also under CONTEXTS + self.addGlobal ('CONTEXTS', vars) + diff --git a/src/libs/python/bongo/external/simpletal/simpleTALUtils.py b/src/libs/python/bongo/external/simpletal/simpleTALUtils.py new file mode 100644 index 0000000..5295098 --- /dev/null +++ b/src/libs/python/bongo/external/simpletal/simpleTALUtils.py @@ -0,0 +1,290 @@ +""" simpleTALUtils + + Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + If you make any bug fixes or feature enhancements please let me know! + + This module is holds utilities that make using SimpleTAL easier. + Initially this is just the HTMLStructureCleaner class, used to clean + up HTML that can then be used as 'structure' content. + + Module Dependencies: None +""" + +import StringIO, os, stat, threading, sys, codecs, sgmllib, cgi, re, types +import bongo.external.simpletal, bongo.external.simpleTAL + +__version__ = simpletal.__version__ + +# This is used to check for already escaped attributes. +ESCAPED_TEXT_REGEX=re.compile (r"\&\S+?;") + +class HTMLStructureCleaner (sgmllib.SGMLParser): + """ A helper class that takes HTML content and parses it, so converting + any stray '&', '<', or '>' symbols into their respective entity references. + """ + def clean (self, content, encoding=None): + """ Takes the HTML content given, parses it, and converts stray markup. + The content can be either: + - A unicode string, in which case the encoding parameter is not required + - An ordinary string, in which case the encoding will be used + - A file-like object, in which case the encoding will be used if present + + The method returns a unicode string which is suitable for addition to a + simpleTALES.Context object. + """ + if (isinstance (content, types.StringType)): + # Not unicode, convert + converter = codecs.lookup (encoding)[1] + file = StringIO.StringIO (converter (content)[0]) + elif (isinstance (content, types.UnicodeType)): + file = StringIO.StringIO (content) + else: + # Treat it as a file type object - and convert it if we have an encoding + if (encoding is not None): + converterStream = codecs.lookup (encoding)[2] + file = converterStream (content) + else: + file = content + + self.outputFile = StringIO.StringIO (u"") + self.feed (file.read()) + self.close() + return self.outputFile.getvalue() + + def unknown_starttag (self, tag, attributes): + self.outputFile.write (tagAsText (tag, attributes)) + + def unknown_endtag (self, tag): + self.outputFile.write ('') + + def handle_data (self, data): + self.outputFile.write (cgi.escape (data)) + + def handle_charref (self, ref): + self.outputFile.write (u'&#%s;' % ref) + + def handle_entityref (self, ref): + self.outputFile.write (u'&%s;' % ref) + + +class FastStringOutput: + """ A very simple StringIO replacement that only provides write() and getvalue() + and is around 6% faster than StringIO. + """ + def __init__ (self): + self.data = [] + + def write (self, data): + self.data.append (data) + + def getvalue (self): + return "".join (self.data) + +class TemplateCache: + """ A TemplateCache is a multi-thread safe object that caches compiled templates. + This cache only works with file based templates, the ctime of the file is + checked on each hit, if the file has changed the template is re-compiled. + """ + def __init__ (self): + self.templateCache = {} + self.cacheLock = threading.Lock() + self.hits = 0 + self.misses = 0 + + def getTemplate (self, name, inputEncoding='ISO-8859-1'): + """ Name should be the path of a template file. If the path ends in 'xml' it is treated + as an XML Template, otherwise it's treated as an HTML Template. If the template file + has changed since the last cache it will be re-compiled. + + inputEncoding is only used for HTML templates, and should be the encoding that the template + is stored in. + """ + if (self.templateCache.has_key (name)): + template, oldctime = self.templateCache [name] + ctime = os.stat (name)[stat.ST_MTIME] + if (oldctime == ctime): + # Cache hit! + self.hits += 1 + return template + # Cache miss, let's cache this template + return self._cacheTemplate_ (name, inputEncoding) + + def getXMLTemplate (self, name): + """ Name should be the path of an XML template file. + """ + if (self.templateCache.has_key (name)): + template, oldctime = self.templateCache [name] + ctime = os.stat (name)[stat.ST_MTIME] + if (oldctime == ctime): + # Cache hit! + self.hits += 1 + return template + # Cache miss, let's cache this template + return self._cacheTemplate_ (name, None, xmlTemplate=1) + + def _cacheTemplate_ (self, name, inputEncoding, xmlTemplate=0): + self.cacheLock.acquire () + try: + tempFile = open (name, 'r') + if (xmlTemplate): + # We know it is XML + template = simpleTAL.compileXMLTemplate (tempFile) + else: + # We have to guess... + firstline = tempFile.readline() + tempFile.seek(0) + if (name [-3:] == "xml") or (firstline.strip ()[:5] == '" + return result + +class MacroExpansionInterpreter (simpleTAL.TemplateInterpreter): + def __init__ (self): + simpleTAL.TemplateInterpreter.__init__ (self) + # Override the standard interpreter way of doing things. + self.macroStateStack = [] + self.commandHandler [simpleTAL.TAL_DEFINE] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_CONDITION] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_REPEAT] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_CONTENT] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_ATTRIBUTES] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_OMITTAG] = self.cmdNoOp + self.commandHandler [simpleTAL.TAL_START_SCOPE] = self.cmdStartScope + self.commandHandler [simpleTAL.TAL_OUTPUT] = self.cmdOutput + self.commandHandler [simpleTAL.TAL_STARTTAG] = self.cmdOutputStartTag + self.commandHandler [simpleTAL.TAL_ENDTAG_ENDSCOPE] = self.cmdEndTagEndScope + self.commandHandler [simpleTAL.METAL_USE_MACRO] = self.cmdUseMacro + self.commandHandler [simpleTAL.METAL_DEFINE_SLOT] = self.cmdDefineSlot + self.commandHandler [simpleTAL.TAL_NOOP] = self.cmdNoOp + + self.inMacro = None + self.macroArg = None + # Original cmdOutput + # Original cmdEndTagEndScope + + def popProgram (self): + self.inMacro = self.macroStateStack.pop() + simpleTAL.TemplateInterpreter.popProgram (self) + + def pushProgram (self): + self.macroStateStack.append (self.inMacro) + simpleTAL.TemplateInterpreter.pushProgram (self) + + def cmdOutputStartTag (self, command, args): + newAtts = [] + for att, value in self.originalAttributes.items(): + if (self.macroArg is not None and att == "metal:define-macro"): + newAtts.append (("metal:use-macro",self.macroArg)) + elif (self.inMacro and att=="metal:define-slot"): + newAtts.append (("metal:fill-slot", value)) + else: + newAtts.append ((att, value)) + self.macroArg = None + self.currentAttributes = newAtts + simpleTAL.TemplateInterpreter.cmdOutputStartTag (self, command, args) + + def cmdUseMacro (self, command, args): + simpleTAL.TemplateInterpreter.cmdUseMacro (self, command, args) + if (self.tagContent is not None): + # We have a macro, add the args to the in-macro list + self.inMacro = 1 + self.macroArg = args[0] + + def cmdEndTagEndScope (self, command, args): + # Args: tagName, omitFlag + if (self.tagContent is not None): + contentType, resultVal = self.tagContent + if (contentType): + if (isinstance (resultVal, simpleTAL.Template)): + # We have another template in the context, evaluate it! + # Save our state! + self.pushProgram() + resultVal.expandInline (self.context, self.file, self) + # Restore state + self.popProgram() + # End of the macro expansion (if any) so clear the parameters + self.slotParameters = {} + # End of the macro + self.inMacro = 0 + else: + if (isinstance (resultVal, types.UnicodeType)): + self.file.write (resultVal) + elif (isinstance (resultVal, types.StringType)): + self.file.write (unicode (resultVal, 'ascii')) + else: + self.file.write (unicode (str (resultVal), 'ascii')) + else: + if (isinstance (resultVal, types.UnicodeType)): + self.file.write (cgi.escape (resultVal)) + elif (isinstance (resultVal, types.StringType)): + self.file.write (cgi.escape (unicode (resultVal, 'ascii'))) + else: + self.file.write (cgi.escape (unicode (str (resultVal), 'ascii'))) + + if (self.outputTag and not args[1]): + self.file.write ('') + + if (self.movePCBack is not None): + self.programCounter = self.movePCBack + return + + if (self.localVarsDefined): + self.context.popLocals() + + self.movePCForward,self.movePCBack,self.outputTag,self.originalAttributes,self.currentAttributes,self.repeatVariable,self.tagContent,self.localVarsDefined = self.scopeStack.pop() + self.programCounter += 1 + +def ExpandMacros (context, template, outputEncoding="ISO-8859-1"): + out = StringIO.StringIO() + interp = MacroExpansionInterpreter() + interp.initialise (context, out) + template.expand (context, out, outputEncoding=outputEncoding, interpreter=interp) + # StringIO returns unicode, so we need to turn it back into native string + result = out.getvalue() + reencoder = codecs.lookup (outputEncoding)[0] + return reencoder (result)[0] + diff --git a/src/libs/python/bongo/store/StoreClient.py b/src/libs/python/bongo/store/StoreClient.py index 30e70fa..58f1e01 100644 --- a/src/libs/python/bongo/store/StoreClient.py +++ b/src/libs/python/bongo/store/StoreClient.py @@ -16,6 +16,7 @@ class DocTypes: Addressbook = 0x0004 Conversation = 0x0005 Calendar = 0x0006 + Config = 0x0007 Folder = 0x1000 Shared = 0x2000 diff --git a/src/www/Bongo.rules b/src/www/Bongo.rules index a265469..79e7875 100644 --- a/src/www/Bongo.rules +++ b/src/www/Bongo.rules @@ -11,6 +11,7 @@ htl10n_DATA := $(htl10n_jsfiles) cssdir = $(htdocsdir)/css dist_css_DATA := \ + src/www/css/admin.css \ src/www/css/calendar.css \ src/www/css/composer.css \ src/www/css/contacts.css \ @@ -285,43 +286,36 @@ pkgpythonhawkeyedir = $(pkgpythondir)/hawkeye pkgpythonhawkeye_PYTHON := \ src/www/bongo/hawkeye/__init__.py \ - src/www/bongo/hawkeye/AgentAttributes.py \ src/www/bongo/hawkeye/AgentsView.py \ src/www/bongo/hawkeye/Auth.py \ - src/www/bongo/hawkeye/Hawkeye.py \ + src/www/bongo/hawkeye/BackupView.py \ src/www/bongo/hawkeye/HawkeyeHandler.py \ src/www/bongo/hawkeye/HawkeyePath.py \ src/www/bongo/hawkeye/RootView.py \ src/www/bongo/hawkeye/Server.py \ - src/www/bongo/hawkeye/SummaryView.py \ - src/www/bongo/hawkeye/UserAttributes.py \ + src/www/bongo/hawkeye/ServerView.py \ + src/www/bongo/hawkeye/SummaryView.py \ src/www/bongo/hawkeye/UsersView.py -hawkeyepspdir = $(pkgdatadir)/hawkeye/psp -dist_hawkeyepsp_DATA := \ - src/www/hawkeye/psp/footer.psp \ - src/www/hawkeye/psp/header.psp +hawkeyetplrootdir = $(htdocsdir)/hawkeye/root +dist_hawkeyetplroot_DATA := \ + src/www/hawkeye/root/login.tpl \ + src/www/hawkeye/root/test.tpl \ + src/www/hawkeye/root/test-hello.tpl \ + src/www/hawkeye/root/index.tpl -hawkeyeagentspspdir = $(hawkeyepspdir)/agents -dist_hawkeyeagentspsp_DATA := \ - src/www/hawkeye/psp/agents/agents.psp \ - src/www/hawkeye/psp/agents/edit.psp \ - src/www/hawkeye/psp/agents/index.psp +hawkeyetplbackupdir = $(htdocsdir)/hawkeye/backup +dist_hawkeyetplbackup_DATA := \ + src/www/hawkeye/backup/index.tpl -hawkeyeuserspspdir = $(hawkeyepspdir)/users -dist_hawkeyeuserspsp_DATA := \ - src/www/hawkeye/psp/users/users.psp \ - src/www/hawkeye/psp/users/edit.psp \ - src/www/hawkeye/psp/users/index.psp +hawkeyetplserverdir = $(htdocsdir)/hawkeye/server +dist_hawkeyetplserver_DATA := \ + src/www/hawkeye/server/index.tpl -hawkeyesummarypspdir = $(hawkeyepspdir)/summary -dist_hawkeyesummarypsp_DATA := \ - src/www/hawkeye/psp/summary/index.psp - -hawkeyerootpspdir = $(hawkeyepspdir)/root -dist_hawkeyerootpsp_DATA := \ - src/www/hawkeye/psp/root/index.psp \ - src/www/hawkeye/psp/root/login.psp +hawkeyetpldir = $(htdocsdir)/hawkeye +dist_hawkeyetpl_DATA := \ + src/www/hawkeye/footer.tpl \ + src/www/hawkeye/header.tpl src/www/bongo-standalone: $(top_builddir)/src/libs/python/bongo-python-wrapper $(INSTALL) -m 755 $< $@ diff --git a/src/www/bongo-standalone.py b/src/www/bongo-standalone.py index 5cf17e2..71d4ef6 100755 --- a/src/www/bongo-standalone.py +++ b/src/www/bongo-standalone.py @@ -363,17 +363,13 @@ class ThreadingSSLServer(ThreadingHTTPServer): self.server_bind() self.server_activate() -# Find the htdocs dir if we're running from an svn checkout. -# FIXME -#binpath = os.path.abspath(os.path.dirname(sys.argv[0])) -# -#default_file_path = binpath -#if binpath.startswith(Xpl.DEFAULT_LIBEXEC_DIR): default_file_path = os.path.abspath(Xpl.DEFAULT_DATA_DIR+"/htdocs") parser = OptionParser() parser.add_option("-d", "--debug", dest="debug", action="store_true", help="enable debugging output") +parser.add_option("-D", "--devel", dest="devel", action="store_true", + help="development mode (implies -d)") parser.add_option("", "--profile", dest="profile", action="store_true", help="enable profiling output") parser.add_option("", "--profile-memory", dest="profile_memory", action="store_true", @@ -399,7 +395,7 @@ if __name__ == '__main__': console.setFormatter(formatter) logging.root.addHandler(console) - if options.debug: + if options.debug or options.devel: logging.root.setLevel(logging.DEBUG) else: logging.root.setLevel(logging.INFO) @@ -415,6 +411,11 @@ if __name__ == '__main__': % (os.path.basename(sys.argv[0]), Xpl.BONGO_USER) sys.exit(1) + if options.devel: + default_file_path = os.path.abspath(os.path.dirname(sys.argv[0])) + HttpRequest.options["HawkeyeTmplRoot"] = "%s/%s" % (default_file_path, "hawkeye") + print "Devel: serving static files from %s" % default_file_path + if options.file_path: httppath = options.file_path diff --git a/src/www/bongo/dragonfly/Standalone.py b/src/www/bongo/dragonfly/Standalone.py index 9983ab8..2f63f6e 100644 --- a/src/www/bongo/dragonfly/Standalone.py +++ b/src/www/bongo/dragonfly/Standalone.py @@ -21,6 +21,9 @@ class HttpServer: class HttpRequest: """Make a SimpleHTTPRequestHandler look like a mod_python request""" log = logging.getLogger("Dragonfly") + options = { "DragonflyUriRoot" : "/user", + "HawkeyeTmplRoot" : None, # determine this later + "HawkeyeUriRoot" : "/admin"} def __init__(self, req, path, server=HttpServer()): self.oldreq = req @@ -56,14 +59,12 @@ class HttpRequest: binpath = os.path.abspath(os.path.dirname(sys.argv[0])) - tmplPath = os.path.abspath(os.path.join(Xpl.DEFAULT_DATA_DIR, "hawkeye", "psp")) + tmplPath = os.path.abspath(os.path.join(Xpl.DEFAULT_DATA_DIR, "htdocs/hawkeye")) + if self.options["HawkeyeTmplRoot"] == None: + self.options["HawkeyeTmplRoot"] = tmplPath if not binpath.startswith(Xpl.DEFAULT_LIBEXEC_DIR): - tmplPath = os.path.abspath(os.path.join(os.getcwd(), "hawkeye", "psp")) - - self.options = {"DragonflyUriRoot" : "/user", - "HawkeyeTmplRoot" : tmplPath, - "HawkeyeUriRoot" : "/admin"} + tmplPath = os.path.abspath(os.path.join(os.getcwd(), "hawkeye")) self.form = None reqtype = self.headers_in.get("Content-Type", None) diff --git a/src/www/bongo/hawkeye/AgentAttributes.py b/src/www/bongo/hawkeye/AgentAttributes.py deleted file mode 100644 index 4da3d2d..0000000 --- a/src/www/bongo/hawkeye/AgentAttributes.py +++ /dev/null @@ -1,210 +0,0 @@ -# a per-agent table of editable properties - -import types -import bongo.admin.Schema as Schema -import bongo.admin.Util as Util - -from bongo.bootstrap import mdb, msgapi - -class MultilineStringType(types.StringType): - pass - -class MdbPathType(types.StringType): - pass - -class Attribute: - def __init__(self, type): - self.type = type - self.name = Util.PropertyArguments.get(type) - self.prettyName = Util.PropertiesPrintable.get(type) - -class EditableAttribute(Attribute): - def Validate(self, data): - pass - -HawkeyeAttributes = { - msgapi.A_BOUNCE_CC_POSTMASTER : EditableAttribute(types.BooleanType), - msgapi.A_BOUNCE_RETURN : EditableAttribute(types.BooleanType), - msgapi.A_CLIENT : EditableAttribute(MdbPathType), - msgapi.A_DOMAIN : EditableAttribute(types.StringType), - msgapi.A_MAXIMUM_ITEMS : EditableAttribute(types.IntType), - msgapi.A_MESSAGE_LIMIT : EditableAttribute(types.IntType), - msgapi.A_MESSAGE_STORE : EditableAttribute(MdbPathType), - msgapi.A_MINIMUM_SPACE : EditableAttribute(types.IntType), - msgapi.A_MONITORED_QUEUE : EditableAttribute(MdbPathType), - msgapi.A_PORT : EditableAttribute(types.IntType), - msgapi.A_QUEUE_INTERVAL : EditableAttribute(types.IntType), - msgapi.A_QUEUE_SERVER : EditableAttribute(MdbPathType), - msgapi.A_QUEUE_TIMEOUT : EditableAttribute(types.IntType), - msgapi.A_QUOTA_MESSAGE : EditableAttribute(MultilineStringType), - msgapi.A_ROUTING : EditableAttribute(types.BooleanType), - msgapi.A_SCMS_DIRECTORY : EditableAttribute(types.StringType), - msgapi.A_SCMS_SIZE_THRESHOLD : EditableAttribute(types.IntType), - msgapi.A_SCMS_USER_THRESHOLD : EditableAttribute(types.IntType), - msgapi.A_SMTP_ACCEPT_ETRN : EditableAttribute(types.BooleanType), - msgapi.A_SMTP_SEND_ETRN : EditableAttribute(types.BooleanType), - msgapi.A_SPOOL_DIRECTORY : EditableAttribute(types.StringType), - msgapi.A_SSL_PORT : EditableAttribute(types.IntType), - msgapi.A_STORE_SERVER : EditableAttribute(MdbPathType), - msgapi.A_TIMEOUT : EditableAttribute(types.IntType), - msgapi.A_TIME_INTERVAL : EditableAttribute(types.IntType), - msgapi.A_UBE_REMOTE_AUTH_ONLY : EditableAttribute(types.BooleanType), - msgapi.A_UBE_SMTP_AFTER_POP : EditableAttribute(types.BooleanType), - } - -class HawkeyeAttributes: - extraProps = { - msgapi.C_ADDRESSBOOK : [ msgapi.A_MONITORED_QUEUE, - msgapi.A_PORT ], - - msgapi.C_ANTISPAM : [ msgapi.A_MONITORED_QUEUE, ], - - msgapi.C_CONNMGR : [ msgapi.A_TIMEOUT ], - - msgapi.C_IMAP : [ msgapi.A_PORT, - msgapi.A_SSL_PORT, - msgapi.A_MONITORED_QUEUE ], - - msgapi.C_POP : [ msgapi.A_PORT, - msgapi.A_SSL_PORT, - msgapi.A_MONITORED_QUEUE ], - - msgapi.C_PROXY : [ msgapi.A_MAXIMUM_ITEMS, - msgapi.A_TIME_INTERVAL, - msgapi.A_MONITORED_QUEUE, - msgapi.A_STORE_SERVER ], - - msgapi.C_QUEUE : [ msgapi.A_BOUNCE_RETURN, - msgapi.A_BOUNCE_CC_POSTMASTER, - msgapi.A_SPOOL_DIRECTORY, - msgapi.A_QUOTA_MESSAGE, - msgapi.A_QUEUE_TIMEOUT, - msgapi.A_QUEUE_INTERVAL, - msgapi.A_MINIMUM_SPACE, - msgapi.A_CLIENT ], - - msgapi.C_SMTP : [ msgapi.A_DOMAIN, - msgapi.A_MESSAGE_LIMIT, - msgapi.A_MONITORED_QUEUE, - msgapi.A_QUEUE_SERVER, - msgapi.A_PORT, - msgapi.A_SSL_PORT, - msgapi.A_UBE_REMOTE_AUTH_ONLY, - msgapi.A_ROUTING, - msgapi.A_SMTP_ACCEPT_ETRN, - msgapi.A_SMTP_SEND_ETRN, - msgapi.A_UBE_SMTP_AFTER_POP ], - - msgapi.C_STORE : [ msgapi.A_CLIENT, - msgapi.A_MESSAGE_STORE, - msgapi.A_MINIMUM_SPACE, - msgapi.A_SCMS_USER_THRESHOLD, - msgapi.A_SCMS_SIZE_THRESHOLD, - msgapi.A_SCMS_DIRECTORY ] - } - - def __init__(self, type): - self.editable = editable = [] - self.constant = constant = [] - - self.editable.extend(self.extraProps.get(type, [])) - - def GetConstant(self): - return self.constant - - def GetEditable(self): - return self.editable - -class AgentAttributes: - def __init__(self, mdb, dn, values=None): - self.schema = schema = Util.GetSchema() - - self.mdb = mdb - self.dn = dn - self.server, self.agent = dn.split("\\") - self.newValues = None - - if values: - self.newValues = self.CollapseValues(values) - - details = Util.GetObjectDetails(mdb, dn) - - self.agentType = agentType = details.get("Type") - - self.attrs = Util.GetAgentAttributes(mdb, dn) - self.classAttrs = Util.GetClassAttributeObjects(agentType) - self.typeAttrs = HawkeyeAttributes(agentType) - self.disabled = self.attrs.get(msgapi.A_DISABLED) - - def CollapseValues(self, values): - """Turn a FieldStorage object into a hash""" - ret = {} - - for key in values.keys(): - ret[key] = values.getlist(key) - - return ret - - def IsEnabled(self): - if len(self.disabled) and self.disabled[0]: - return False - return True - - def GetConstant(self): - attrs = [] - - constantAttrs = self.typeAttrs.GetConstant() - - for attr in constantAttrs: - if not self.attrs.has_key(attr): - continue - - id = attr - name = Util.PropertiesPrintable.get(attr, attr) - values = self.attrs.get(attr) - - if len(values) > 0: - for value in values: - attrs.append((id, name, value)) - else: - attrs.append((id, name, "")) - - return attrs - - def GetEditable(self): - attrs = [] - - editableAttrs = self.typeAttrs.GetEditable() - - for attr in editableAttrs: - if not self.attrs.has_key(attr): - continue - - id = attr - name = Util.PropertiesPrintable.get(attr, attr) - values = self.attrs.get(attr) - - if len(values) > 0: - for value in values: - attrs.append((id, name, value)) - else: - attrs.append((id, name, "")) - - return attrs - - def Validate(self): - for key, value in self.newValues.iteritems(): - self.ValidateAttribute(key, value) - return True - - def ValidateAttribute(self, attr, value): - clsAttr = self.classAttrs.get(attr) - - if not clsAttr: - raise ValueError("invalid attribute id: %s" % attr) - - return True - - def Save(self): - Util.ModifyBongoAgent(self.mdb, self.server, self.agent, self.newValues) - self.attrs = Util.GetAgentAttributes(self.mdb, self.dn) diff --git a/src/www/bongo/hawkeye/Auth.py b/src/www/bongo/hawkeye/Auth.py index 92d8416..0a00f76 100644 --- a/src/www/bongo/hawkeye/Auth.py +++ b/src/www/bongo/hawkeye/Auth.py @@ -1,5 +1,7 @@ import bongo.dragonfly.Auth +from bongo.store.StoreClient import StoreClient import bongo.dragonfly.BongoSession as BongoSession +import time def Login(req): if not req.form: @@ -12,30 +14,38 @@ def Login(req): credUser = req.form["bongo-username"].value credPass = req.form["bongo-password"].value - if not bongo.dragonfly.Auth.CheckUserPass(credUser, credPass): - req.log.debug("invalid auth credentials") - if req.session: + store = None + try: + store = StoreClient(credUser, credUser, authPassword=credPass) + finally: + if store: + store.Quit() + else: req.session.invalidate() - return False + return False req.session["credUser"] = credUser req.session["credPass"] = credPass - req.log.debug("updated session: %s", str(req.session)) - + req.session.save() return True def AcceptCredentials(req): - req.log.debug("loaded session: %s", str(req.session)) - credUser = req.session.get("credUser") credPass = req.session.get("credPass") - - if not bongo.dragonfly.Auth.CheckUserPass(credUser, credPass): - req.log.info("invalid auth credentials") - req.session.invalidate() + + if credUser==None and credPass==None: + # no session return False - return True + client = None + try: + client = StoreClient(credUser, credUser, authPassword=credPass) + finally: + if client: + client.Quit() + return True + + return False def authenhandler(req): if AcceptCredentials(req): diff --git a/src/www/bongo/hawkeye/BackupView.py b/src/www/bongo/hawkeye/BackupView.py new file mode 100644 index 0000000..cfc38b0 --- /dev/null +++ b/src/www/bongo/hawkeye/BackupView.py @@ -0,0 +1,64 @@ +import bongo.dragonfly +import bongo.dragonfly.BongoUtil +from bongo.store.StoreClient import StoreClient, DocTypes +import bongo.external.simplejson as simplejson +from HawkeyeHandler import HawkeyeHandler +from tarfile import TarInfo +import datetime + +class BackupHandler(HawkeyeHandler): + def NeedsAuth(self, rp): + return True + + def index_GET(self, req, rp): + today = datetime.date.today() + self.SetVariable("set", today.strftime("%y%m%d")) + return self.SendTemplate(req, rp, "index.tpl") + + def download_GET(self, req, rp): + backup = "" + req.content_type = "application/x-tar" + for filename in ["manager", "avirus"]: + fullname = "/config/%s" % filename + content = self._getFile(req, fullname) + tf = TarInfo(fullname) + tf.size = len(content) + req.write(tf.tobuf()) + req.write(content) + zerobuf = 512 - (len(content) % 512) + req.write("\0" * zerobuf) + return None + + def index_POST(self, req, rp): + if not req.form: + return bongo.dragonfly.HTTP_UNAUTHORIZED + + if not req.form.has_key("command"): + return bongo.dragonfly.HTTP_UNAUTHORIZED + + config = self._getManagerFile(req) + if config.has_key("agents"): + for agent in config["agents"]: + if req.form.has_key(agent["name"]): + agent["enabled"] = True + else: + agent["enabled"] = False + else: + self.SetVariable("message", "Unable to read config") + + self._setManagerFile(req, config) + return self.index_GET(req, rp) + + def _getFile(self, req, filename): + store = None + try: + store = StoreClient(req.session["credUser"], req.session["credUser"], authPassword=req.session["credPass"]) + store.Store("_system") + configfile = store.Read(filename) + store.Quit() + except: + if store: + store.Quit() + return None + + return configfile diff --git a/src/www/bongo/hawkeye/Hawkeye.py b/src/www/bongo/hawkeye/Hawkeye.py deleted file mode 100644 index fd1961f..0000000 --- a/src/www/bongo/hawkeye/Hawkeye.py +++ /dev/null @@ -1,15 +0,0 @@ -# This file is imported by the Hawkeye header.psp file, so all its -# functions can be used by any templates. - -import urllib - -def BuildAgentUrl(server, agent): - return "/".join(("/admin/agents/edit", - urllib.quote(server), urllib.quote(agent))) - -def BuildUserUrl(context, user): - return "/".join(("/admin/users/edit", - urllib.quote(context), urllib.quote(user))) - -def SplitQueryArgs(query): - return [ urllib.unquote(s) for s in query.split("/") ] diff --git a/src/www/bongo/hawkeye/HawkeyeHandler.py b/src/www/bongo/hawkeye/HawkeyeHandler.py index 554c552..3dfb5e2 100644 --- a/src/www/bongo/hawkeye/HawkeyeHandler.py +++ b/src/www/bongo/hawkeye/HawkeyeHandler.py @@ -1,16 +1,10 @@ import os - -from bongo.dragonfly.BongoPSP import PSP -from bongo.MDB import MDB +from bongo.Template import Template import bongo.dragonfly class HawkeyeHandler: def __init__(self): - pass - - def GetMdb(self, req): - session = req.session - return MDB(session.get("credUser"), session.get("credPass")) + self._template = Template() def NeedsAuth(self, rp): return True @@ -18,38 +12,37 @@ class HawkeyeHandler: def ParsePath(self, rp): pass + def SetVariable(self, name, value): + self._template.setVariable(name, value) + def index(self, req, rp): return bongo.dragonfly.OK - def SendTemplate(self, req, rp, file, vars={}): + def SendTemplate(self, req, rp, file): path = os.path.join(rp.tmplPath, file) - - if os.path.exists(path): - req.log.debug("sending template: %s", path) - else: - req.log.debug("template does not exist: %s", path) - return bongo.dragonfly.HTTP_NOT_FOUND - req.content_type = "text/html" - pspvars = {"rp" : rp} - pspvars.update(vars) + if os.path.exists(path): + print "sending template: %s / %s" % (rp.tmplPath, file) + req.log.debug("sending template: %s", path) + else: + print "template does not exist: %s / %s " % (rp.tmplPath, file) + req.log.debug("template does not exist: %s", path) + req.write("

Error

Template not found.

") + return bongo.dragonfly.HTTP_NOT_FOUND - psp = None + self.SetVariable("message", "") try: - psp = PSP(req, path, vars=pspvars) - psp.run() + t = self._template + t.setTemplatePath(rp.tmplRoot) + t.setTemplateUriRoot(rp.tmplUriRoot) + t.setTemplateFile(path) + t.Run(req) return bongo.dragonfly.OK except ImportError, e: - req.log.debug("Could not import psp from mod_python", exc_info=1) - req.write("") - req.write("

ERROR: %s

" % str(e)) - req.write("

The standalone admin interface requires mod_python. Maybe it is not installed?

") - req.write("") - - return bongo.dragonfly.HTTP_INTERNAL_SERVER_ERROR - except: - req.log.debug("Exception running psp", exc_info=1) - if psp: - psp.display_code() + req.write("

ERROR

%s

" % str(e)) return bongo.dragonfly.HTTP_INTERNAL_SERVER_ERROR + #except: + # print "Error running Template" + # req.write("

Error

Very bad error!

") + # return bongo.dragonfly.HTTP_INTERNAL_SERVER_ERROR diff --git a/src/www/bongo/hawkeye/HawkeyePath.py b/src/www/bongo/hawkeye/HawkeyePath.py index 429325c..cbcaa84 100644 --- a/src/www/bongo/hawkeye/HawkeyePath.py +++ b/src/www/bongo/hawkeye/HawkeyePath.py @@ -5,16 +5,14 @@ import bongo.dragonfly from bongo.dragonfly.BongoSession import BongoSession from bongo.dragonfly.HttpError import HttpError -import AgentsView import RootView -import SummaryView -import UsersView +import BackupView +import ServerView views = { - "agents" : AgentsView.AgentsHandler(), "root" : RootView.RootHandler(), - "summary" : SummaryView.SummaryHandler(), - "users" : UsersView.UsersHandler() + "backup" : BackupView.BackupHandler(), + "server" : ServerView.ServerHandler() } class HawkeyePath: @@ -53,6 +51,8 @@ class HawkeyePath: self.path = "/".join(parts) self.tmplPath = os.path.join(hawkeye_tmpl_root, self.view) + self.tmplRoot = hawkeye_tmpl_root + self.tmplUriRoot = hawkeye_root req.session = None diff --git a/src/www/bongo/hawkeye/RootView.py b/src/www/bongo/hawkeye/RootView.py index 1adb3a6..788fff9 100644 --- a/src/www/bongo/hawkeye/RootView.py +++ b/src/www/bongo/hawkeye/RootView.py @@ -5,24 +5,35 @@ import bongo.hawkeye.Auth as Auth class RootHandler(HawkeyeHandler): def NeedsAuth(self, rp): - return False + if rp.action == "login": + return False + else: + return True def index_GET(self, req, rp): - return self.SendTemplate(req, rp, "index.psp") + return self.SendTemplate(req, rp, "index.tpl") + + def test_GET(self, req, rp): + self.SetVariable("helloworld", "Hello World!") + self.SetVariable("escapeme", "If anything appears after this, I've been escaped.  ") + self.SetVariable("greenstyle", "color: green") + self.SetVariable("somethingtrue", 1) + self.SetVariable("somethinguntrue", 0) + self.SetVariable("testlist", [{ "say": "Hello!" }, { "say": "Bonjour!"}]) + return self.SendTemplate(req, rp, "test.tpl") def login_GET(self, req, rp): - return self.SendTemplate(req, rp, "login.psp") + return self.SendTemplate(req, rp, "login.tpl") def login_POST(self, req, rp): if not Auth.Login(req): return self.login_GET(req, rp) - target = "/admin" + target = rp.tmplUriRoot if req.args and len(req.args) > 0: target = req.args - req.session.save() return bongo.dragonfly.BongoUtil.redirect(req, target) def logout_GET(self, req, rp): diff --git a/src/www/bongo/hawkeye/Server.py b/src/www/bongo/hawkeye/Server.py index adaf04e..0ffdca3 100644 --- a/src/www/bongo/hawkeye/Server.py +++ b/src/www/bongo/hawkeye/Server.py @@ -1,4 +1,3 @@ -from mod_python import apache, util from bongo.dragonfly.ApacheLogHandler import ApacheLogHandler, RequestLogProxy from HawkeyePath import HawkeyePath from bongo.dragonfly.HttpError import HttpError diff --git a/src/www/bongo/hawkeye/ServerView.py b/src/www/bongo/hawkeye/ServerView.py new file mode 100644 index 0000000..fe4b26f --- /dev/null +++ b/src/www/bongo/hawkeye/ServerView.py @@ -0,0 +1,86 @@ +import bongo.dragonfly +import bongo.dragonfly.BongoUtil +from bongo.store.StoreClient import StoreClient, DocTypes +import bongo.external.simplejson as simplejson +from HawkeyeHandler import HawkeyeHandler + +class ServerHandler(HawkeyeHandler): + def NeedsAuth(self, rp): + return True + + def index_GET(self, req, rp): + agentlist = [] + config = self._getManagerFile(req) + + # change things which eval. to False to empty elements + if config != None and config.has_key("agents"): + for agent in config["agents"]: + if not agent["enabled"]: + agent["enabled"] = None + self.SetVariable("agentlist", config["agents"]) + self.SetVariable("success", 1) + else: + self.SetVariable("success", 0) + + return self.SendTemplate(req, rp, "index.tpl") + + def index_POST(self, req, rp): + if not req.form: + return bongo.dragonfly.HTTP_UNAUTHORIZED + + if not req.form.has_key("command"): + return bongo.dragonfly.HTTP_UNAUTHORIZED + + config = self._getManagerFile(req) + if config.has_key("agents"): + for agent in config["agents"]: + if req.form.has_key(agent["name"]): + agent["enabled"] = True + else: + agent["enabled"] = False + else: + self.SetVariable("message", "Unable to read config") + + self._setManagerFile(req, config) + return self.index_GET(req, rp) + + def login_POST(self, req, rp): + if not Auth.Login(req): + return self.login_GET(req, rp) + + target = rp.tmplUriRoot + + if req.args and len(req.args) > 0: + target = req.args + + return bongo.dragonfly.BongoUtil.redirect(req, target) + + def _getManagerFile(self, req): + config = {} + store = None + decoder = simplejson.JSONDecoder() + try: + store = StoreClient(req.session["credUser"], req.session["credUser"], authPassword=req.session["credPass"]) + store.Store("_system") + configfile = store.Read("/config/manager") + config = decoder.decode(configfile) + store.Quit() + except: + if store: + store.Quit() + return None + + return config + + def _setManagerFile(self, req, obj): + encoder = simplejson.JSONEncoder() + store = None + try: + store = StoreClient(req.session["credUser"], req.session["credUser"], authPassword=req.session["credPass"]) + store.Store("_system") + configfile = encoder.encode(obj) + store.Replace("/config/manager", configfile) + finally: + if store: + store.Quit() + diff --git a/src/www/bongo/hawkeye/SummaryView.py b/src/www/bongo/hawkeye/SummaryView.py index 2359200..b6e6f7c 100644 --- a/src/www/bongo/hawkeye/SummaryView.py +++ b/src/www/bongo/hawkeye/SummaryView.py @@ -3,6 +3,4 @@ from HawkeyeHandler import HawkeyeHandler class SummaryHandler(HawkeyeHandler): def index_GET(self, req, rp): - mdb = self.GetMdb(req) - vars = {"mdb" : mdb} - return self.SendTemplate(req, rp, "index.psp", vars) + return self.SendTemplate(req, rp, "index.tpl", vars) diff --git a/src/www/bongo/hawkeye/UserAttributes.py b/src/www/bongo/hawkeye/UserAttributes.py deleted file mode 100644 index 39b2fdb..0000000 --- a/src/www/bongo/hawkeye/UserAttributes.py +++ /dev/null @@ -1,113 +0,0 @@ -# a per-agent table of editable properties - -import types -import bongo.admin.Schema as Schema -import bongo.admin.Util as Util - -from bongo.bootstrap import mdb, msgapi - -class HawkeyeUserAttributes: - extraProps = { - mdb.C_USER : [ mdb.A_GIVEN_NAME, - mdb.A_SURNAME ] - } - - def __init__(self, type): - self.constant = constant = [] - self.editable = editable = [] - - editable.extend(self.extraProps.get(type, [])) - - def GetConstant(self): - return self.constant - - def GetEditable(self): - return self.editable - -class UserAttributes: - def __init__(self, mdb, dn, values=None): - self.schema = schema = Util.GetSchema() - - self.mdb = mdb - self.dn = dn - self.context, self.user = dn.rsplit("\\", 1) - self.newValues = None - - if values: - self.newValues = newValues = {} - for key in values.keys(): - newValues[key] = values[key].value - - print "details for", dn - details = mdb.GetObjectDetails(dn) - - self.type = type = details.get("Type") - - self.attrs = Util.GetUserAttributes(mdb, self.context, self.user) - self.classAttrs = Util.GetClassAttributeObjects(type) - self.typeAttrs = HawkeyeUserAttributes(type) - - def GetConstant(self): - attrs = [] - - constantAttrs = self.typeAttrs.GetConstant() - - for attr in constantAttrs: - if not self.attrs.has_key(attr): - continue - - id = attr - name = Util.PropertiesPrintable.get(attr, attr) - values = self.attrs.get(attr) - - if len(values) > 0: - for value in values: - attrs.append((id, name, value)) - else: - attrs.append((id, name, "")) - - return attrs - - def GetEditable(self): - attrs = [] - - editableAttrs = self.typeAttrs.GetEditable() - - for attr in editableAttrs: - if not self.attrs.has_key(attr): - continue - - id = attr - name = Util.PropertiesPrintable.get(attr, attr) - values = self.attrs.get(attr) - - if len(values) > 0: - for value in values: - attrs.append((id, name, value)) - else: - attrs.append((id, name, "")) - - return attrs - - def Validate(self): - for key, value in self.newValues.iteritems(): - self.ValidateAttribute(key, value) - return True - - def ValidateAttribute(self, attr, value): - clsAttr = self.classAttrs.get(attr) - - if not clsAttr: - raise ValueError("invalid attribute id: %s" % attr) - - return True - - def Save(self): - attrs = {} - - for key, value in self.newValues.iteritems(): - if self.attrs.get(key) != value: - attrs[key] = [value] - - Util.ModifyBongoUser(self.mdb, self.context, self.user, attrs) - self.attrs = Util.GetUserAttributes(self.mdb, self.context, self.user) diff --git a/src/www/css/admin.css b/src/www/css/admin.css new file mode 100644 index 0000000..23797c9 --- /dev/null +++ b/src/www/css/admin.css @@ -0,0 +1,74 @@ +* { + font-family: sans-serif; +} + +html, body { + overflow: auto; + margin: 0; + padding: 0; + border: 0; +} + +body { + border-top: 2px solid #d4dceb; + background-color: #627aa3; /* #b4c5e1; */ +} + +img#logo { + position: absolute; + top: 40px; + left: 40px; + width: 339px; + height: 59px; +} + +ul.navigation { + position: absolute; + top: 110px; + left: 40px; + width: 150px; + background-color: white; + padding: 10px; + border: 1px solid #555; + margin: 0; +} + +ul.navigation li { + list-style: none; + margin: 0; + text-align: left; +} + +ul.navigation li a { + display: block; + text-decoration: none; + padding: .25em; + color: black; + border-bottom: 1px solid #555; +} + +ul.navigation li a:hover { + background-color: #ddd; +} + +div#content { + position: absolute; + top: 110px; + right: 20px; + left: 220px; + border: 1px solid #555; + background-color: white; + padding: 10px; +} + +div#login { + position: absolute; + top: 200px; + left: 220px; + width: 500px; + height: 250px; + background-color: white; + padding: 20px; + text-align: right; + border: 1px solid #555; +} diff --git a/src/www/hawkeye/backup/index.tpl b/src/www/hawkeye/backup/index.tpl new file mode 100644 index 0000000..a9e1b7d --- /dev/null +++ b/src/www/hawkeye/backup/index.tpl @@ -0,0 +1,18 @@ +%(include|header.tpl)s + +

Backup and Restore

+ +

This allows you to save and restore copies of the Bongo server configuration. +It does not look at user data such as contacts or e-mail: use the +specialist bongo-backup tool to handle those (potentially very large) +datasets!

+ +

Backup

+ +

Download backup file

+ +

Restore

+ +

TODO, sorry :)

+ +%(include|footer.tpl)s diff --git a/src/www/hawkeye/psp/footer.psp b/src/www/hawkeye/footer.tpl similarity index 96% rename from src/www/hawkeye/psp/footer.psp rename to src/www/hawkeye/footer.tpl index 4f0fa39..de32b55 100644 --- a/src/www/hawkeye/psp/footer.psp +++ b/src/www/hawkeye/footer.tpl @@ -1,4 +1,5 @@ + + - diff --git a/src/www/hawkeye/header.tpl b/src/www/hawkeye/header.tpl new file mode 100644 index 0000000..41a6e97 --- /dev/null +++ b/src/www/hawkeye/header.tpl @@ -0,0 +1,22 @@ + + + Bongo Web Administration + + + + + + + + + + + +
diff --git a/src/www/hawkeye/psp/agents/agents.psp b/src/www/hawkeye/psp/agents/agents.psp deleted file mode 100644 index d70433c..0000000 --- a/src/www/hawkeye/psp/agents/agents.psp +++ /dev/null @@ -1,16 +0,0 @@ -<% -import bongo.admin.Util as Util -for server in Util.ListBongoMessagingServers(mdb): - # indent -%> -<%= server %> - -<% -# outdent -%> diff --git a/src/www/hawkeye/psp/agents/edit.psp b/src/www/hawkeye/psp/agents/edit.psp deleted file mode 100644 index 64b6697..0000000 --- a/src/www/hawkeye/psp/agents/edit.psp +++ /dev/null @@ -1,57 +0,0 @@ -<%@ include file="../header.psp" %> - - - - - - - -
-<%@ include file="agents.psp" %> - -<% -import bongo.admin.Util as Util -from bongo.hawkeye.AgentAttributes import AgentAttributes - -server, agent = rp.pathInfo - -dn = rp.path.replace("/", "\\") - -if form: - props = AgentAttributes(mdb, dn, form) - if props.Validate(): - props.Save() -else: - props = AgentAttributes(mdb, dn) -# outdent -%> -<%= agent %><%= props.IsEnabled() and "(enabled)" or "(disabled)" %>
- -<% -constant = props.GetConstant() - -for (id, name, value) in constant: - # indent -%> -<%= name %>: <%= value %>
-<% -#outdent -%> - -
-<% -editable = props.GetEditable() - -for (id, name, value) in editable: - # indent -%> -<%= name %>
-<% -#outdent -%> - - -
-
- -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/agents/index.psp b/src/www/hawkeye/psp/agents/index.psp deleted file mode 100644 index 2f73da6..0000000 --- a/src/www/hawkeye/psp/agents/index.psp +++ /dev/null @@ -1,3 +0,0 @@ -<%@ include file="../header.psp" %> -<%@ include file="agents.psp" %> -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/header.psp b/src/www/hawkeye/psp/header.psp deleted file mode 100644 index e3e01af..0000000 --- a/src/www/hawkeye/psp/header.psp +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - Bongo - - - -<% -# imports for all hawkeye templates -import bongo.hawkeye.Hawkeye as Hawkeye -%> - - -
- - - -
-
-
diff --git a/src/www/hawkeye/psp/root/index.psp b/src/www/hawkeye/psp/root/index.psp deleted file mode 100644 index 1681b44..0000000 --- a/src/www/hawkeye/psp/root/index.psp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ include file="../header.psp" %> - -

-This is a new web administration interface for Bongo. You can't do -much with it yet, but you can modify existing agents and users. -

- -

For more administrative flexibility, please look into the -bongo-admin commandline tool. -

- -Administer Bongo Agents
-Administer Bongo Users - -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/root/login.psp b/src/www/hawkeye/psp/root/login.psp deleted file mode 100644 index 76d4cac..0000000 --- a/src/www/hawkeye/psp/root/login.psp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ include file="../header.psp" %> - -
-
- Bongo -

Log in to use Bongo Admin

- - - - - -
-
-
- -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/summary/index.psp b/src/www/hawkeye/psp/summary/index.psp deleted file mode 100644 index 8037504..0000000 --- a/src/www/hawkeye/psp/summary/index.psp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ include file="../header.psp" %> - -<% -import bongo.admin.Util as Util -for server in Util.ListBongoMessagingServers(mdb): - # indent -%> -

<%= server %>

-
    -<% -for agent in Util.ListBongoAgents(mdb, server): - # indent -%> -
  • <%= agent %>
  • -
-<% -# outdent -%> - -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/users/edit.psp b/src/www/hawkeye/psp/users/edit.psp deleted file mode 100644 index f17c294..0000000 --- a/src/www/hawkeye/psp/users/edit.psp +++ /dev/null @@ -1,59 +0,0 @@ -<%@ include file="../header.psp" %> - -<% -import bongo.admin.Util as Util -from bongo.hawkeye.UserAttributes import UserAttributes - -context, user = rp.pathInfo -%> - - - - - - - -
-<%@ include file="users.psp" %> - -<% -dn = rp.path.replace("/", "\\") - -if form: - props = UserAttributes(mdb, dn, form) - if props.Validate(): - props.Save() -else: - props = UserAttributes(mdb, dn) -# outdent -%> -<%= user %>
- -<% -constant = props.GetConstant() - -for (id, name, value) in constant: - # indent -%> -<%= name %>: <%= value %>
-<% -#outdent -%> - -
-<% -editable = props.GetEditable() - -for (id, name, value) in editable: - # indent -%> -<%= name %>
-<% -#outdent -%> - - -
-
- -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/users/index.psp b/src/www/hawkeye/psp/users/index.psp deleted file mode 100644 index 06b0b79..0000000 --- a/src/www/hawkeye/psp/users/index.psp +++ /dev/null @@ -1,3 +0,0 @@ -<%@ include file="../header.psp" %> -<%@ include file="users.psp" %> -<%@ include file="../footer.psp" %> diff --git a/src/www/hawkeye/psp/users/users.psp b/src/www/hawkeye/psp/users/users.psp deleted file mode 100644 index 62e2348..0000000 --- a/src/www/hawkeye/psp/users/users.psp +++ /dev/null @@ -1,19 +0,0 @@ -<% -import bongo.admin.Util as Util -from bongo.libs import msgapi - -if not context: - context = msgapi.GetConfigProperty(msgapi.DEFAULT_CONTEXT) -# outdent -%> -Bongo Users -
    -<% -for user in Util.ListBongoUsers(mdb, context): - # indent -%> -
  • <%= user %>
  • -<% -# outdent -%> -
diff --git a/src/www/hawkeye/root/index.tpl b/src/www/hawkeye/root/index.tpl new file mode 100644 index 0000000..3562cea --- /dev/null +++ b/src/www/hawkeye/root/index.tpl @@ -0,0 +1,7 @@ +%(include|header.tpl)s + +

Welcome

+ +

This is the admin tool. Under construction, obviously :)

+ +%(include|footer.tpl)s diff --git a/src/www/hawkeye/root/login.tpl b/src/www/hawkeye/root/login.tpl new file mode 100644 index 0000000..e2743e4 --- /dev/null +++ b/src/www/hawkeye/root/login.tpl @@ -0,0 +1,28 @@ + + + Bongo Web Administration + + + + + + + + + +
+ +

Login Required

+ +
+

User name:

+

Password:

+ +
+ +
+ + + diff --git a/src/www/hawkeye/root/test-hello.tpl b/src/www/hawkeye/root/test-hello.tpl new file mode 100644 index 0000000..16114e1 --- /dev/null +++ b/src/www/hawkeye/root/test-hello.tpl @@ -0,0 +1 @@ +Test data that we include in the test template. diff --git a/src/www/hawkeye/root/test.tpl b/src/www/hawkeye/root/test.tpl new file mode 100644 index 0000000..c4c9ea3 --- /dev/null +++ b/src/www/hawkeye/root/test.tpl @@ -0,0 +1,63 @@ + + +

Test Template

+ +

This is designed to exercise the Bongo Template system.

+ +

Templates are first pre-processed, then run entire result is run through the Tal system.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestOutput
PP substitution%(helloworld)s
PP relative URL fixup%(url|/)s
PP include template%(include|root/test-hello.tpl)s
Tal substitutionTest failed.
Tal sub. w/restructure +

Test 1 failed.

+

Test 2 failed.

+
Tal loopy
    +
  • +
+
Tal conditionals +

Test 1 succeeded!

+

Test 2 FAILED

+
Tal attributes

If I'm green, the test succeeded!

Tal omit-tag

If I'm red, the test failed.

+ + + diff --git a/src/www/hawkeye/server/index.tpl b/src/www/hawkeye/server/index.tpl new file mode 100644 index 0000000..21e40b0 --- /dev/null +++ b/src/www/hawkeye/server/index.tpl @@ -0,0 +1,31 @@ +%(include|header.tpl)s + +

Server Settings

+ +

No message.

+ +

+ Unable to read configuration information from the Bongo store. + Are you logged in as a user with administrative permissions? +

+ +
+
+ + + + + + + + + + +
Enabled?Agent Name
+ + unknown agent
+ +
+
+ +%(include|footer.tpl)s