pyratemp.py 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Small, simple and powerful template-engine for Python.
  5. A template-engine for Python, which is very simple, easy to use, small,
  6. fast, powerful, modular, extensible, well documented and pythonic.
  7. See documentation for a list of features, template-syntax etc.
  8. :Version: 0.3.0
  9. :Requires: Python >=2.6 / 3.x
  10. :Usage:
  11. see class ``Template`` and examples below.
  12. :Example:
  13. Note that the examples are in Python 2; they also work in
  14. Python 3 if you replace u"..." by "...", unicode() by str()
  15. and partly "..." by b"...".
  16. quickstart::
  17. >>> t = Template("hello @!name!@")
  18. >>> print(t(name="marvin"))
  19. hello marvin
  20. quickstart with a template-file::
  21. # >>> t = Template(filename="mytemplate.tmpl")
  22. # >>> print(t(name="marvin"))
  23. # hello marvin
  24. generic usage::
  25. >>> t = Template(u"output is in Unicode \\xe4\\xf6\\xfc\\u20ac")
  26. >>> t #doctest: +ELLIPSIS
  27. <...Template instance at 0x...>
  28. >>> t()
  29. u'output is in Unicode \\xe4\\xf6\\xfc\\u20ac'
  30. >>> unicode(t)
  31. u'output is in Unicode \\xe4\\xf6\\xfc\\u20ac'
  32. with data::
  33. >>> t = Template("hello @!name!@", data={"name":"world"})
  34. >>> t()
  35. u'hello world'
  36. >>> t(name="worlds")
  37. u'hello worlds'
  38. # >>> t(note="data must be Unicode or ASCII", name=u"\\xe4")
  39. # u'hello \\xe4'
  40. escaping::
  41. >>> t = Template("hello escaped: @!name!@, unescaped: $!name!$")
  42. >>> t(name='''<>&'"''')
  43. u'hello escaped: &lt;&gt;&amp;&#39;&quot;, unescaped: <>&\\'"'
  44. result-encoding::
  45. # encode the unicode-object to your encoding with encode()
  46. >>> t = Template(u"hello \\xe4\\xf6\\xfc\\u20ac")
  47. >>> result = t()
  48. >>> result
  49. u'hello \\xe4\\xf6\\xfc\\u20ac'
  50. >>> result.encode("utf-8")
  51. 'hello \\xc3\\xa4\\xc3\\xb6\\xc3\\xbc\\xe2\\x82\\xac'
  52. >>> result.encode("ascii")
  53. Traceback (most recent call last):
  54. ...
  55. UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-9: ordinal not in range(128)
  56. >>> result.encode("ascii", 'xmlcharrefreplace')
  57. 'hello &#228;&#246;&#252;&#8364;'
  58. Python-expressions::
  59. >>> Template('formatted: @! "%8.5f" % value !@')(value=3.141592653)
  60. u'formatted: 3.14159'
  61. >>> Template("hello --@!name.upper().center(20)!@--")(name="world")
  62. u'hello -- WORLD --'
  63. >>> Template("calculate @!var*5+7!@")(var=7)
  64. u'calculate 42'
  65. blocks (if/for/macros/...)::
  66. >>> t = Template("<!--(if foo == 1)-->bar<!--(elif foo == 2)-->baz<!--(else)-->unknown(@!foo!@)<!--(end)-->")
  67. >>> t(foo=2)
  68. u'baz'
  69. >>> t(foo=5)
  70. u'unknown(5)'
  71. >>> t = Template("<!--(for i in mylist)-->@!i!@ <!--(else)-->(empty)<!--(end)-->")
  72. >>> t(mylist=[])
  73. u'(empty)'
  74. >>> t(mylist=[1,2,3])
  75. u'1 2 3 '
  76. >>> t = Template("<!--(for i,elem in enumerate(mylist))--> - @!i!@: @!elem!@<!--(end)-->")
  77. >>> t(mylist=["a","b","c"])
  78. u' - 0: a - 1: b - 2: c'
  79. >>> t = Template('<!--(macro greetings)-->hello <strong>@!name!@</strong><!--(end)--> @!greetings(name=user)!@')
  80. >>> t(user="monty")
  81. u' hello <strong>monty</strong>'
  82. exists::
  83. >>> t = Template('<!--(if exists("foo"))-->YES<!--(else)-->NO<!--(end)-->')
  84. >>> t()
  85. u'NO'
  86. >>> t(foo=1)
  87. u'YES'
  88. >>> t(foo=None) # note this difference to 'default()'
  89. u'YES'
  90. default-values::
  91. # non-existing variables raise an error
  92. >>> Template('hi @!optional!@')()
  93. Traceback (most recent call last):
  94. ...
  95. TemplateRenderError: Cannot eval expression 'optional'. (NameError: name 'optional' is not defined)
  96. >>> t = Template('hi @!default("optional","anyone")!@')
  97. >>> t()
  98. u'hi anyone'
  99. >>> t(optional=None)
  100. u'hi anyone'
  101. >>> t(optional="there")
  102. u'hi there'
  103. # the 1st parameter can be any eval-expression
  104. >>> t = Template('@!default("5*var1+var2","missing variable")!@')
  105. >>> t(var1=10)
  106. u'missing variable'
  107. >>> t(var1=10, var2=2)
  108. u'52'
  109. # also in blocks
  110. >>> t = Template('<!--(if default("opt1+opt2",0) > 0)-->yes<!--(else)-->no<!--(end)-->')
  111. >>> t()
  112. u'no'
  113. >>> t(opt1=23, opt2=42)
  114. u'yes'
  115. >>> t = Template('<!--(for i in default("optional_list",[]))-->@!i!@<!--(end)-->')
  116. >>> t()
  117. u''
  118. >>> t(optional_list=[1,2,3])
  119. u'123'
  120. # but make sure to put the expression in quotation marks, otherwise:
  121. >>> Template('@!default(optional,"fallback")!@')()
  122. Traceback (most recent call last):
  123. ...
  124. TemplateRenderError: Cannot eval expression 'default(optional,"fallback")'. (NameError: name 'optional' is not defined)
  125. setvar::
  126. >>> t = Template('$!setvar("i", "i+1")!$@!i!@')
  127. >>> t(i=6)
  128. u'7'
  129. >>> t = Template('''<!--(if isinstance(s, (list,tuple)))-->$!setvar("s", '"\\\\\\\\n".join(s)')!$<!--(end)-->@!s!@''')
  130. >>> t(isinstance=isinstance, s="123")
  131. u'123'
  132. >>> t(isinstance=isinstance, s=["123", "456"])
  133. u'123\\n456'
  134. :Author: Roland Koebler (rk at simple-is-better dot org)
  135. :Copyright: Roland Koebler
  136. :License: MIT/X11-like, see __license__
  137. :RCS: $Id: pyratemp.py,v 1.12 2013/04/02 20:26:06 rk Exp $
  138. """
  139. from __future__ import unicode_literals
  140. __version__ = "0.3.0"
  141. __author__ = "Roland Koebler <rk at simple-is-better dot org>"
  142. __license__ = """Copyright (c) Roland Koebler, 2007-2013
  143. Permission is hereby granted, free of charge, to any person obtaining a copy
  144. of this software and associated documentation files (the "Software"), to deal
  145. in the Software without restriction, including without limitation the rights
  146. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  147. copies of the Software, and to permit persons to whom the Software is
  148. furnished to do so, subject to the following conditions:
  149. The above copyright notice and this permission notice shall be included in
  150. all copies or substantial portions of the Software.
  151. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  152. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  153. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  154. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  155. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  156. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  157. IN THE SOFTWARE."""
  158. #=========================================
  159. import os, re, sys
  160. if sys.version_info[0] >= 3:
  161. import builtins
  162. unicode = str
  163. long = int
  164. else:
  165. import __builtin__ as builtins
  166. from codecs import open
  167. #=========================================
  168. # some useful functions
  169. #----------------------
  170. # string-position: i <-> row,col
  171. def srow(string, i):
  172. """Get line numer of ``string[i]`` in `string`.
  173. :Returns: row, starting at 1
  174. :Note: This works for text-strings with ``\\n`` or ``\\r\\n``.
  175. """
  176. return string.count('\n', 0, max(0, i)) + 1
  177. def scol(string, i):
  178. """Get column number of ``string[i]`` in `string`.
  179. :Returns: column, starting at 1 (but may be <1 if i<0)
  180. :Note: This works for text-strings with ``\\n`` or ``\\r\\n``.
  181. """
  182. return i - string.rfind('\n', 0, max(0, i))
  183. def sindex(string, row, col):
  184. """Get index of the character at `row`/`col` in `string`.
  185. :Parameters:
  186. - `row`: row number, starting at 1.
  187. - `col`: column number, starting at 1.
  188. :Returns: ``i``, starting at 0 (but may be <1 if row/col<0)
  189. :Note: This works for text-strings with '\\n' or '\\r\\n'.
  190. """
  191. n = 0
  192. for _ in range(row-1):
  193. n = string.find('\n', n) + 1
  194. return n+col-1
  195. #----------------------
  196. def dictkeyclean(d):
  197. """Convert all keys of the dict `d` to strings.
  198. """
  199. new_d = {}
  200. for k, v in d.items():
  201. new_d[str(k)] = v
  202. return new_d
  203. #----------------------
  204. def dummy(*_, **__):
  205. """Dummy function, doing nothing.
  206. """
  207. pass
  208. def dummy_raise(exception, value):
  209. """Create an exception-raising dummy function.
  210. :Returns: dummy function, raising ``exception(value)``
  211. """
  212. def mydummy(*_, **__):
  213. raise exception(value)
  214. return mydummy
  215. #=========================================
  216. # escaping
  217. (NONE, HTML, LATEX, MAIL_HEADER) = range(0, 4)
  218. ESCAPE_SUPPORTED = {"NONE":None, "HTML":HTML, "LATEX":LATEX, "MAIL_HEADER":MAIL_HEADER}
  219. def escape(s, format=HTML):
  220. """Replace special characters by their escape sequence.
  221. :Parameters:
  222. - `s`: unicode-string to escape
  223. - `format`:
  224. - `NONE`: nothing is replaced
  225. - `HTML`: replace &<>'" by &...;
  226. - `LATEX`: replace \#$%&_{}~^
  227. - `MAIL_HEADER`: escape non-ASCII mail-header-contents
  228. :Returns:
  229. the escaped string in unicode
  230. :Exceptions:
  231. - `ValueError`: if `format` is invalid.
  232. :Uses:
  233. MAIL_HEADER uses module email
  234. """
  235. #Note: If you have to make sure that every character gets replaced
  236. # only once (and if you cannot achieve this with the following code),
  237. # use something like "".join([replacedict.get(c,c) for c in s])
  238. # which is about 2-3 times slower (but maybe needs less memory).
  239. #Note: This is one of the most time-consuming parts of the template.
  240. if format is None or format == NONE:
  241. pass
  242. elif format == HTML:
  243. s = s.replace("&", "&amp;") # must be done first!
  244. s = s.replace("<", "&lt;")
  245. s = s.replace(">", "&gt;")
  246. s = s.replace('"', "&quot;")
  247. s = s.replace("'", "&#39;")
  248. elif format == LATEX:
  249. s = s.replace("\\", "\\x") #must be done first!
  250. s = s.replace("#", "\\#")
  251. s = s.replace("$", "\\$")
  252. s = s.replace("%", "\\%")
  253. s = s.replace("&", "\\&")
  254. s = s.replace("_", "\\_")
  255. s = s.replace("{", "\\{")
  256. s = s.replace("}", "\\}")
  257. s = s.replace("\\x","\\textbackslash{}")
  258. s = s.replace("~", "\\textasciitilde{}")
  259. s = s.replace("^", "\\textasciicircum{}")
  260. elif format == MAIL_HEADER:
  261. import email.header
  262. try:
  263. s.encode("ascii")
  264. return s
  265. except UnicodeEncodeError:
  266. return email.header.make_header([(s, "utf-8")]).encode()
  267. else:
  268. raise ValueError('Invalid format (only None, HTML, LATEX and MAIL_HEADER are supported).')
  269. return s
  270. #=========================================
  271. #-----------------------------------------
  272. # Exceptions
  273. class TemplateException(Exception):
  274. """Base class for template-exceptions."""
  275. pass
  276. class TemplateParseError(TemplateException):
  277. """Template parsing failed."""
  278. def __init__(self, err, errpos):
  279. """
  280. :Parameters:
  281. - `err`: error-message or exception to wrap
  282. - `errpos`: ``(filename,row,col)`` where the error occured.
  283. """
  284. self.err = err
  285. self.filename, self.row, self.col = errpos
  286. TemplateException.__init__(self)
  287. def __str__(self):
  288. if not self.filename:
  289. return "line %d, col %d: %s" % (self.row, self.col, str(self.err))
  290. else:
  291. return "file %s, line %d, col %d: %s" % (self.filename, self.row, self.col, str(self.err))
  292. class TemplateSyntaxError(TemplateParseError, SyntaxError):
  293. """Template syntax-error."""
  294. pass
  295. class TemplateIncludeError(TemplateParseError):
  296. """Template 'include' failed."""
  297. pass
  298. class TemplateRenderError(TemplateException):
  299. """Template rendering failed."""
  300. pass
  301. #-----------------------------------------
  302. # Loader
  303. class LoaderString:
  304. """Load template from a string/unicode.
  305. Note that 'include' is not possible in such templates.
  306. """
  307. def __init__(self, encoding='utf-8'):
  308. self.encoding = encoding
  309. def load(self, s):
  310. """Return template-string as unicode.
  311. """
  312. if isinstance(s, unicode):
  313. u = s
  314. else:
  315. u = s.decode(self.encoding)
  316. return u
  317. class LoaderFile:
  318. """Load template from a file.
  319. When loading a template from a file, it's possible to including other
  320. templates (by using 'include' in the template). But for simplicity
  321. and security, all included templates have to be in the same directory!
  322. (see ``allowed_path``)
  323. """
  324. def __init__(self, allowed_path=None, encoding='utf-8'):
  325. """Init the loader.
  326. :Parameters:
  327. - `allowed_path`: path of the template-files
  328. - `encoding`: encoding of the template-files
  329. :Exceptions:
  330. - `ValueError`: if `allowed_path` is not a directory
  331. """
  332. if allowed_path and not os.path.isdir(allowed_path):
  333. raise ValueError("'allowed_path' has to be a directory.")
  334. self.path = allowed_path
  335. self.encoding = encoding
  336. def load(self, filename):
  337. """Load a template from a file.
  338. Check if filename is allowed and return its contens in unicode.
  339. :Parameters:
  340. - `filename`: filename of the template without path
  341. :Returns:
  342. the contents of the template-file in unicode
  343. :Exceptions:
  344. - `ValueError`: if `filename` contains a path
  345. """
  346. if filename != os.path.basename(filename):
  347. raise ValueError("No path allowed in filename. (%s)" %(filename))
  348. filename = os.path.join(self.path, filename)
  349. f = open(filename, 'r', encoding=self.encoding)
  350. u = f.read()
  351. f.close()
  352. return u
  353. #-----------------------------------------
  354. # Parser
  355. class Parser(object):
  356. """Parse a template into a parse-tree.
  357. Includes a syntax-check, an optional expression-check and verbose
  358. error-messages.
  359. See documentation for a description of the parse-tree.
  360. """
  361. # template-syntax
  362. _comment_start = "#!"
  363. _comment_end = "!#"
  364. _sub_start = "$!"
  365. _sub_end = "!$"
  366. _subesc_start = "@!"
  367. _subesc_end = "!@"
  368. _block_start = "<!--("
  369. _block_end = ")-->"
  370. # build regexps
  371. # comment
  372. # single-line, until end-tag or end-of-line.
  373. _strComment = r"""%s(?P<content>.*?)(?P<end>%s|\n|$)""" \
  374. % (re.escape(_comment_start), re.escape(_comment_end))
  375. _reComment = re.compile(_strComment, re.M)
  376. # escaped or unescaped substitution
  377. # single-line ("|$" is needed to be able to generate good error-messges)
  378. _strSubstitution = r"""
  379. (
  380. %s\s*(?P<sub>.*?)\s*(?P<end>%s|$) #substitution
  381. |
  382. %s\s*(?P<escsub>.*?)\s*(?P<escend>%s|$) #escaped substitution
  383. )
  384. """ % (re.escape(_sub_start), re.escape(_sub_end),
  385. re.escape(_subesc_start), re.escape(_subesc_end))
  386. _reSubstitution = re.compile(_strSubstitution, re.X|re.M)
  387. # block
  388. # - single-line, no nesting.
  389. # or
  390. # - multi-line, nested by whitespace indentation:
  391. # * start- and end-tag of a block must have exactly the same indentation.
  392. # * start- and end-tags of *nested* blocks should have a greater indentation.
  393. # NOTE: A single-line block must not start at beginning of the line with
  394. # the same indentation as the enclosing multi-line blocks!
  395. # Note that " " and "\t" are different, although they may
  396. # look the same in an editor!
  397. _s = re.escape(_block_start)
  398. _e = re.escape(_block_end)
  399. _strBlock = r"""
  400. ^(?P<mEnd>[ \t]*)%send%s(?P<meIgnored>.*)\r?\n? # multi-line end (^ <!--(end)-->IGNORED_TEXT\n)
  401. |
  402. (?P<sEnd>)%send%s # single-line end (<!--(end)-->)
  403. |
  404. (?P<sSpace>[ \t]*) # single-line tag (no nesting)
  405. %s(?P<sKeyw>\w+)[ \t]*(?P<sParam>.*?)%s
  406. (?P<sContent>.*?)
  407. (?=(?:%s.*?%s.*?)??%send%s) # (match until end or i.e. <!--(elif/else...)-->)
  408. |
  409. # multi-line tag, nested by whitespace indentation
  410. ^(?P<indent>[ \t]*) # save indentation of start tag
  411. %s(?P<mKeyw>\w+)\s*(?P<mParam>.*?)%s(?P<mIgnored>.*)\r?\n
  412. (?P<mContent>(?:.*\n)*?)
  413. (?=(?P=indent)%s(?:.|\s)*?%s) # match indentation
  414. """ % (_s, _e,
  415. _s, _e,
  416. _s, _e, _s, _e, _s, _e,
  417. _s, _e, _s, _e)
  418. _reBlock = re.compile(_strBlock, re.X|re.M)
  419. # "for"-block parameters: "var(,var)* in ..."
  420. _strForParam = r"""^(?P<names>\w+(?:\s*,\s*\w+)*)\s+in\s+(?P<iter>.+)$"""
  421. _reForParam = re.compile(_strForParam)
  422. # allowed macro-names
  423. _reMacroParam = re.compile(r"""^\w+$""")
  424. def __init__(self, loadfunc=None, testexpr=None, escape=HTML):
  425. """Init the parser.
  426. :Parameters:
  427. - `loadfunc`: function to load included templates
  428. (i.e. ``LoaderFile(...).load``)
  429. - `testexpr`: function to test if a template-expressions is valid
  430. (i.e. ``EvalPseudoSandbox().compile``)
  431. - `escape`: default-escaping (may be modified by the template)
  432. :Exceptions:
  433. - `ValueError`: if `testexpr` or `escape` is invalid.
  434. """
  435. if loadfunc is None:
  436. self._load = dummy_raise(NotImplementedError, "'include' not supported, since no 'loadfunc' was given.")
  437. else:
  438. self._load = loadfunc
  439. if testexpr is None:
  440. self._testexprfunc = dummy
  441. else:
  442. try: # test if testexpr() works
  443. testexpr("i==1")
  444. except Exception as err:
  445. raise ValueError("Invalid 'testexpr'. (%s)" %(err))
  446. self._testexprfunc = testexpr
  447. if escape not in ESCAPE_SUPPORTED.values():
  448. raise ValueError("Unsupported 'escape'. (%s)" %(escape))
  449. self.escape = escape
  450. self._includestack = []
  451. def parse(self, template):
  452. """Parse a template.
  453. :Parameters:
  454. - `template`: template-unicode-string
  455. :Returns: the resulting parse-tree
  456. :Exceptions:
  457. - `TemplateSyntaxError`: for template-syntax-errors
  458. - `TemplateIncludeError`: if template-inclusion failed
  459. - `TemplateException`
  460. """
  461. self._includestack = [(None, template)] # for error-messages (_errpos)
  462. return self._parse(template)
  463. def _errpos(self, fpos):
  464. """Convert `fpos` to ``(filename,row,column)`` for error-messages."""
  465. filename, string = self._includestack[-1]
  466. return filename, srow(string, fpos), scol(string, fpos)
  467. def _testexpr(self, expr, fpos=0):
  468. """Test a template-expression to detect errors."""
  469. try:
  470. self._testexprfunc(expr)
  471. except SyntaxError as err:
  472. raise TemplateSyntaxError(err, self._errpos(fpos))
  473. def _parse_sub(self, parsetree, text, fpos=0):
  474. """Parse substitutions, and append them to the parse-tree.
  475. Additionally, remove comments.
  476. """
  477. curr = 0
  478. for match in self._reSubstitution.finditer(text):
  479. start = match.start()
  480. if start > curr:
  481. parsetree.append(("str", self._reComment.sub('', text[curr:start])))
  482. if match.group("sub") is not None:
  483. if not match.group("end"):
  484. raise TemplateSyntaxError("Missing closing tag '%s' for '%s'."
  485. % (self._sub_end, match.group()), self._errpos(fpos+start))
  486. if len(match.group("sub")) > 0:
  487. self._testexpr(match.group("sub"), fpos+start)
  488. parsetree.append(("sub", match.group("sub")))
  489. else:
  490. assert(match.group("escsub") is not None)
  491. if not match.group("escend"):
  492. raise TemplateSyntaxError("Missing closing tag '%s' for '%s'."
  493. % (self._subesc_end, match.group()), self._errpos(fpos+start))
  494. if len(match.group("escsub")) > 0:
  495. self._testexpr(match.group("escsub"), fpos+start)
  496. parsetree.append(("esc", self.escape, match.group("escsub")))
  497. curr = match.end()
  498. if len(text) > curr:
  499. parsetree.append(("str", self._reComment.sub('', text[curr:])))
  500. def _parse(self, template, fpos=0):
  501. """Recursive part of `parse()`.
  502. :Parameters:
  503. - template
  504. - fpos: position of ``template`` in the complete template (for error-messages)
  505. """
  506. # blank out comments
  507. # (So that its content does not collide with other syntax, and
  508. # because removing them completely would falsify the character-
  509. # position ("match.start()") of error-messages)
  510. template = self._reComment.sub(lambda match: self._comment_start+" "*len(match.group(1))+match.group(2), template)
  511. # init parser
  512. parsetree = []
  513. curr = 0 # current position (= end of previous block)
  514. block_type = None # block type: if,for,macro,raw,...
  515. block_indent = None # None: single-line, >=0: multi-line
  516. # find blocks
  517. for match in self._reBlock.finditer(template):
  518. start = match.start()
  519. # process template-part before this block
  520. if start > curr:
  521. self._parse_sub(parsetree, template[curr:start], fpos)
  522. # analyze block syntax (incl. error-checking and -messages)
  523. keyword = None
  524. block = match.groupdict()
  525. pos__ = fpos + start # shortcut
  526. if block["sKeyw"] is not None: # single-line block tag
  527. block_indent = None
  528. keyword = block["sKeyw"]
  529. param = block["sParam"]
  530. content = block["sContent"]
  531. if block["sSpace"]: # restore spaces before start-tag
  532. if len(parsetree) > 0 and parsetree[-1][0] == "str":
  533. parsetree[-1] = ("str", parsetree[-1][1] + block["sSpace"])
  534. else:
  535. parsetree.append(("str", block["sSpace"]))
  536. pos_p = fpos + match.start("sParam") # shortcuts
  537. pos_c = fpos + match.start("sContent")
  538. elif block["mKeyw"] is not None: # multi-line block tag
  539. block_indent = len(block["indent"])
  540. keyword = block["mKeyw"]
  541. param = block["mParam"]
  542. content = block["mContent"]
  543. pos_p = fpos + match.start("mParam")
  544. pos_c = fpos + match.start("mContent")
  545. ignored = block["mIgnored"].strip()
  546. if ignored and ignored != self._comment_start:
  547. raise TemplateSyntaxError("No code allowed after block-tag.", self._errpos(fpos+match.start("mIgnored")))
  548. elif block["mEnd"] is not None: # multi-line block end
  549. if block_type is None:
  550. raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos(pos__) )
  551. if block_indent != len(block["mEnd"]):
  552. raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos(pos__) )
  553. ignored = block["meIgnored"].strip()
  554. if ignored and ignored != self._comment_start:
  555. raise TemplateSyntaxError("No code allowed after end-tag.", self._errpos(fpos+match.start("meIgnored")))
  556. block_type = None
  557. elif block["sEnd"] is not None: # single-line block end
  558. if block_type is None:
  559. raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos(pos__))
  560. if block_indent is not None:
  561. raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos(pos__))
  562. block_type = None
  563. else:
  564. raise TemplateException("FATAL: Block regexp error. Please contact the author. (%s)" % match.group())
  565. # analyze block content (mainly error-checking and -messages)
  566. if keyword:
  567. keyword = keyword.lower()
  568. if 'for' == keyword:
  569. if block_type is not None:
  570. raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos(pos__))
  571. block_type = 'for'
  572. cond = self._reForParam.match(param)
  573. if cond is None:
  574. raise TemplateSyntaxError("Invalid 'for ...' at '%s'." %(param), self._errpos(pos_p))
  575. names = tuple(n.strip() for n in cond.group("names").split(","))
  576. self._testexpr(cond.group("iter"), pos_p+cond.start("iter"))
  577. parsetree.append(("for", names, cond.group("iter"), self._parse(content, pos_c)))
  578. elif 'if' == keyword:
  579. if block_type is not None:
  580. raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos(pos__))
  581. if not param:
  582. raise TemplateSyntaxError("Missing condition for 'if' at '%s'." %(match.group()), self._errpos(pos__))
  583. block_type = 'if'
  584. self._testexpr(param, pos_p)
  585. parsetree.append(("if", param, self._parse(content, pos_c)))
  586. elif 'elif' == keyword:
  587. if block_type != 'if':
  588. raise TemplateSyntaxError("'elif' may only appear after 'if' at '%s'." %(match.group()), self._errpos(pos__))
  589. if not param:
  590. raise TemplateSyntaxError("Missing condition for 'elif' at '%s'." %(match.group()), self._errpos(pos__))
  591. self._testexpr(param, pos_p)
  592. parsetree.append(("elif", param, self._parse(content, pos_c)))
  593. elif 'else' == keyword:
  594. if block_type not in ('if', 'for'):
  595. raise TemplateSyntaxError("'else' may only appear after 'if' or 'for' at '%s'." %(match.group()), self._errpos(pos__))
  596. if param:
  597. raise TemplateSyntaxError("'else' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
  598. parsetree.append(("else", self._parse(content, pos_c)))
  599. elif 'macro' == keyword:
  600. if block_type is not None:
  601. raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
  602. block_type = 'macro'
  603. # make sure param is "\w+" (instead of ".+")
  604. if not param:
  605. raise TemplateSyntaxError("Missing name for 'macro' at '%s'." %(match.group()), self._errpos(pos__))
  606. if not self._reMacroParam.match(param):
  607. raise TemplateSyntaxError("Invalid name for 'macro' at '%s'." %(match.group()), self._errpos(pos__))
  608. #remove last newline
  609. if len(content) > 0 and content[-1] == '\n':
  610. content = content[:-1]
  611. if len(content) > 0 and content[-1] == '\r':
  612. content = content[:-1]
  613. parsetree.append(("macro", param, self._parse(content, pos_c)))
  614. # parser-commands
  615. elif 'raw' == keyword:
  616. if block_type is not None:
  617. raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
  618. if param:
  619. raise TemplateSyntaxError("'raw' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
  620. block_type = 'raw'
  621. parsetree.append(("str", content))
  622. elif 'include' == keyword:
  623. if block_type is not None:
  624. raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
  625. if param:
  626. raise TemplateSyntaxError("'include' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
  627. block_type = 'include'
  628. try:
  629. u = self._load(content.strip())
  630. except Exception as err:
  631. raise TemplateIncludeError(err, self._errpos(pos__))
  632. self._includestack.append((content.strip(), u)) # current filename/template for error-msg.
  633. p = self._parse(u)
  634. self._includestack.pop()
  635. parsetree.extend(p)
  636. elif 'set_escape' == keyword:
  637. if block_type is not None:
  638. raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
  639. if param:
  640. raise TemplateSyntaxError("'set_escape' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
  641. block_type = 'set_escape'
  642. esc = content.strip().upper()
  643. if esc not in ESCAPE_SUPPORTED:
  644. raise TemplateSyntaxError("Unsupported escape '%s'." %(esc), self._errpos(pos__))
  645. self.escape = ESCAPE_SUPPORTED[esc]
  646. else:
  647. raise TemplateSyntaxError("Invalid keyword '%s'." %(keyword), self._errpos(pos__))
  648. curr = match.end()
  649. if block_type is not None:
  650. raise TemplateSyntaxError("Missing end-tag.", self._errpos(pos__))
  651. if len(template) > curr: # process template-part after last block
  652. self._parse_sub(parsetree, template[curr:], fpos+curr)
  653. return parsetree
  654. #-----------------------------------------
  655. # Evaluation
  656. # some checks
  657. assert len(eval("dir()", {'__builtins__':{'dir':dir}})) == 1, \
  658. "FATAL: 'eval' does not work as expected (%s)."
  659. assert compile("0 .__class__", "<string>", "eval").co_names == ('__class__',), \
  660. "FATAL: 'compile' does not work as expected."
  661. class EvalPseudoSandbox:
  662. """An eval-pseudo-sandbox.
  663. The pseudo-sandbox restricts the available functions/objects, so the
  664. code can only access:
  665. - some of the builtin Python-functions, which are considered "safe"
  666. (see safe_builtins)
  667. - some additional functions (exists(), default(), setvar(), escape())
  668. - the passed objects incl. their methods.
  669. Additionally, names beginning with "_" are forbidden.
  670. This is to prevent things like '0 .__class__', with which you could
  671. easily break out of a "sandbox".
  672. Be careful to only pass "safe" objects/functions to the template,
  673. because any unsafe function/method could break the sandbox!
  674. For maximum security, restrict the access to as few objects/functions
  675. as possible!
  676. :Warning:
  677. Note that this is no real sandbox! (And although I don't know any
  678. way to break out of the sandbox without passing-in an unsafe object,
  679. I cannot guarantee that there is no such way. So use with care.)
  680. Take care if you want to use it for untrusted code!!
  681. """
  682. safe_builtins = {
  683. "True" : True,
  684. "False" : False,
  685. "None" : None,
  686. "abs" : builtins.abs,
  687. "chr" : builtins.chr,
  688. "divmod" : builtins.divmod,
  689. "hash" : builtins.hash,
  690. "hex" : builtins.hex,
  691. "len" : builtins.len,
  692. "max" : builtins.max,
  693. "min" : builtins.min,
  694. "oct" : builtins.oct,
  695. "ord" : builtins.ord,
  696. "pow" : builtins.pow,
  697. "range" : builtins.range,
  698. "round" : builtins.round,
  699. "sorted" : builtins.sorted,
  700. "sum" : builtins.sum,
  701. "unichr" : builtins.chr,
  702. "zip" : builtins.zip,
  703. "bool" : builtins.bool,
  704. "bytes" : builtins.bytes,
  705. "complex" : builtins.complex,
  706. "dict" : builtins.dict,
  707. "enumerate" : builtins.enumerate,
  708. "float" : builtins.float,
  709. "int" : builtins.int,
  710. "list" : builtins.list,
  711. "long" : long,
  712. "reversed" : builtins.reversed,
  713. "str" : builtins.str,
  714. "tuple" : builtins.tuple,
  715. "unicode" : unicode,
  716. }
  717. if sys.version_info[0] < 3:
  718. safe_builtins["unichr"] = builtins.unichr
  719. def __init__(self):
  720. self._compile_cache = {}
  721. self.locals_ptr = None
  722. self.eval_allowed_globals = self.safe_builtins.copy()
  723. self.register("__import__", self.f_import)
  724. self.register("exists", self.f_exists)
  725. self.register("default", self.f_default)
  726. self.register("setvar", self.f_setvar)
  727. self.register("escape", self.f_escape)
  728. def register(self, name, obj):
  729. """Add an object to the "allowed eval-globals".
  730. Mainly useful to add user-defined functions to the pseudo-sandbox.
  731. """
  732. self.eval_allowed_globals[name] = obj
  733. def compile(self, expr):
  734. """Compile a Python-eval-expression.
  735. - Use a compile-cache.
  736. - Raise a `NameError` if `expr` contains a name beginning with ``_``.
  737. :Returns: the compiled `expr`
  738. :Exceptions:
  739. - `SyntaxError`: for compile-errors
  740. - `NameError`: if expr contains a name beginning with ``_``
  741. """
  742. if expr not in self._compile_cache:
  743. c = compile(expr, "", "eval")
  744. for i in c.co_names: #prevent breakout via new-style-classes
  745. if i[0] == '_':
  746. raise NameError("Name '%s' is not allowed." % i)
  747. self._compile_cache[expr] = c
  748. return self._compile_cache[expr]
  749. def eval(self, expr, locals):
  750. """Eval a Python-eval-expression.
  751. Sets ``self.locals_ptr`` to ``locales`` and compiles the code
  752. before evaluating.
  753. """
  754. sav = self.locals_ptr
  755. self.locals_ptr = locals
  756. x = eval(self.compile(expr), {"__builtins__":self.eval_allowed_globals}, locals)
  757. self.locals_ptr = sav
  758. return x
  759. def f_import(self, name, *_, **__):
  760. """``import``/``__import__()`` for the sandboxed code.
  761. Since "import" is insecure, the PseudoSandbox does not allow to
  762. import other modules. But since some functions need to import
  763. other modules (e.g. "datetime.datetime.strftime" imports "time"),
  764. this function replaces the builtin "import" and allows to use
  765. modules which are already accessible by the sandboxed code.
  766. :Note:
  767. - This probably only works for rather simple imports.
  768. - For security, it may be better to avoid such (complex) modules
  769. which import other modules. (e.g. use time.localtime and
  770. time.strftime instead of datetime.datetime.strftime,
  771. or write a small wrapper.)
  772. :Example:
  773. >>> from datetime import datetime
  774. >>> import pyratemp
  775. >>> t = pyratemp.Template('@!mytime.strftime("%H:%M:%S")!@')
  776. # >>> print(t(mytime=datetime.now()))
  777. # Traceback (most recent call last):
  778. # ...
  779. # ImportError: import not allowed in pseudo-sandbox; try to import 'time' yourself and pass it to the sandbox/template
  780. >>> import time
  781. >>> print(t(mytime=datetime.strptime("13:40:54", "%H:%M:%S"), time=time))
  782. 13:40:54
  783. # >>> print(t(mytime=datetime.now(), time=time))
  784. # 13:40:54
  785. """
  786. import types
  787. if self.locals_ptr is not None and name in self.locals_ptr and isinstance(self.locals_ptr[name], types.ModuleType):
  788. return self.locals_ptr[name]
  789. else:
  790. raise ImportError("import not allowed in pseudo-sandbox; try to import '%s' yourself (and maybe pass it to the sandbox/template)" % name)
  791. def f_exists(self, varname):
  792. """``exists()`` for the sandboxed code.
  793. Test if the variable `varname` exists in the current locals-namespace.
  794. This only works for single variable names. If you want to test
  795. complicated expressions, use i.e. `default`.
  796. (i.e. `default("expr",False)`)
  797. :Note: the variable-name has to be quoted! (like in eval)
  798. :Example: see module-docstring
  799. """
  800. return (varname in self.locals_ptr)
  801. def f_default(self, expr, default=None):
  802. """``default()`` for the sandboxed code.
  803. Try to evaluate an expression and return the result or a
  804. fallback-/default-value; the `default`-value is used
  805. if `expr` does not exist/is invalid/results in None.
  806. This is very useful for optional data.
  807. :Parameter:
  808. - expr: eval-expression
  809. - default: fallback-falue if eval(expr) fails or is None.
  810. :Returns:
  811. the eval-result or the "fallback"-value.
  812. :Note: the eval-expression has to be quoted! (like in eval)
  813. :Example: see module-docstring
  814. """
  815. try:
  816. r = self.eval(expr, self.locals_ptr)
  817. if r is None:
  818. return default
  819. return r
  820. #TODO: which exceptions should be catched here?
  821. except (NameError, LookupError, TypeError):
  822. return default
  823. def f_setvar(self, name, expr):
  824. """``setvar()`` for the sandboxed code.
  825. Set a variable.
  826. :Example: see module-docstring
  827. """
  828. self.locals_ptr[name] = self.eval(expr, self.locals_ptr)
  829. return ""
  830. def f_escape(self, s, format="HTML"):
  831. """``escape()`` for the sandboxed code.
  832. """
  833. if isinstance(format, (str, unicode)):
  834. format = ESCAPE_SUPPORTED[format.upper()]
  835. return escape(unicode(s), format)
  836. #-----------------------------------------
  837. # basic template / subtemplate
  838. class TemplateBase:
  839. """Basic template-class.
  840. Used both for the template itself and for 'macro's ("subtemplates") in
  841. the template.
  842. """
  843. def __init__(self, parsetree, renderfunc, data=None):
  844. """Create the Template/Subtemplate/Macro.
  845. :Parameters:
  846. - `parsetree`: parse-tree of the template/subtemplate/macro
  847. - `renderfunc`: render-function
  848. - `data`: data to fill into the template by default (dictionary).
  849. This data may later be overridden when rendering the template.
  850. :Exceptions:
  851. - `TypeError`: if `data` is not a dictionary
  852. """
  853. #TODO: parameter-checking?
  854. self.parsetree = parsetree
  855. if isinstance(data, dict):
  856. self.data = data
  857. elif data is None:
  858. self.data = {}
  859. else:
  860. raise TypeError('"data" must be a dict (or None).')
  861. self.current_data = data
  862. self._render = renderfunc
  863. def __call__(self, **override):
  864. """Fill out/render the template.
  865. :Parameters:
  866. - `override`: objects to add to the data-namespace, overriding
  867. the "default"-data.
  868. :Returns: the filled template (in unicode)
  869. :Note: This is also called when invoking macros
  870. (i.e. ``$!mymacro()!$``).
  871. """
  872. self.current_data = self.data.copy()
  873. self.current_data.update(override)
  874. u = "".join(self._render(self.parsetree, self.current_data))
  875. self.current_data = self.data # restore current_data
  876. return _dontescape(u) # (see class _dontescape)
  877. def __unicode__(self):
  878. """Alias for __call__()."""
  879. return self.__call__()
  880. def __str__(self):
  881. """Alias for __call__()."""
  882. return self.__call__()
  883. #-----------------------------------------
  884. # Renderer
  885. class _dontescape(unicode):
  886. """Unicode-string which should not be escaped.
  887. If ``isinstance(object,_dontescape)``, then don't escape the object in
  888. ``@!...!@``. It's useful for not double-escaping macros, and it's
  889. automatically used for macros/subtemplates.
  890. :Note: This only works if the object is used on its own in ``@!...!@``.
  891. It i.e. does not work in ``@!object*2!@`` or ``@!object + "hi"!@``.
  892. """
  893. __slots__ = []
  894. class Renderer(object):
  895. """Render a template-parse-tree.
  896. :Uses: `TemplateBase` for macros
  897. """
  898. def __init__(self, evalfunc, escapefunc):
  899. """Init the renderer.
  900. :Parameters:
  901. - `evalfunc`: function for template-expression-evaluation
  902. (i.e. ``EvalPseudoSandbox().eval``)
  903. - `escapefunc`: function for escaping special characters
  904. (i.e. `escape`)
  905. """
  906. #TODO: test evalfunc
  907. self.evalfunc = evalfunc
  908. self.escapefunc = escapefunc
  909. def _eval(self, expr, data):
  910. """evalfunc with error-messages"""
  911. try:
  912. return self.evalfunc(expr, data)
  913. #TODO: any other errors to catch here?
  914. except (TypeError,NameError,LookupError,AttributeError, SyntaxError) as err:
  915. raise TemplateRenderError("Cannot eval expression '%s'. (%s: %s)" %(expr, err.__class__.__name__, err))
  916. def render(self, parsetree, data):
  917. """Render a parse-tree of a template.
  918. :Parameters:
  919. - `parsetree`: the parse-tree
  920. - `data`: the data to fill into the template (dictionary)
  921. :Returns: the rendered output-unicode-string
  922. :Exceptions:
  923. - `TemplateRenderError`
  924. """
  925. _eval = self._eval # shortcut
  926. output = []
  927. do_else = False # use else/elif-branch?
  928. if parsetree is None:
  929. return ""
  930. for elem in parsetree:
  931. if "str" == elem[0]:
  932. output.append(elem[1])
  933. elif "sub" == elem[0]:
  934. output.append(unicode(_eval(elem[1], data)))
  935. elif "esc" == elem[0]:
  936. obj = _eval(elem[2], data)
  937. #prevent double-escape
  938. if isinstance(obj, _dontescape) or isinstance(obj, TemplateBase):
  939. output.append(unicode(obj))
  940. else:
  941. output.append(self.escapefunc(unicode(obj), elem[1]))
  942. elif "for" == elem[0]:
  943. do_else = True
  944. (names, iterable) = elem[1:3]
  945. try:
  946. loop_iter = iter(_eval(iterable, data))
  947. except TypeError:
  948. raise TemplateRenderError("Cannot loop over '%s'." % iterable)
  949. for i in loop_iter:
  950. do_else = False
  951. if len(names) == 1:
  952. data[names[0]] = i
  953. else:
  954. data.update(zip(names, i)) #"for a,b,.. in list"
  955. output.extend(self.render(elem[3], data))
  956. elif "if" == elem[0]:
  957. do_else = True
  958. if _eval(elem[1], data):
  959. do_else = False
  960. output.extend(self.render(elem[2], data))
  961. elif "elif" == elem[0]:
  962. if do_else and _eval(elem[1], data):
  963. do_else = False
  964. output.extend(self.render(elem[2], data))
  965. elif "else" == elem[0]:
  966. if do_else:
  967. do_else = False
  968. output.extend(self.render(elem[1], data))
  969. elif "macro" == elem[0]:
  970. data[elem[1]] = TemplateBase(elem[2], self.render, data)
  971. else:
  972. raise TemplateRenderError("Invalid parse-tree (%s)." %(elem))
  973. return output
  974. #-----------------------------------------
  975. # template user-interface (putting it all together)
  976. class Template(TemplateBase):
  977. """Template-User-Interface.
  978. :Usage:
  979. ::
  980. t = Template(...) (<- see __init__)
  981. output = t(...) (<- see TemplateBase.__call__)
  982. :Example:
  983. see module-docstring
  984. """
  985. def __init__(self, string=None,filename=None,parsetree=None, encoding='utf-8', data=None, escape=HTML,
  986. loader_class=LoaderFile,
  987. parser_class=Parser,
  988. renderer_class=Renderer,
  989. eval_class=EvalPseudoSandbox,
  990. escape_func=escape):
  991. """Load (+parse) a template.
  992. :Parameters:
  993. - `string,filename,parsetree`: a template-string,
  994. filename of a template to load,
  995. or a template-parsetree.
  996. (only one of these 3 is allowed)
  997. - `encoding`: encoding of the template-files (only used for "filename")
  998. - `data`: data to fill into the template by default (dictionary).
  999. This data may later be overridden when rendering the template.
  1000. - `escape`: default-escaping for the template, may be overwritten by the template!
  1001. - `loader_class`
  1002. - `parser_class`
  1003. - `renderer_class`
  1004. - `eval_class`
  1005. - `escapefunc`
  1006. """
  1007. if [string, filename, parsetree].count(None) != 2:
  1008. raise ValueError('Exactly 1 of string,filename,parsetree is necessary.')
  1009. tmpl = None
  1010. # load template
  1011. if filename is not None:
  1012. incl_load = loader_class(os.path.dirname(filename), encoding).load
  1013. tmpl = incl_load(os.path.basename(filename))
  1014. if string is not None:
  1015. incl_load = dummy_raise(NotImplementedError, "'include' not supported for template-strings.")
  1016. tmpl = LoaderString(encoding).load(string)
  1017. # eval (incl. compile-cache)
  1018. templateeval = eval_class()
  1019. # parse
  1020. if tmpl is not None:
  1021. p = parser_class(loadfunc=incl_load, testexpr=templateeval.compile, escape=escape)
  1022. parsetree = p.parse(tmpl)
  1023. del p
  1024. # renderer
  1025. renderfunc = renderer_class(templateeval.eval, escape_func).render
  1026. #create template
  1027. TemplateBase.__init__(self, parsetree, renderfunc, data)
  1028. #=========================================
  1029. #doctest
  1030. def _doctest():
  1031. """doctest this module."""
  1032. import doctest
  1033. doctest.testmod()
  1034. #----------------------
  1035. if __name__ == '__main__':
  1036. if sys.version_info[0] <= 2:
  1037. _doctest()
  1038. #=========================================