4 # A light-weight, fully functional, general purpose templating engine
6 # Copyright (c) 2009 joonis new media
7 # Author: Thimo Kraemer <thimo.kraemer@joonis.de>
9 # Based on Templite - Tomer Filiba
10 # http://code.activestate.com/recipes/496702/
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
30 class Templite(object):
31 auto_emit = re.compile('(^[\'\"])|(^[a-zA-Z0-9_\[\]\'\"]+$)')
33 def __init__(self, template, start='${', end='}$'):
34 if len(start) != 2 or len(end) != 2:
35 raise ValueError('each delimiter must be two characters long')
36 delimiter = re.compile('%s(.*?)%s' % (re.escape(start), re.escape(end)), re.DOTALL)
39 for i, part in enumerate(delimiter.split(template)):
40 part = part.replace('\\'.join(list(start)), start)
41 part = part.replace('\\'.join(list(end)), end)
44 part = part.replace('\\', '\\\\').replace('"', '\\"')
45 part = '\t' * offset + 'emit("""%s""")' % part
49 if part.lstrip().startswith(':'):
51 raise SyntaxError('no block statement to terminate: ${%s}$' % part)
53 part = part.lstrip()[1:]
54 if not part.endswith(':'): continue
55 elif self.auto_emit.match(part.lstrip()):
56 part = 'emit(%s)' % part.lstrip()
57 lines = part.splitlines()
58 margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip())
59 part = '\n'.join('\t' * offset + l[margin:] for l in lines)
60 if part.endswith(':'):
64 raise SyntaxError('%i block statement(s) not terminated' % offset)
65 self.__code = compile('\n'.join(tokens), '<templite %r>' % template[:20], 'exec')
67 def render(self, __namespace=None, **kw):
69 renders the template according to the given namespace.
70 __namespace - a dictionary serving as a namespace for evaluation
71 **kw - keyword arguments which are added to the namespace
74 if __namespace: namespace.update(__namespace)
75 if kw: namespace.update(kw)
76 namespace['emit'] = self.write
81 eval(self.__code, namespace)
83 return ''.join(self.__output)
85 def write(self, *args):
87 self.__output.append(str(a))
90 if __name__ == '__main__':
92 if '--demo' not in sys.argv:
93 vars = eval('dict(' + ' '.join(sys.argv[1:]) + ')')
94 sys.stdout.write(Templite(sys.stdin.read()).render(vars))
103 emit("hello ", arg, "<br>")
111 emit(" </tr></td>\n")
118 say_hello("big x")}$ lala
120 $\{this is escaped starting delimiter
122 ${emit("this }\$ is an escaped ending delimiter")}$
124 ${# this is a python comment }$
129 But this is completely new:
131 x is ${emit('greater')}$ than ${print x-1}$ Well, the print statement produces a newline.
133 This terminates the previous code block and starts an else code block
134 Also this would work: $\{:end}\$$\{else:}\$, but not this: $\{:end}\$ $\{else:}\$
135 ${:this terminates the else-block
136 only the starting colon is essential}$
138 So far you had to write:
142 After a condition you could not continue your template.
143 You had to write pure python code.
144 The only way was to use %%-based substitutions %s
149 Now you do not need to break your template ${print x}$
154 ${for i in range(x-1):}$ Of course you can use any type of block statement ${i}$ ${"fmt: %s" % (i*2)}$
156 Single variables and expressions starting with quotes are substituted automatically.
157 Instead $\{emit(x)}\$ you can write $\{x}\$ or $\{'%s' % x}\$ or $\{"", x}\$
158 Therefore standalone statements like break, continue or pass
159 must be enlosed by a semicolon: $\{continue;}\$
164 t = Templite(template)
170 This we already know:
176 <tr><td> hello 0<br> </tr></td>
177 <tr><td> hello 1<br> </tr></td>
178 <tr><td> hello 2<br> </tr></td>
179 <tr><td> hello 3<br> </tr></td>
180 <tr><td> hello 4<br> </tr></td>
181 <tr><td> hello 5<br> </tr></td>
182 <tr><td> hello 6<br> </tr></td>
183 <tr><td> hello 7<br> </tr></td>
184 <tr><td> hello 8<br> </tr></td>
185 <tr><td> hello 9<br> </tr></td>
191 tralala hello big x<br> lala
193 ${this is escaped starting delimiter
195 this }$ is an escaped ending delimiter
202 But this is completely new:
205 Well, the print statement produces a newline.
208 So far you had to write:
210 After a condition you could not continue your template.
211 You had to write pure python code.
212 The only way was to use %-based substitutions 8
216 Now you do not need to break your template 8
220 Of course you can use any type of block statement 0 fmt: 0
221 Of course you can use any type of block statement 1 fmt: 2
222 Of course you can use any type of block statement 2 fmt: 4
223 Of course you can use any type of block statement 3 fmt: 6
224 Of course you can use any type of block statement 4 fmt: 8
225 Of course you can use any type of block statement 5 fmt: 10
226 Of course you can use any type of block statement 6 fmt: 12
228 Single variables and expressions starting with quotes are substituted automatically.
229 Instead ${emit(x)}$ you can write ${x}$ or ${'%s' % x}$ or ${"", x}$
230 Therefore standalone statements like break, continue or pass
231 must be enlosed by a semicolon: ${continue;}$