1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 ==================
19 Template Objects
20 ==================
21
22 Template Objects.
23 """
24 __author__ = u"Andr\xe9 Malo"
25 __docformat__ = "restructuredtext en"
26 __all__ = ['Template', 'OverlayTemplate', 'AutoUpdate']
27
28 import sys as _sys
29
30 from tdi._exceptions import Error
31 from tdi._exceptions import TemplateReloadError
32 from tdi._exceptions import AutoUpdateWarning
33 from tdi._exceptions import OverlayError
34 from tdi import model_adapters as _model_adapters
35 from tdi import util as _util
36
37
39 """
40 Template class
41
42 :IVariables:
43 `filename` : ``str``
44 The filename of the template
45
46 `mtime` : any
47 Last modification time key (``None`` means n/a)
48
49 `factory` : `Factory`
50 Template factory
51
52 `_loader` : ``callable``
53 Loader
54
55 `_tree` : ``list``
56 The nodetree, the overlay-filtered tree and the prerendered tree
57 """
58 __slots__ = [
59 '__weakref__', 'filename', 'mtime', 'factory', '_loader', '_tree'
60 ]
61
63 """
64 The node tree with overlay filters
65
66 :Type: `tdi.nodetree.Root`
67 """
68
69 def fget(self):
70 return self._prerender(None, None)
71 return locals()
72 tree = _util.Property(tree)
73
75 """
76 The node tree without overlay filters
77
78 :Type: `tdi.nodetree.Root`
79 """
80
81 def fget(self):
82 return self._tree[0]
83 return locals()
84 virgin_tree = _util.Property(virgin_tree)
85
87 """
88 The template encoding
89
90 :Type: ``str``
91 """
92
93 def fget(self):
94 return self.tree.encoder.encoding
95 return locals()
96 encoding = _util.Property(encoding)
97
99 """
100 Source overlay names
101
102 :Type: iterable
103 """
104
105 def fget(self):
106 return self.tree.source_overlay_names
107 return locals()
108 source_overlay_names = _util.Property(source_overlay_names)
109
111 """
112 Target overlay names
113
114 :Type: iterable
115 """
116
117 def fget(self):
118 return self.tree.target_overlay_names
119 return locals()
120 target_overlay_names = _util.Property(target_overlay_names)
121
122 - def __init__(self, tree, filename, mtime, factory, loader=None):
123 """
124 Initialization
125
126 :Parameters:
127 `tree` : `tdi.nodetree.Root`
128 The template tree
129
130 `filename` : ``str``
131 Filename of the template
132
133 `mtime` : ``int``
134 Last modification time of the template (maybe ``None``)
135
136 `factory` : `Factory`
137 Template factory
138
139 `loader` : ``callable``
140 Template loader
141 """
142 self._tree = [tree, None, None]
143 self.filename = filename
144 self.mtime = mtime
145 self.factory = factory
146 self._loader = loader
147
149 """
150 String representation of the node tree
151
152 :Return: The string representation
153 :Rtype: ``str``
154 """
155 return self.tree.to_string(verbose=False)
156
158 """ Return a clean template (without any wrapper) """
159 return self
160
162 """
163 Reload template(s) if possible and needed
164
165 :Return: The reloaded (new) template or self
166 :Rtype: `Template`
167 """
168 if self._loader is not None:
169 try:
170 tree, mtime = self._loader(self.mtime)
171 except (AttributeError, IOError, OSError, Error), e:
172 raise TemplateReloadError(str(e))
173 if tree is not None:
174 return self.__class__(
175 tree, self.filename, mtime, self.factory, self._loader
176 )
177 return self
178
180 """
181 Check for update
182
183 :Return: Update available?
184 :Rtype: ``bool``
185 """
186 if self._loader is not None:
187 return self._loader(self.mtime, check_only=True)[0]
188 return False
189
191 """
192 Possibly prerender the tree
193
194 :Parameters:
195 `model` : any
196 Prerender-Model
197
198 `adapter` : `ModelAdapterInterface`
199 Prerender-adapter
200
201 :Return: The tree to finally render (either prerendered or not)
202 :Rtype: `tdi.nodetree.Root`
203 """
204 otree, factory = self._tree, self.factory
205
206
207 ftree = otree[1]
208 if ftree is None:
209 ftree = otree[0]
210 if factory.overlay_filters is not None:
211 ffactory = factory.replace(**factory.overlay_filters)
212 ftree = ffactory.from_string(''.join(list(ftree.render(
213 _model_adapters.RenderAdapter.for_prerender(None,
214 attr=dict(
215 scope=ffactory.builder().analyze.scope,
216 tdi=ffactory.builder().analyze.attribute,
217 )
218 )
219 )))).virgin_tree
220 otree[1] = ftree
221
222
223 if model is None:
224 return ftree
225
226
227 ptree = otree[2]
228 if ptree is None:
229 version = None
230 else:
231 version = ptree[1]
232 checker = getattr(model, 'prerender_version', None)
233 if checker is not None:
234 rerender, version = checker(version)
235 elif version is not None:
236
237
238
239
240
241
242 rerender, version = True, None
243 else:
244 rerender = False
245 if ptree is not None and not rerender:
246 otree[2] = ptree[0], version
247 return ptree[0]
248
249
250 filters = getattr(model, 'prerender_filters', None)
251 if filters is not None:
252 filters = filters().get
253 factory = factory.replace(
254 eventfilters=filters('eventfilters'),
255 default_eventfilters=filters(
256 'default_eventfilters', True
257 ),
258 streamfilters=filters('streamfilters'),
259 default_streamfilters=filters(
260 'default_streamfilters', True
261 ),
262 )
263 if adapter is None:
264 adapter = _model_adapters.RenderAdapter.for_prerender
265 adapted = adapter(model, attr=dict(
266 scope=factory.builder().analyze.scope,
267 tdi=factory.builder().analyze.attribute,
268 ))
269 tree = factory.from_string(''.join(list(ftree.render(adapted)))).tree
270
271 otree[2] = tree, version
272 return tree
273
274 - def render(self, model=None, stream=None, flush=False,
275 startnode=None, adapter=None, prerender=None, preadapter=None):
276 """
277 Render the template into `stream` using `model`
278
279 :Parameters:
280 `model` : any
281 The model object
282
283 `stream` : ``file``
284 The stream to render to. If ``None``, ``sys.stdout`` is
285 taken.
286
287 `flush` : ``bool``
288 flush after each write? The stream needs a ``flush``
289 method in order to do this (If it doesn't have one, `flush`
290 being ``True`` is silently ignored)
291
292 `startnode` : ``str``
293 Only render this node (and all its children). The node
294 is addressed via a dotted string notation, like ``a.b.c`` (this
295 would render the ``c`` node.) The notation does not describe a
296 strict node chain, though. Between to parts of a node chain may
297 be gaps in the tree. The algorithm looks out for the first
298 matching node. It does no backtracking and so does not cover all
299 branches (yet?), but that works fine for realistic cases :).
300 A non-working example would be (searching for a.b.c)::
301
302 *
303 +- a
304 | `- b - d
305 `- a
306 `- b - c
307
308 `adapter` : ``callable``
309 Usermodel adapter factory (takes the model). This
310 adapter is responsible for method and attribute resolving in the
311 actual user model. If omitted or ``None``, the standard
312 `model_adapters.RenderAdapter` is applied.
313
314 `prerender` : any
315 Prerender-Model. If omitted or ``None``, no prerendering will
316 happen and the original template is rendered.
317
318 `preadapter` : `ModelAdapterInterface`
319 Prerender-model adapter factory (takes the model and an attribute
320 specification dict). If omitted or ``None``, the standard
321 `model_adapters.RenderAdapter.for_prerender` is applied.
322 """
323 if stream is None:
324 stream = _sys.stdout
325 if adapter is None:
326 adapter = _model_adapters.RenderAdapter
327 result = self._prerender(prerender, preadapter).render(
328 adapter(model), startnode
329 )
330 if flush == -1:
331 stream.write(''.join(list(result)))
332 else:
333 write = stream.write
334 if flush:
335 try:
336 flush = stream.flush
337 except AttributeError:
338 pass
339 else:
340 for chunk in result:
341 write(chunk)
342 flush()
343 return
344 for chunk in result:
345 write(chunk)
346
347 - def render_string(self, model=None, startnode=None, adapter=None,
348 prerender=None, preadapter=None):
349 """
350 Render the template as string using `model`
351
352 :Parameters:
353 `model` : any
354 The model object
355
356 `startnode` : ``str``
357 Only render this node (and all its children). See
358 `render` for details.
359
360 `adapter` : ``callable``
361 Usermodel adapter factory (takes the model). This
362 adapter is responsible for method and attribute resolving in the
363 actual user model. If omitted or ``None``, the standard
364 `model_adapters.RenderAdapter` is applied.
365
366 `prerender` : any
367 Prerender-Model
368
369 `preadapter` : `ModelAdapterInterface`
370 Prerender-adapter
371
372 :Return: The rendered document
373 :Rtype: ``str``
374 """
375 if adapter is None:
376 adapter = _model_adapters.RenderAdapter
377 return ''.join(list(self._prerender(prerender, preadapter).render(
378 adapter(model), startnode
379 )))
380
382 """
383 Overlay this template with another one
384
385 :Parameters:
386 `other` : `Template`
387 The template layed over self
388
389 :Return: The combined template
390 :Rtype: `Template`
391 """
392 return OverlayTemplate(self, other)
393
394
396 """ Overlay template representation """
397 __slots__ = ['_left', '_right']
398
399 - def __init__(self, original, overlay, keep=False):
400 """
401 Initialization
402
403 :Parameters:
404 `original` : `Template`
405 Original template
406
407 `overlay` : `Template`
408 Overlay template
409
410 `keep` : `Template`
411 Keep original templates?
412 """
413 tree1, tree2 = original.virgin_tree, overlay.virgin_tree
414 if tree1.encoder.encoding != tree2.encoder.encoding:
415 raise OverlayError("Incompatible templates: encoding mismatch")
416 if keep:
417 self._left, self._right = original, overlay
418 else:
419 self._left, self._right = None, None
420 filename = "<overlay>"
421 if original.mtime is not None and overlay.mtime is not None:
422 mtime = max(original.mtime, overlay.mtime)
423 else:
424 mtime = None
425 super(OverlayTemplate, self).__init__(
426 tree1.overlay(tree2), filename, mtime, original.factory
427 )
428
430 """ Return a clean template """
431 if self._left is not None:
432 original = self._left.template()
433 overlay = self._right.template()
434 if original is not self._left or overlay is not self._right:
435 return self.__class__(original, overlay, keep=True)
436 return self
437
439 """
440 Reload template(s) if possible and needed
441
442 :Return: The reloaded template or self
443 :Rtype: `Template`
444 """
445 if self._left is not None:
446 original = self._left.reload()
447 overlay = self._right.reload()
448 if original is not self._left or overlay is not self._right:
449 return self.__class__(original, overlay, keep=True)
450 return self
451
453 """
454 Check for update
455
456 :Return: Update available?
457 :Rtype: ``bool``
458 """
459 if self._left is not None:
460 return (
461 self._left.update_available() or
462 self._right.update_available()
463 )
464 return False
465
466
468 """ Autoupdate wrapper """
469
471 """
472 Initialization
473
474 :Parameters:
475 `template` : `Template`
476 The template to autoupdate
477 """
478 self._template = template.template()
479
481 """
482 Pass through every request to the original template
483
484 The template is checked before and possibly replaced if it changed.
485
486 :Parameters:
487 `name` : ``str``
488 Name to lookup
489
490 :Return: The requested value
491 :Rtype: any
492
493 :Exceptions:
494 - `AttributeError` : not found
495 """
496 template = self._template
497 if template.update_available():
498 try:
499 self._template = template.reload()
500 except TemplateReloadError, e:
501 AutoUpdateWarning.emit(
502 'Template autoupdate failed: %s' % str(e)
503 )
504 return getattr(self._template, name)
505
507 """
508 Overlay this template with another one
509
510 :Parameters:
511 `other` : `Template`
512 The template layed over self
513
514 :Return: The combined template
515 :Rtype: `Template`
516 """
517 return self.__class__(OverlayTemplate(self, other, keep=True))
518