Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: UAVCAN UAVCAN_Subscriber
libuavcan/dsdl_compiler/libuavcan_dsdl_compiler/pyratemp.py@0:dfe6edabb8ec, 2018-04-14 (annotated)
- Committer:
- RuslanUrya
- Date:
- Sat Apr 14 10:25:32 2018 +0000
- Revision:
- 0:dfe6edabb8ec
Initial commit
Who changed what in which revision?
| User | Revision | Line number | New 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: <>&'", 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 äöü€' |
| 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("&", "&") # must be done first! |
| RuslanUrya | 0:dfe6edabb8ec | 300 | s = s.replace("<", "<") |
| RuslanUrya | 0:dfe6edabb8ec | 301 | s = s.replace(">", ">") |
| RuslanUrya | 0:dfe6edabb8ec | 302 | s = s.replace('"', """) |
| RuslanUrya | 0:dfe6edabb8ec | 303 | s = s.replace("'", "'") |
| 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 |