1 """PyPlate : a simple Python-based templating program
3 PyPlate parses a file and replaces directives (in double square brackets [[ ... ]])
4 by various means using a given dictionary of variables. Arbitrary Python code
5 can be run inside many of the directives, making this system highly flexible.
8 # Load and parse template file
9 template = pyplate.Template("output") (filename or string)
10 # Execute it with a dictionary of variables
11 template.execute_file(output_stream, locals())
13 PyPlate defines the following directives:
14 [[...]] evaluate the arbitrary Python expression and insert the
15 result into the output
19 [[exec ...]] execute arbitrary Python code in the sandbox namespace
21 [[if ...]] conditional expressions with usual Python semantics
26 [[for ... in ...]] for-loop with usual Python semantics
29 [[def ...(...)]] define a "function" out of other templating elements
32 [[call ...]] call a templating function (not a regular Python function)
36 # Copyright (C) 2002 Michael Droettboom
38 # This program is free software; you can redistribute it and/or
39 # modify it under the terms of the GNU General Public License
40 # as published by the Free Software Foundation; either version 2
41 # of the License, or (at your option) any later version.
43 # This program is distributed in the hope that it will be useful,
44 # but WITHOUT ANY WARRANTY; without even the implied warranty of
45 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46 # GNU General Public License for more details.
48 # You should have received a copy of the GNU General Public License
49 # along with this program; if not, write to the Free Software
50 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
53 from __future__
import nested_scopes
54 import sys
, string
, re
, cStringIO
56 re_directive
= re
.compile("\[\[(.*)\]\]")
57 re_for_loop
= re
.compile("for (.*) in (.*)")
58 re_if
= re
.compile("if (.*)")
59 re_elif
= re
.compile("elif (.*)")
60 re_def
= re
.compile("def (.*?)\((.*)\)")
61 re_call
= re
.compile("call (.*?)\((.*)\)")
62 re_exec
= re
.compile("exec (.*)")
63 re_comment
= re
.compile("#(.*)#")
65 ############################################################
67 class ParserException(Exception):
68 def __init__(self
, lineno
, s
):
69 Exception.__init
__(self
, "line %d: %s" % (lineno
, s
))
72 def __init__(self
, filename
=None):
75 self
.parse_file(filename
)
77 self
.parse_string(filename
)
79 def parse_file(self
, filename
):
80 file = open(filename
, 'r')
84 def parse_string(self
, template
):
85 file = cStringIO
.StringIO(template
)
89 def parse(self
, file):
91 self
.line
= self
.file.read()
94 self
.tree
= TopLevelTemplateNode(self
)
101 def parser_eat(self
, chars
):
102 self
.lineno
= self
.lineno
+ self
.line
[:chars
].count("\n")
103 self
.line
= self
.line
[chars
:]
105 def parser_exception(self
, s
):
106 raise ParserException(self
.lineno
, s
)
108 def execute_file(self
, filename
, data
):
109 file = open(filename
, 'w')
110 self
.execute(file, data
)
113 def execute_string(self
, data
):
114 s
= cStringIO
.StringIO()
115 self
.execute(s
, data
)
118 def execute_stdout(self
, data
):
119 self
.execute(sys
.stdout
, data
)
121 def execute(self
, stream
=sys
.stdout
, data
={}):
122 self
.tree
.execute(stream
, data
)
125 return repr(self
.tree
)
128 ############################################################
131 def __init__(self
, parent
, s
):
136 new_node
= TemplateNodeFactory(parent
)
137 if self
.add_node(new_node
):
140 def add_node(self
, node
):
144 self
.node_list
.append(node
)
146 raise self
.parent
.parser_exception(
147 "[[%s]] does not have a matching [[end]]" % self
.s
)
149 def execute(self
, stream
, data
):
150 for node
in self
.node_list
:
151 node
.execute(stream
, data
)
154 r
= "<" + self
.__class
__.__name
__ + " "
155 for i
in self
.node_list
:
160 class TopLevelTemplateNode(TemplateNode
):
161 def __init__(self
, parent
):
162 TemplateNode
.__init
__(self
, parent
, '')
164 def add_node(self
, node
):
166 self
.node_list
.append(node
)
170 class ForTemplateNode(TemplateNode
):
171 def __init__(self
, parent
, s
):
172 TemplateNode
.__init
__(self
, parent
, s
)
173 match
= re_for_loop
.match(s
)
175 raise self
.parent
.parser_exception(
176 "[[%s]] is not a valid for-loop expression" % self
.s
)
178 self
.vars_temp
= match
.group(1).split(",")
180 for v
in self
.vars_temp
:
181 self
.vars.append(v
.strip())
183 self
.expression
= match
.group(2)
185 def execute(self
, stream
, data
):
187 for var
in self
.vars:
188 if data
.has_key(var
):
189 remember_vars
[var
] = data
[var
]
190 for list in eval(self
.expression
, globals(), data
):
191 if is_sequence(list):
192 for index
, value
in enumerate(list):
193 data
[self
.vars[index
]] = value
195 data
[self
.vars[0]] = list
196 TemplateNode
.execute(self
, stream
, data
)
197 for key
, value
in remember_vars
.items():
200 class IfTemplateNode(TemplateNode
):
201 def __init__(self
, parent
, s
):
202 self
.else_node
= None
203 TemplateNode
.__init
__(self
, parent
, s
)
204 match
= re_if
.match(s
)
206 raise self
.parent
.parser_exception(
207 "[[%s]] is not a valid if expression" % self
.s
)
209 self
.expression
= match
.group(1)
211 def add_node(self
, node
):
214 elif isinstance(node
, ElseTemplateNode
):
215 self
.else_node
= node
217 elif isinstance(node
, ElifTemplateNode
):
218 self
.else_node
= node
221 self
.node_list
.append(node
)
223 raise self
.parent
.parser_exception(
224 "[[%s]] does not have a matching [[end]]" % self
.s
)
226 def execute(self
, stream
, data
):
227 if eval(self
.expression
, globals(), data
):
228 TemplateNode
.execute(self
, stream
, data
)
229 elif self
.else_node
!= None:
230 self
.else_node
.execute(stream
, data
)
232 class ElifTemplateNode(IfTemplateNode
):
233 def __init__(self
, parent
, s
):
234 self
.else_node
= None
235 TemplateNode
.__init
__(self
, parent
, s
)
236 match
= re_elif
.match(s
)
238 self
.parent
.parser_exception(
239 "[[%s]] is not a valid elif expression" % self
.s
)
241 self
.expression
= match
.group(1)
243 class ElseTemplateNode(TemplateNode
):
246 class FunctionTemplateNode(TemplateNode
):
247 def __init__(self
, parent
, s
):
248 TemplateNode
.__init
__(self
, parent
, s
)
249 match
= re_def
.match(s
)
251 self
.parent
.parser_exception(
252 "[[%s]] is not a valid function definition" % self
.s
)
253 self
.function_name
= match
.group(1)
254 self
.vars_temp
= match
.group(2).split(",")
256 for v
in self
.vars_temp
:
257 self
.vars.append(v
.strip())
259 self
.parent
.functions
[self
.function_name
] = self
261 def execute(self
, stream
, data
):
264 def call(self
, args
, stream
, data
):
266 for index
, var
in enumerate(self
.vars):
267 if data
.has_key(var
):
268 remember_vars
[var
] = data
[var
]
269 data
[var
] = args
[index
]
270 TemplateNode
.execute(self
, stream
, data
)
271 for key
, value
in remember_vars
.items():
274 class LeafTemplateNode(TemplateNode
):
275 def __init__(self
, parent
, s
):
279 def execute(self
, stream
, data
):
283 return "<" + self
.__class
__.__name
__ + ">"
285 class CommentTemplateNode(LeafTemplateNode
):
286 def execute(self
, stream
, data
):
289 class ExpressionTemplateNode(LeafTemplateNode
):
290 def execute(self
, stream
, data
):
291 stream
.write(str(eval(self
.s
, globals(), data
)))
293 class ExecTemplateNode(LeafTemplateNode
):
294 def __init__(self
, parent
, s
):
295 LeafTemplateNode
.__init
__(self
, parent
, s
)
296 match
= re_exec
.match(s
)
298 self
.parent
.parser_exception(
299 "[[%s]] is not a valid statement" % self
.s
)
300 self
.s
= match
.group(1)
302 def execute(self
, stream
, data
):
303 exec(self
.s
, globals(), data
)
306 class CallTemplateNode(LeafTemplateNode
):
307 def __init__(self
, parent
, s
):
308 LeafTemplateNode
.__init
__(self
, parent
, s
)
309 match
= re_call
.match(s
)
311 self
.parent
.parser_exception(
312 "[[%s]] is not a valid function call" % self
.s
)
313 self
.function_name
= match
.group(1)
314 self
.vars = "(" + match
.group(2).strip() + ",)"
316 def execute(self
, stream
, data
):
317 self
.parent
.functions
[self
.function_name
].call(
318 eval(self
.vars, globals(), data
), stream
, data
)
321 ############################################################
323 template_factory_type_map
= {
324 'if' : IfTemplateNode
,
325 'for' : ForTemplateNode
,
326 'elif' : ElifTemplateNode
,
327 'else' : ElseTemplateNode
,
328 'def' : FunctionTemplateNode
,
329 'call' : CallTemplateNode
,
330 'exec' : ExecTemplateNode
}
331 template_factory_types
= template_factory_type_map
.keys()
333 def TemplateNodeFactory(parent
):
334 src
= parent
.parser_get()
338 match
= re_directive
.search(src
)
340 parent
.parser_eat(len(src
))
341 return LeafTemplateNode(parent
, src
)
342 elif src
== '' or match
.start() != 0:
343 parent
.parser_eat(match
.start())
344 return LeafTemplateNode(parent
, src
[:match
.start()])
346 directive
= match
.group()[2:-2].strip()
347 parent
.parser_eat(match
.end())
348 if directive
== 'end':
350 elif re_comment
.match(directive
):
351 return CommentTemplateNode(parent
, directive
)
353 for i
in template_factory_types
:
354 if directive
[0:len(i
)] == i
:
355 return template_factory_type_map
[i
](parent
, directive
)
356 return ExpressionTemplateNode(parent
, directive
)
358 def is_sequence(object):