libuav original

Dependents:   UAVCAN UAVCAN_Subscriber

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers pyratemp.py Source File

pyratemp.py

00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 """
00004 Small, simple and powerful template-engine for Python.
00005 
00006 A template-engine for Python, which is very simple, easy to use, small,
00007 fast, powerful, modular, extensible, well documented and pythonic.
00008 
00009 See documentation for a list of features, template-syntax etc.
00010 
00011 :Version:   0.3.0
00012 :Requires:  Python >=2.6 / 3.x
00013 
00014 :Usage:
00015     see class ``Template`` and examples below.
00016 
00017 :Example:
00018 
00019     Note that the examples are in Python 2; they also work in
00020     Python 3 if you replace u"..." by "...", unicode() by str()
00021     and partly "..." by b"...".
00022 
00023     quickstart::
00024         >>> t = Template("hello @!name!@")
00025         >>> print(t(name="marvin"))
00026         hello marvin
00027 
00028     quickstart with a template-file::
00029         # >>> t = Template(filename="mytemplate.tmpl")
00030         # >>> print(t(name="marvin"))
00031         # hello marvin
00032 
00033     generic usage::
00034         >>> t = Template(u"output is in Unicode \\xe4\\xf6\\xfc\\u20ac")
00035         >>> t                                           #doctest: +ELLIPSIS
00036         <...Template instance at 0x...>
00037         >>> t()
00038         u'output is in Unicode \\xe4\\xf6\\xfc\\u20ac'
00039         >>> unicode(t)
00040         u'output is in Unicode \\xe4\\xf6\\xfc\\u20ac'
00041 
00042     with data::
00043         >>> t = Template("hello @!name!@", data={"name":"world"})
00044         >>> t()
00045         u'hello world'
00046         >>> t(name="worlds")
00047         u'hello worlds'
00048 
00049         # >>> t(note="data must be Unicode or ASCII", name=u"\\xe4")
00050         # u'hello \\xe4'
00051 
00052     escaping::
00053         >>> t = Template("hello escaped: @!name!@, unescaped: $!name!$")
00054         >>> t(name='''<>&'"''')
00055         u'hello escaped: &lt;&gt;&amp;&#39;&quot;, unescaped: <>&\\'"'
00056 
00057     result-encoding::
00058         # encode the unicode-object to your encoding with encode()
00059         >>> t = Template(u"hello \\xe4\\xf6\\xfc\\u20ac")
00060         >>> result = t()
00061         >>> result
00062         u'hello \\xe4\\xf6\\xfc\\u20ac'
00063         >>> result.encode("utf-8")
00064         'hello \\xc3\\xa4\\xc3\\xb6\\xc3\\xbc\\xe2\\x82\\xac'
00065         >>> result.encode("ascii")
00066         Traceback (most recent call last):
00067           ...
00068         UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-9: ordinal not in range(128)
00069         >>> result.encode("ascii", 'xmlcharrefreplace')
00070         'hello &#228;&#246;&#252;&#8364;'
00071 
00072     Python-expressions::
00073         >>> Template('formatted: @! "%8.5f" % value !@')(value=3.141592653)
00074         u'formatted:  3.14159'
00075         >>> Template("hello --@!name.upper().center(20)!@--")(name="world")
00076         u'hello --       WORLD        --'
00077         >>> Template("calculate @!var*5+7!@")(var=7)
00078         u'calculate 42'
00079 
00080     blocks (if/for/macros/...)::
00081         >>> t = Template("<!--(if foo == 1)-->bar<!--(elif foo == 2)-->baz<!--(else)-->unknown(@!foo!@)<!--(end)-->")
00082         >>> t(foo=2)
00083         u'baz'
00084         >>> t(foo=5)
00085         u'unknown(5)'
00086 
00087         >>> t = Template("<!--(for i in mylist)-->@!i!@ <!--(else)-->(empty)<!--(end)-->")
00088         >>> t(mylist=[])
00089         u'(empty)'
00090         >>> t(mylist=[1,2,3])
00091         u'1 2 3 '
00092 
00093         >>> t = Template("<!--(for i,elem in enumerate(mylist))--> - @!i!@: @!elem!@<!--(end)-->")
00094         >>> t(mylist=["a","b","c"])
00095         u' - 0: a - 1: b - 2: c'
00096 
00097         >>> t = Template('<!--(macro greetings)-->hello <strong>@!name!@</strong><!--(end)-->  @!greetings(name=user)!@')
00098         >>> t(user="monty")
00099         u'  hello <strong>monty</strong>'
00100 
00101     exists::
00102         >>> t = Template('<!--(if exists("foo"))-->YES<!--(else)-->NO<!--(end)-->')
00103         >>> t()
00104         u'NO'
00105         >>> t(foo=1)
00106         u'YES'
00107         >>> t(foo=None)       # note this difference to 'default()'
00108         u'YES'
00109 
00110     default-values::
00111         # non-existing variables raise an error
00112         >>> Template('hi @!optional!@')()
00113         Traceback (most recent call last):
00114           ...
00115         TemplateRenderError: Cannot eval expression 'optional'. (NameError: name 'optional' is not defined)
00116 
00117         >>> t = Template('hi @!default("optional","anyone")!@')
00118         >>> t()
00119         u'hi anyone'
00120         >>> t(optional=None)
00121         u'hi anyone'
00122         >>> t(optional="there")
00123         u'hi there'
00124 
00125         # the 1st parameter can be any eval-expression
00126         >>> t = Template('@!default("5*var1+var2","missing variable")!@')
00127         >>> t(var1=10)
00128         u'missing variable'
00129         >>> t(var1=10, var2=2)
00130         u'52'
00131 
00132         # also in blocks
00133         >>> t = Template('<!--(if default("opt1+opt2",0) > 0)-->yes<!--(else)-->no<!--(end)-->')
00134         >>> t()
00135         u'no'
00136         >>> t(opt1=23, opt2=42)
00137         u'yes'
00138 
00139         >>> t = Template('<!--(for i in default("optional_list",[]))-->@!i!@<!--(end)-->')
00140         >>> t()
00141         u''
00142         >>> t(optional_list=[1,2,3])
00143         u'123'
00144 
00145 
00146         # but make sure to put the expression in quotation marks, otherwise:
00147         >>> Template('@!default(optional,"fallback")!@')()
00148         Traceback (most recent call last):
00149           ...
00150         TemplateRenderError: Cannot eval expression 'default(optional,"fallback")'. (NameError: name 'optional' is not defined)
00151 
00152     setvar::
00153         >>> t = Template('$!setvar("i", "i+1")!$@!i!@')
00154         >>> t(i=6)
00155         u'7'
00156 
00157         >>> t = Template('''<!--(if isinstance(s, (list,tuple)))-->$!setvar("s", '"\\\\\\\\n".join(s)')!$<!--(end)-->@!s!@''')
00158         >>> t(isinstance=isinstance, s="123")
00159         u'123'
00160         >>> t(isinstance=isinstance, s=["123", "456"])
00161         u'123\\n456'
00162 
00163 :Author:    Roland Koebler (rk at simple-is-better dot org)
00164 :Copyright: Roland Koebler
00165 :License:   MIT/X11-like, see __license__
00166 
00167 :RCS:       $Id: pyratemp.py,v 1.12 2013/04/02 20:26:06 rk Exp $
00168 """
00169 from __future__ import unicode_literals
00170 
00171 __version__ = "0.3.0"
00172 __author__   = "Roland Koebler <rk at simple-is-better dot org>"
00173 __license__  = """Copyright (c) Roland Koebler, 2007-2013
00174 
00175 Permission is hereby granted, free of charge, to any person obtaining a copy
00176 of this software and associated documentation files (the "Software"), to deal
00177 in the Software without restriction, including without limitation the rights
00178 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00179 copies of the Software, and to permit persons to whom the Software is
00180 furnished to do so, subject to the following conditions:
00181 
00182 The above copyright notice and this permission notice shall be included in
00183 all copies or substantial portions of the Software.
00184 
00185 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00186 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00187 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00188 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00189 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
00190 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
00191 IN THE SOFTWARE."""
00192 
00193 #=========================================
00194 
00195 import os, re, sys
00196 if sys.version_info[0] >= 3:
00197     import builtins
00198     unicode = str
00199     long = int
00200 else:
00201     import __builtin__ as builtins
00202     from codecs import open
00203 
00204 #=========================================
00205 # some useful functions
00206 
00207 #----------------------
00208 # string-position: i <-> row,col
00209 
00210 def srow(string, i):
00211     """Get line numer of ``string[i]`` in `string`.
00212 
00213     :Returns: row, starting at 1
00214     :Note:    This works for text-strings with ``\\n`` or ``\\r\\n``.
00215     """
00216     return string.count('\n', 0, max(0, i)) + 1
00217 
00218 def scol(string, i):
00219     """Get column number of ``string[i]`` in `string`.
00220 
00221     :Returns: column, starting at 1 (but may be <1 if i<0)
00222     :Note:    This works for text-strings with ``\\n`` or ``\\r\\n``.
00223     """
00224     return i - string.rfind('\n', 0, max(0, i))
00225 
00226 def sindex(string, row, col):
00227     """Get index of the character at `row`/`col` in `string`.
00228 
00229     :Parameters:
00230         - `row`: row number, starting at 1.
00231         - `col`: column number, starting at 1.
00232     :Returns:    ``i``, starting at 0 (but may be <1 if row/col<0)
00233     :Note:       This works for text-strings with '\\n' or '\\r\\n'.
00234     """
00235     n = 0
00236     for _ in range(row-1):
00237         n = string.find('\n', n) + 1
00238     return n+col-1
00239 
00240 #----------------------
00241 
00242 def dictkeyclean(d):
00243     """Convert all keys of the dict `d` to strings.
00244     """
00245     new_d = {}
00246     for k, v in d.items():
00247         new_d[str(k)] = v
00248     return new_d
00249 
00250 #----------------------
00251 
00252 def dummy(*_, **__):
00253     """Dummy function, doing nothing.
00254     """
00255     pass
00256 
00257 def dummy_raise(exception, value):
00258     """Create an exception-raising dummy function.
00259 
00260     :Returns: dummy function, raising ``exception(value)``
00261     """
00262     def mydummy(*_, **__):
00263         raise exception(value)
00264     return mydummy
00265 
00266 #=========================================
00267 # escaping
00268 
00269 (NONE, HTML, LATEX, MAIL_HEADER) = range(0, 4)
00270 ESCAPE_SUPPORTED = {"NONE":None, "HTML":HTML, "LATEX":LATEX, "MAIL_HEADER":MAIL_HEADER}
00271 
00272 def escape(s, format=HTML):
00273     """Replace special characters by their escape sequence.
00274 
00275     :Parameters:
00276         - `s`: unicode-string to escape
00277         - `format`:
00278 
00279           - `NONE`:  nothing is replaced
00280           - `HTML`:  replace &<>'" by &...;
00281           - `LATEX`: replace \#$%&_{}~^
00282           - `MAIL_HEADER`: escape non-ASCII mail-header-contents
00283     :Returns:
00284         the escaped string in unicode
00285     :Exceptions:
00286         - `ValueError`: if `format` is invalid.
00287 
00288     :Uses:
00289         MAIL_HEADER uses module email
00290     """
00291     #Note: If you have to make sure that every character gets replaced
00292     #      only once (and if you cannot achieve this with the following code),
00293     #      use something like "".join([replacedict.get(c,c) for c in s])
00294     #      which is about 2-3 times slower (but maybe needs less memory).
00295     #Note: This is one of the most time-consuming parts of the template.
00296     if format is None or format == NONE:
00297         pass
00298     elif format == HTML:
00299         s = s.replace("&", "&amp;") # must be done first!
00300         s = s.replace("<", "&lt;")
00301         s = s.replace(">", "&gt;")
00302         s = s.replace('"', "&quot;")
00303         s = s.replace("'", "&#39;")
00304     elif format == LATEX:
00305         s = s.replace("\\", "\\x")    #must be done first!
00306         s = s.replace("#",  "\\#")
00307         s = s.replace("$",  "\\$")
00308         s = s.replace("%",  "\\%")
00309         s = s.replace("&",  "\\&")
00310         s = s.replace("_",  "\\_")
00311         s = s.replace("{",  "\\{")
00312         s = s.replace("}",  "\\}")
00313         s = s.replace("\\x","\\textbackslash{}")
00314         s = s.replace("~",  "\\textasciitilde{}")
00315         s = s.replace("^",  "\\textasciicircum{}")
00316     elif format == MAIL_HEADER:
00317         import email.header
00318         try:
00319             s.encode("ascii")
00320             return s
00321         except UnicodeEncodeError:
00322             return email.header.make_header([(s, "utf-8")]).encode()
00323     else:
00324         raise ValueError('Invalid format (only None, HTML, LATEX and MAIL_HEADER are supported).')
00325     return s
00326 
00327 #=========================================
00328 
00329 #-----------------------------------------
00330 # Exceptions
00331 
00332 class TemplateException (Exception):
00333     """Base class for template-exceptions."""
00334     pass
00335 
00336 class TemplateParseError(TemplateException):
00337     """Template parsing failed."""
00338     def __init__ (self, err, errpos):
00339         """
00340         :Parameters:
00341             - `err`:    error-message or exception to wrap
00342             - `errpos`: ``(filename,row,col)`` where the error occured.
00343         """
00344         self.err  = err
00345         self.filename, self.row, self.col  = errpos
00346         TemplateException.__init__(self)
00347     def __str__(self):
00348         if not self.filename:
00349             return "line %d, col %d: %s" % (self.row, self.col , str(self.err ))
00350         else:
00351             return "file %s, line %d, col %d: %s" % (self.filename, self.row, self.col , str(self.err ))
00352 
00353 class TemplateSyntaxError (TemplateParseError , SyntaxError):
00354     """Template syntax-error."""
00355     pass
00356 
00357 class TemplateIncludeError(TemplateParseError):
00358     """Template 'include' failed."""
00359     pass
00360 
00361 class TemplateRenderError (TemplateException ):
00362     """Template rendering failed."""
00363     pass
00364 
00365 #-----------------------------------------
00366 # Loader
00367 
00368 class LoaderString :
00369     """Load template from a string/unicode.
00370 
00371     Note that 'include' is not possible in such templates.
00372     """
00373     def __init__(self, encoding='utf-8'):
00374         self.encoding = encoding
00375 
00376     def load (self, s):
00377         """Return template-string as unicode.
00378         """
00379         if isinstance(s, unicode):
00380             u = s
00381         else:
00382             u = s.decode(self.encoding )
00383         return u
00384 
00385 class LoaderFile :
00386     """Load template from a file.
00387 
00388     When loading a template from a file, it's possible to including other
00389     templates (by using 'include' in the template). But for simplicity
00390     and security, all included templates have to be in the same directory!
00391     (see ``allowed_path``)
00392     """
00393     def __init__ (self, allowed_path=None, encoding='utf-8'):
00394         """Init the loader.
00395 
00396         :Parameters:
00397             - `allowed_path`: path of the template-files
00398             - `encoding`: encoding of the template-files
00399         :Exceptions:
00400             - `ValueError`: if `allowed_path` is not a directory
00401         """
00402         if allowed_path and not os.path.isdir(allowed_path):
00403             raise ValueError("'allowed_path' has to be a directory.")
00404         self.path      = allowed_path
00405         self.encoding  = encoding
00406 
00407     def load (self, filename):
00408         """Load a template from a file.
00409 
00410         Check if filename is allowed and return its contens in unicode.
00411 
00412         :Parameters:
00413             - `filename`: filename of the template without path
00414         :Returns:
00415             the contents of the template-file in unicode
00416         :Exceptions:
00417             - `ValueError`: if `filename` contains a path
00418         """
00419         if filename != os.path.basename(filename):
00420             raise ValueError("No path allowed in filename. (%s)" %(filename))
00421         filename = os.path.join(self.path , filename)
00422 
00423         f = open(filename, 'r', encoding=self.encoding)
00424         u = f.read()
00425         f.close()
00426 
00427         return u
00428 
00429 #-----------------------------------------
00430 # Parser
00431 
00432 class Parser (object):
00433     """Parse a template into a parse-tree.
00434 
00435     Includes a syntax-check, an optional expression-check and verbose
00436     error-messages.
00437 
00438     See documentation for a description of the parse-tree.
00439     """
00440     # template-syntax
00441     _comment_start = "#!"
00442     _comment_end   = "!#"
00443     _sub_start     = "$!"
00444     _sub_end       = "!$"
00445     _subesc_start  = "@!"
00446     _subesc_end    = "!@"
00447     _block_start   = "<!--("
00448     _block_end     = ")-->"
00449 
00450     # build regexps
00451     # comment
00452     #   single-line, until end-tag or end-of-line.
00453     _strComment = r"""%s(?P<content>.*?)(?P<end>%s|\n|$)""" \
00454                     % (re.escape(_comment_start), re.escape(_comment_end))
00455     _reComment = re.compile(_strComment, re.M)
00456 
00457     # escaped or unescaped substitution
00458     #   single-line ("|$" is needed to be able to generate good error-messges)
00459     _strSubstitution = r"""
00460                     (
00461                     %s\s*(?P<sub>.*?)\s*(?P<end>%s|$)       #substitution
00462                     |
00463                     %s\s*(?P<escsub>.*?)\s*(?P<escend>%s|$) #escaped substitution
00464                     )
00465                 """ % (re.escape(_sub_start),    re.escape(_sub_end),
00466                        re.escape(_subesc_start), re.escape(_subesc_end))
00467     _reSubstitution = re.compile(_strSubstitution, re.X|re.M)
00468 
00469     # block
00470     #   - single-line, no nesting.
00471     #   or
00472     #   - multi-line, nested by whitespace indentation:
00473     #       * start- and end-tag of a block must have exactly the same indentation.
00474     #       * start- and end-tags of *nested* blocks should have a greater indentation.
00475     # NOTE: A single-line block must not start at beginning of the line with
00476     #       the same indentation as the enclosing multi-line blocks!
00477     #       Note that "       " and "\t" are different, although they may
00478     #       look the same in an editor!
00479     _s = re.escape(_block_start)
00480     _e = re.escape(_block_end)
00481     _strBlock = r"""
00482                     ^(?P<mEnd>[ \t]*)%send%s(?P<meIgnored>.*)\r?\n?   # multi-line end  (^   <!--(end)-->IGNORED_TEXT\n)
00483                     |
00484                     (?P<sEnd>)%send%s                               # single-line end (<!--(end)-->)
00485                     |
00486                     (?P<sSpace>[ \t]*)                              # single-line tag (no nesting)
00487                     %s(?P<sKeyw>\w+)[ \t]*(?P<sParam>.*?)%s
00488                     (?P<sContent>.*?)
00489                     (?=(?:%s.*?%s.*?)??%send%s)                     # (match until end or i.e. <!--(elif/else...)-->)
00490                     |
00491                                                                     # multi-line tag, nested by whitespace indentation
00492                     ^(?P<indent>[ \t]*)                             #   save indentation of start tag
00493                     %s(?P<mKeyw>\w+)\s*(?P<mParam>.*?)%s(?P<mIgnored>.*)\r?\n
00494                     (?P<mContent>(?:.*\n)*?)
00495                     (?=(?P=indent)%s(?:.|\s)*?%s)                   #   match indentation
00496                 """ % (_s, _e,
00497                        _s, _e,
00498                        _s, _e, _s, _e, _s, _e,
00499                        _s, _e, _s, _e)
00500     _reBlock = re.compile(_strBlock, re.X|re.M)
00501 
00502     # "for"-block parameters: "var(,var)* in ..."
00503     _strForParam = r"""^(?P<names>\w+(?:\s*,\s*\w+)*)\s+in\s+(?P<iter>.+)$"""
00504     _reForParam  = re.compile(_strForParam)
00505 
00506     # allowed macro-names
00507     _reMacroParam = re.compile(r"""^\w+$""")
00508 
00509 
00510     def __init__ (self, loadfunc=None, testexpr=None, escape=HTML):
00511         """Init the parser.
00512 
00513         :Parameters:
00514             - `loadfunc`: function to load included templates
00515               (i.e. ``LoaderFile(...).load``)
00516             - `testexpr`: function to test if a template-expressions is valid
00517               (i.e. ``EvalPseudoSandbox().compile``)
00518             - `escape`:   default-escaping (may be modified by the template)
00519         :Exceptions:
00520             - `ValueError`: if `testexpr` or `escape` is invalid.
00521         """
00522         if loadfunc is None:
00523             self._load  = dummy_raise(NotImplementedError, "'include' not supported, since no 'loadfunc' was given.")
00524         else:
00525             self._load  = loadfunc
00526 
00527         if testexpr is None:
00528             self._testexprfunc  = dummy
00529         else:
00530             try:    # test if testexpr() works
00531                 testexpr("i==1")
00532             except Exception as err:
00533                 raise ValueError("Invalid 'testexpr'. (%s)" %(err))
00534             self._testexprfunc  = testexpr
00535 
00536         if escape not in ESCAPE_SUPPORTED.values():
00537             raise ValueError("Unsupported 'escape'. (%s)" %(escape))
00538         self.escape  = escape
00539         self._includestack  = []
00540 
00541     def parse (self, template):
00542         """Parse a template.
00543 
00544         :Parameters:
00545             - `template`: template-unicode-string
00546         :Returns:         the resulting parse-tree
00547         :Exceptions:
00548             - `TemplateSyntaxError`: for template-syntax-errors
00549             - `TemplateIncludeError`: if template-inclusion failed
00550             - `TemplateException`
00551         """
00552         self._includestack  = [(None, template)]   # for error-messages (_errpos)
00553         return self._parse (template)
00554 
00555     def _errpos(self, fpos):
00556         """Convert `fpos` to ``(filename,row,column)`` for error-messages."""
00557         filename, string = self._includestack [-1]
00558         return filename, srow(string, fpos), scol(string, fpos)
00559 
00560     def _testexpr(self, expr,  fpos=0):
00561         """Test a template-expression to detect errors."""
00562         try:
00563             self._testexprfunc (expr)
00564         except SyntaxError as err:
00565             raise TemplateSyntaxError(err, self._errpos (fpos))
00566 
00567     def _parse_sub(self, parsetree, text, fpos=0):
00568         """Parse substitutions, and append them to the parse-tree.
00569 
00570         Additionally, remove comments.
00571         """
00572         curr = 0
00573         for match in self._reSubstitution .finditer(text):
00574             start = match.start()
00575             if start > curr:
00576                 parsetree.append(("str", self._reComment .sub('', text[curr:start])))
00577 
00578             if match.group("sub") is not None:
00579                 if not match.group("end"):
00580                     raise TemplateSyntaxError("Missing closing tag '%s' for '%s'."
00581                             % (self._sub_end , match.group()), self._errpos (fpos+start))
00582                 if len(match.group("sub")) > 0:
00583                     self._testexpr (match.group("sub"), fpos+start)
00584                     parsetree.append(("sub", match.group("sub")))
00585             else:
00586                 assert(match.group("escsub") is not None)
00587                 if not match.group("escend"):
00588                     raise TemplateSyntaxError("Missing closing tag '%s' for '%s'."
00589                             % (self._subesc_end , match.group()), self._errpos (fpos+start))
00590                 if len(match.group("escsub")) > 0:
00591                     self._testexpr (match.group("escsub"), fpos+start)
00592                     parsetree.append(("esc", self.escape , match.group("escsub")))
00593 
00594             curr = match.end()
00595 
00596         if len(text) > curr:
00597             parsetree.append(("str", self._reComment .sub('', text[curr:])))
00598 
00599     def _parse(self, template, fpos=0):
00600         """Recursive part of `parse()`.
00601 
00602         :Parameters:
00603             - template
00604             - fpos: position of ``template`` in the complete template (for error-messages)
00605         """
00606         # blank out comments
00607         # (So that its content does not collide with other syntax, and
00608         #  because removing them completely would falsify the character-
00609         #  position ("match.start()") of error-messages)
00610         template = self._reComment .sub(lambda match: self._comment_start +" "*len(match.group(1))+match.group(2), template)
00611 
00612         # init parser
00613         parsetree = []
00614         curr = 0            # current position (= end of previous block)
00615         block_type = None   # block type: if,for,macro,raw,...
00616         block_indent = None # None: single-line, >=0: multi-line
00617 
00618         # find blocks
00619         for match in self._reBlock .finditer(template):
00620             start = match.start()
00621             # process template-part before this block
00622             if start > curr:
00623                 self._parse_sub (parsetree, template[curr:start], fpos)
00624 
00625             # analyze block syntax (incl. error-checking and -messages)
00626             keyword = None
00627             block = match.groupdict()
00628             pos__ = fpos + start                # shortcut
00629             if   block["sKeyw"] is not None:    # single-line block tag
00630                 block_indent = None
00631                 keyword = block["sKeyw"]
00632                 param   = block["sParam"]
00633                 content = block["sContent"]
00634                 if block["sSpace"]:             # restore spaces before start-tag
00635                     if len(parsetree) > 0 and parsetree[-1][0] == "str":
00636                         parsetree[-1] = ("str", parsetree[-1][1] + block["sSpace"])
00637                     else:
00638                         parsetree.append(("str", block["sSpace"]))
00639                 pos_p = fpos + match.start("sParam")    # shortcuts
00640                 pos_c = fpos + match.start("sContent")
00641             elif block["mKeyw"] is not None:    # multi-line block tag
00642                 block_indent = len(block["indent"])
00643                 keyword = block["mKeyw"]
00644                 param   = block["mParam"]
00645                 content = block["mContent"]
00646                 pos_p = fpos + match.start("mParam")
00647                 pos_c = fpos + match.start("mContent")
00648                 ignored = block["mIgnored"].strip()
00649                 if ignored  and  ignored != self._comment_start :
00650                     raise TemplateSyntaxError("No code allowed after block-tag.", self._errpos (fpos+match.start("mIgnored")))
00651             elif block["mEnd"] is not None:     # multi-line block end
00652                 if block_type is None:
00653                     raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos (pos__) )
00654                 if block_indent != len(block["mEnd"]):
00655                     raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos (pos__) )
00656                 ignored = block["meIgnored"].strip()
00657                 if ignored  and  ignored != self._comment_start :
00658                     raise TemplateSyntaxError("No code allowed after end-tag.", self._errpos (fpos+match.start("meIgnored")))
00659                 block_type = None
00660             elif block["sEnd"] is not None:     # single-line block end
00661                 if block_type is None:
00662                     raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos (pos__))
00663                 if block_indent is not None:
00664                     raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos (pos__))
00665                 block_type = None
00666             else:
00667                 raise TemplateException("FATAL: Block regexp error. Please contact the author. (%s)" % match.group())
00668 
00669             # analyze block content (mainly error-checking and -messages)
00670             if keyword:
00671                 keyword = keyword.lower()
00672                 if   'for'   == keyword:
00673                     if block_type is not None:
00674                         raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos (pos__))
00675                     block_type = 'for'
00676                     cond = self._reForParam .match(param)
00677                     if cond is None:
00678                         raise TemplateSyntaxError("Invalid 'for ...' at '%s'." %(param), self._errpos (pos_p))
00679                     names = tuple(n.strip()  for n in cond.group("names").split(","))
00680                     self._testexpr (cond.group("iter"), pos_p+cond.start("iter"))
00681                     parsetree.append(("for", names, cond.group("iter"), self._parse (content, pos_c)))
00682                 elif 'if'    == keyword:
00683                     if block_type is not None:
00684                         raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos (pos__))
00685                     if not param:
00686                         raise TemplateSyntaxError("Missing condition for 'if' at '%s'." %(match.group()), self._errpos (pos__))
00687                     block_type = 'if'
00688                     self._testexpr (param, pos_p)
00689                     parsetree.append(("if", param, self._parse (content, pos_c)))
00690                 elif 'elif'  == keyword:
00691                     if block_type != 'if':
00692                         raise TemplateSyntaxError("'elif' may only appear after 'if' at '%s'." %(match.group()), self._errpos (pos__))
00693                     if not param:
00694                         raise TemplateSyntaxError("Missing condition for 'elif' at '%s'." %(match.group()), self._errpos (pos__))
00695                     self._testexpr (param, pos_p)
00696                     parsetree.append(("elif", param, self._parse (content, pos_c)))
00697                 elif 'else'  == keyword:
00698                     if block_type not in ('if', 'for'):
00699                         raise TemplateSyntaxError("'else' may only appear after 'if' or 'for' at '%s'." %(match.group()), self._errpos (pos__))
00700                     if param:
00701                         raise TemplateSyntaxError("'else' may not have parameters at '%s'." %(match.group()), self._errpos (pos__))
00702                     parsetree.append(("else", self._parse (content, pos_c)))
00703                 elif 'macro' == keyword:
00704                     if block_type is not None:
00705                         raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos (pos__))
00706                     block_type = 'macro'
00707                     # make sure param is "\w+" (instead of ".+")
00708                     if not param:
00709                         raise TemplateSyntaxError("Missing name for 'macro' at '%s'." %(match.group()), self._errpos (pos__))
00710                     if not self._reMacroParam .match(param):
00711                         raise TemplateSyntaxError("Invalid name for 'macro' at '%s'." %(match.group()), self._errpos (pos__))
00712                     #remove last newline
00713                     if len(content) > 0 and content[-1] == '\n':
00714                         content = content[:-1]
00715                     if len(content) > 0 and content[-1] == '\r':
00716                         content = content[:-1]
00717                     parsetree.append(("macro", param, self._parse (content, pos_c)))
00718 
00719                 # parser-commands
00720                 elif 'raw'   == keyword:
00721                     if block_type is not None:
00722                         raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos (pos__))
00723                     if param:
00724                         raise TemplateSyntaxError("'raw' may not have parameters at '%s'." %(match.group()), self._errpos (pos__))
00725                     block_type = 'raw'
00726                     parsetree.append(("str", content))
00727                 elif 'include' == keyword:
00728                     if block_type is not None:
00729                         raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos (pos__))
00730                     if param:
00731                         raise TemplateSyntaxError("'include' may not have parameters at '%s'." %(match.group()), self._errpos (pos__))
00732                     block_type = 'include'
00733                     try:
00734                         u = self._load (content.strip())
00735                     except Exception as err:
00736                         raise TemplateIncludeError(err, self._errpos (pos__))
00737                     self._includestack .append((content.strip(), u))  # current filename/template for error-msg.
00738                     p = self._parse (u)
00739                     self._includestack .pop()
00740                     parsetree.extend(p)
00741                 elif 'set_escape' == keyword:
00742                     if block_type is not None:
00743                         raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos (pos__))
00744                     if param:
00745                         raise TemplateSyntaxError("'set_escape' may not have parameters at '%s'." %(match.group()), self._errpos (pos__))
00746                     block_type = 'set_escape'
00747                     esc = content.strip().upper()
00748                     if esc not in ESCAPE_SUPPORTED:
00749                         raise TemplateSyntaxError("Unsupported escape '%s'." %(esc), self._errpos (pos__))
00750                     self.escape  = ESCAPE_SUPPORTED[esc]
00751                 else:
00752                     raise TemplateSyntaxError("Invalid keyword '%s'." %(keyword), self._errpos (pos__))
00753             curr = match.end()
00754 
00755         if block_type is not None:
00756             raise TemplateSyntaxError("Missing end-tag.", self._errpos (pos__))
00757 
00758         if len(template) > curr:            # process template-part after last block
00759             self._parse_sub (parsetree, template[curr:], fpos+curr)
00760 
00761         return parsetree
00762 
00763 #-----------------------------------------
00764 # Evaluation
00765 
00766 # some checks
00767 assert len(eval("dir()", {'__builtins__':{'dir':dir}})) == 1, \
00768     "FATAL: 'eval' does not work as expected (%s)."
00769 assert compile("0 .__class__", "<string>", "eval").co_names == ('__class__',), \
00770     "FATAL: 'compile' does not work as expected."
00771 
00772 class EvalPseudoSandbox :
00773     """An eval-pseudo-sandbox.
00774 
00775     The pseudo-sandbox restricts the available functions/objects, so the
00776     code can only access:
00777 
00778     - some of the builtin Python-functions, which are considered "safe"
00779       (see safe_builtins)
00780     - some additional functions (exists(), default(), setvar(), escape())
00781     - the passed objects incl. their methods.
00782 
00783     Additionally, names beginning with "_" are forbidden.
00784     This is to prevent things like '0 .__class__', with which you could
00785     easily break out of a "sandbox".
00786 
00787     Be careful to only pass "safe" objects/functions to the template,
00788     because any unsafe function/method could break the sandbox!
00789     For maximum security, restrict the access to as few objects/functions
00790     as possible!
00791 
00792     :Warning:
00793         Note that this is no real sandbox! (And although I don't know any
00794         way to break out of the sandbox without passing-in an unsafe object,
00795         I cannot guarantee that there is no such way. So use with care.)
00796 
00797         Take care if you want to use it for untrusted code!!
00798     """
00799 
00800     safe_builtins = {
00801         "True"      : True,
00802         "False"     : False,
00803         "None"      : None,
00804 
00805         "abs"       : builtins.abs,
00806         "chr"       : builtins.chr,
00807         "divmod"    : builtins.divmod,
00808         "hash"      : builtins.hash,
00809         "hex"       : builtins.hex,
00810         "len"       : builtins.len,
00811         "max"       : builtins.max,
00812         "min"       : builtins.min,
00813         "oct"       : builtins.oct,
00814         "ord"       : builtins.ord,
00815         "pow"       : builtins.pow,
00816         "range"     : builtins.range,
00817         "round"     : builtins.round,
00818         "sorted"    : builtins.sorted,
00819         "sum"       : builtins.sum,
00820         "unichr"    : builtins.chr,
00821         "zip"       : builtins.zip,
00822 
00823         "bool"      : builtins.bool,
00824         "bytes"     : builtins.bytes,
00825         "complex"   : builtins.complex,
00826         "dict"      : builtins.dict,
00827         "enumerate" : builtins.enumerate,
00828         "float"     : builtins.float,
00829         "int"       : builtins.int,
00830         "list"      : builtins.list,
00831         "long"      : long,
00832         "reversed"  : builtins.reversed,
00833         "str"       : builtins.str,
00834         "tuple"     : builtins.tuple,
00835         "unicode"   : unicode,
00836     }
00837     if sys.version_info[0] < 3:
00838         safe_builtins["unichr"] = builtins.unichr
00839 
00840     def __init__(self):
00841         self._compile_cache  = {}
00842         self.locals_ptr  = None
00843         self.eval_allowed_globals  = self.safe_builtins .copy()
00844         self.register ("__import__", self.f_import )
00845         self.register ("exists",  self.f_exists )
00846         self.register ("default", self.f_default )
00847         self.register ("setvar",  self.f_setvar )
00848         self.register ("escape",  self.f_escape )
00849 
00850     def register (self, name, obj):
00851         """Add an object to the "allowed eval-globals".
00852 
00853         Mainly useful to add user-defined functions to the pseudo-sandbox.
00854         """
00855         self.eval_allowed_globals [name] = obj
00856 
00857     def compile (self, expr):
00858         """Compile a Python-eval-expression.
00859 
00860         - Use a compile-cache.
00861         - Raise a `NameError` if `expr` contains a name beginning with ``_``.
00862 
00863         :Returns: the compiled `expr`
00864         :Exceptions:
00865             - `SyntaxError`: for compile-errors
00866             - `NameError`: if expr contains a name beginning with ``_``
00867         """
00868         if expr not in self._compile_cache :
00869             c = compile(expr, "", "eval")
00870             for i in c.co_names:    #prevent breakout via new-style-classes
00871                 if i[0] == '_':
00872                     raise NameError("Name '%s' is not allowed." % i)
00873             self._compile_cache [expr] = c
00874         return self._compile_cache [expr]
00875 
00876     def eval (self, expr, locals):
00877         """Eval a Python-eval-expression.
00878 
00879         Sets ``self.locals_ptr`` to ``locales`` and compiles the code
00880         before evaluating.
00881         """
00882         sav = self.locals_ptr 
00883         self.locals_ptr  = locals
00884         x = eval(self.compile (expr), {"__builtins__":self.eval_allowed_globals }, locals)
00885         self.locals_ptr  = sav
00886         return x
00887 
00888     def f_import (self, name, *_, **__):
00889         """``import``/``__import__()`` for the sandboxed code.
00890 
00891         Since "import" is insecure, the PseudoSandbox does not allow to
00892         import other modules. But since some functions need to import
00893         other modules (e.g. "datetime.datetime.strftime" imports "time"),
00894         this function replaces the builtin "import" and allows to use
00895         modules which are already accessible by the sandboxed code.
00896 
00897         :Note:
00898             - This probably only works for rather simple imports.
00899             - For security, it may be better to avoid such (complex) modules
00900               which import other modules. (e.g. use time.localtime and
00901               time.strftime instead of datetime.datetime.strftime,
00902               or write a small wrapper.)
00903 
00904         :Example:
00905 
00906             >>> from datetime import datetime
00907             >>> import pyratemp
00908             >>> t = pyratemp.Template('@!mytime.strftime("%H:%M:%S")!@')
00909 
00910             # >>> print(t(mytime=datetime.now()))
00911             # Traceback (most recent call last):
00912             #   ...
00913             # ImportError: import not allowed in pseudo-sandbox; try to import 'time' yourself and pass it to the sandbox/template
00914 
00915             >>> import time
00916             >>> print(t(mytime=datetime.strptime("13:40:54", "%H:%M:%S"), time=time))
00917             13:40:54
00918 
00919             # >>> print(t(mytime=datetime.now(), time=time))
00920             # 13:40:54
00921         """
00922         import types
00923         if self.locals_ptr  is not None  and  name in self.locals_ptr   and  isinstance(self.locals_ptr [name], types.ModuleType):
00924             return self.locals_ptr [name]
00925         else:
00926             raise ImportError("import not allowed in pseudo-sandbox; try to import '%s' yourself (and maybe pass it to the sandbox/template)" % name)
00927 
00928     def f_exists (self, varname):
00929         """``exists()`` for the sandboxed code.
00930 
00931         Test if the variable `varname` exists in the current locals-namespace.
00932 
00933         This only works for single variable names. If you want to test
00934         complicated expressions, use i.e. `default`.
00935         (i.e. `default("expr",False)`)
00936 
00937         :Note:      the variable-name has to be quoted! (like in eval)
00938         :Example:   see module-docstring
00939         """
00940         return (varname in self.locals_ptr )
00941 
00942     def f_default (self, expr, default=None):
00943         """``default()`` for the sandboxed code.
00944 
00945         Try to evaluate an expression and return the result or a
00946         fallback-/default-value; the `default`-value is used
00947         if `expr` does not exist/is invalid/results in None.
00948 
00949         This is very useful for optional data.
00950 
00951         :Parameter:
00952             - expr: eval-expression
00953             - default: fallback-falue if eval(expr) fails or is None.
00954         :Returns:
00955             the eval-result or the "fallback"-value.
00956 
00957         :Note:      the eval-expression has to be quoted! (like in eval)
00958         :Example:   see module-docstring
00959         """
00960         try:
00961             r = self.eval (expr, self.locals_ptr )
00962             if r is None:
00963                 return default
00964             return r
00965         #TODO: which exceptions should be catched here?
00966         except (NameError, LookupError, TypeError):
00967             return default
00968 
00969     def f_setvar (self, name, expr):
00970         """``setvar()`` for the sandboxed code.
00971 
00972         Set a variable.
00973 
00974         :Example:   see module-docstring
00975         """
00976         self.locals_ptr [name] = self.eval (expr, self.locals_ptr )
00977         return ""
00978 
00979     def f_escape (self, s, format="HTML"):
00980         """``escape()`` for the sandboxed code.
00981         """
00982         if isinstance(format, (str, unicode)):
00983             format = ESCAPE_SUPPORTED[format.upper()]
00984         return escape(unicode(s), format)
00985 
00986 #-----------------------------------------
00987 # basic template / subtemplate
00988 
00989 class TemplateBase :
00990     """Basic template-class.
00991 
00992     Used both for the template itself and for 'macro's ("subtemplates") in
00993     the template.
00994     """
00995 
00996     def __init__ (self, parsetree, renderfunc, data=None):
00997         """Create the Template/Subtemplate/Macro.
00998 
00999         :Parameters:
01000             - `parsetree`: parse-tree of the template/subtemplate/macro
01001             - `renderfunc`: render-function
01002             - `data`: data to fill into the template by default (dictionary).
01003               This data may later be overridden when rendering the template.
01004         :Exceptions:
01005             - `TypeError`: if `data` is not a dictionary
01006         """
01007         #TODO: parameter-checking?
01008         self.parsetree  = parsetree
01009         if isinstance(data, dict):
01010             self.data  = data
01011         elif data is None:
01012             self.data  = {}
01013         else:
01014             raise TypeError('"data" must be a dict (or None).')
01015         self.current_data  = data
01016         self._render  = renderfunc
01017 
01018     def __call__ (self, **override):
01019         """Fill out/render the template.
01020 
01021         :Parameters:
01022             - `override`: objects to add to the data-namespace, overriding
01023               the "default"-data.
01024         :Returns:    the filled template (in unicode)
01025         :Note:       This is also called when invoking macros
01026                      (i.e. ``$!mymacro()!$``).
01027         """
01028         self.current_data  = self.data .copy()
01029         self.current_data .update(override)
01030         u = "".join(self._render (self.parsetree , self.current_data ))
01031         self.current_data  = self.data        # restore current_data
01032         return _dontescape(u)               # (see class _dontescape)
01033 
01034     def __unicode__ (self):
01035         """Alias for __call__()."""
01036         return self.__call__ ()
01037     def __str__ (self):
01038         """Alias for __call__()."""
01039         return self.__call__ ()
01040 
01041 #-----------------------------------------
01042 # Renderer
01043 
01044 class _dontescape (unicode):
01045     """Unicode-string which should not be escaped.
01046 
01047     If ``isinstance(object,_dontescape)``, then don't escape the object in
01048     ``@!...!@``. It's useful for not double-escaping macros, and it's
01049     automatically used for macros/subtemplates.
01050 
01051     :Note: This only works if the object is used on its own in ``@!...!@``.
01052            It i.e. does not work in ``@!object*2!@`` or ``@!object + "hi"!@``.
01053     """
01054     __slots__ = []
01055 
01056 
01057 class Renderer (object):
01058     """Render a template-parse-tree.
01059 
01060     :Uses: `TemplateBase` for macros
01061     """
01062 
01063     def __init__ (self, evalfunc, escapefunc):
01064         """Init the renderer.
01065 
01066         :Parameters:
01067             - `evalfunc`: function for template-expression-evaluation
01068               (i.e. ``EvalPseudoSandbox().eval``)
01069             - `escapefunc`: function for escaping special characters
01070               (i.e. `escape`)
01071         """
01072         #TODO: test evalfunc
01073         self.evalfunc  = evalfunc
01074         self.escapefunc  = escapefunc
01075 
01076     def _eval(self, expr, data):
01077         """evalfunc with error-messages"""
01078         try:
01079             return self.evalfunc (expr, data)
01080         #TODO: any other errors to catch here?
01081         except (TypeError,NameError,LookupError,AttributeError, SyntaxError) as err:
01082             raise TemplateRenderError("Cannot eval expression '%s'. (%s: %s)" %(expr, err.__class__.__name__, err))
01083 
01084     def render (self, parsetree, data):
01085         """Render a parse-tree of a template.
01086 
01087         :Parameters:
01088             - `parsetree`: the parse-tree
01089             - `data`:      the data to fill into the template (dictionary)
01090         :Returns:   the rendered output-unicode-string
01091         :Exceptions:
01092             - `TemplateRenderError`
01093         """
01094         _eval = self._eval   # shortcut
01095         output = []
01096         do_else = False     # use else/elif-branch?
01097 
01098         if parsetree is None:
01099             return ""
01100         for elem in parsetree:
01101             if   "str"   == elem[0]:
01102                 output.append(elem[1])
01103             elif "sub"   == elem[0]:
01104                 output.append(unicode(_eval(elem[1], data)))
01105             elif "esc"   == elem[0]:
01106                 obj = _eval(elem[2], data)
01107                 #prevent double-escape
01108                 if isinstance(obj, _dontescape) or isinstance(obj, TemplateBase):
01109                     output.append(unicode(obj))
01110                 else:
01111                     output.append(self.escapefunc (unicode(obj), elem[1]))
01112             elif "for"   == elem[0]:
01113                 do_else = True
01114                 (names, iterable) = elem[1:3]
01115                 try:
01116                     loop_iter = iter(_eval(iterable, data))
01117                 except TypeError:
01118                     raise TemplateRenderError("Cannot loop over '%s'." % iterable)
01119                 for i in loop_iter:
01120                     do_else = False
01121                     if len(names) == 1:
01122                         data[names[0]] = i
01123                     else:
01124                         data.update(zip(names, i))   #"for a,b,.. in list"
01125                     output.extend(self.render (elem[3], data))
01126             elif "if"    == elem[0]:
01127                 do_else = True
01128                 if _eval(elem[1], data):
01129                     do_else = False
01130                     output.extend(self.render (elem[2], data))
01131             elif "elif"  == elem[0]:
01132                 if do_else and _eval(elem[1], data):
01133                     do_else = False
01134                     output.extend(self.render (elem[2], data))
01135             elif "else"  == elem[0]:
01136                 if do_else:
01137                     do_else = False
01138                     output.extend(self.render (elem[1], data))
01139             elif "macro" == elem[0]:
01140                 data[elem[1]] = TemplateBase(elem[2], self.render , data)
01141             else:
01142                 raise TemplateRenderError("Invalid parse-tree (%s)." %(elem))
01143 
01144         return output
01145 
01146 #-----------------------------------------
01147 # template user-interface (putting it all together)
01148 
01149 class Template (TemplateBase ):
01150     """Template-User-Interface.
01151 
01152     :Usage:
01153         ::
01154             t = Template(...)  (<- see __init__)
01155             output = t(...)    (<- see TemplateBase.__call__)
01156 
01157     :Example:
01158         see module-docstring
01159     """
01160 
01161     def __init__ (self, string=None,filename=None,parsetree=None, encoding='utf-8', data=None, escape=HTML,
01162             loader_class=LoaderFile,
01163             parser_class=Parser,
01164             renderer_class=Renderer,
01165             eval_class=EvalPseudoSandbox,
01166             escape_func=escape):
01167         """Load (+parse) a template.
01168 
01169         :Parameters:
01170             - `string,filename,parsetree`: a template-string,
01171                                            filename of a template to load,
01172                                            or a template-parsetree.
01173                                            (only one of these 3 is allowed)
01174             - `encoding`: encoding of the template-files (only used for "filename")
01175             - `data`:     data to fill into the template by default (dictionary).
01176                           This data may later be overridden when rendering the template.
01177             - `escape`:   default-escaping for the template, may be overwritten by the template!
01178             - `loader_class`
01179             - `parser_class`
01180             - `renderer_class`
01181             - `eval_class`
01182             - `escapefunc`
01183         """
01184         if [string, filename, parsetree].count(None) != 2:
01185             raise ValueError('Exactly 1 of string,filename,parsetree is necessary.')
01186 
01187         tmpl = None
01188         # load template
01189         if filename is not None:
01190             incl_load = loader_class(os.path.dirname(filename), encoding).load
01191             tmpl = incl_load(os.path.basename(filename))
01192         if string is not None:
01193             incl_load = dummy_raise(NotImplementedError, "'include' not supported for template-strings.")
01194             tmpl = LoaderString(encoding).load(string)
01195 
01196         # eval (incl. compile-cache)
01197         templateeval = eval_class()
01198 
01199         # parse
01200         if tmpl is not None:
01201             p = parser_class(loadfunc=incl_load, testexpr=templateeval.compile, escape=escape)
01202             parsetree = p.parse(tmpl)
01203             del p
01204 
01205         # renderer
01206         renderfunc = renderer_class(templateeval.eval, escape_func).render
01207 
01208         #create template
01209         TemplateBase.__init__(self, parsetree, renderfunc, data)
01210 
01211 
01212 #=========================================
01213 #doctest
01214 
01215 def _doctest():
01216     """doctest this module."""
01217     import doctest
01218     doctest.testmod()
01219 
01220 #----------------------
01221 if __name__ == '__main__':
01222     if sys.version_info[0] <= 2:
01223         _doctest()
01224 
01225 #=========================================
01226