Руслан Урядинский / libuavcan

Dependents:   UAVCAN UAVCAN_Subscriber

Committer:
RuslanUrya
Date:
Sat Apr 14 10:25:32 2018 +0000
Revision:
0:dfe6edabb8ec
Initial commit

Who changed what in which revision?

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