Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/chameleon/zpt/template.py : 47%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1try:
2 import ast
3except ImportError:
4 from chameleon import ast25 as ast
6from functools import partial
7from os.path import dirname
8from hashlib import md5
10from ..i18n import simple_translate
11from ..tales import PythonExpr
12from ..tales import StringExpr
13from ..tales import NotExpr
14from ..tales import ExistsExpr
15from ..tales import ImportExpr
16from ..tales import ProxyExpr
17from ..tales import StructureExpr
18from ..tales import ExpressionParser
19from ..tales import DEFAULT_MARKER
20from ..tal import RepeatDict
22from ..template import BaseTemplate
23from ..template import BaseTemplateFile
24from ..compiler import ExpressionEngine
25from ..loader import TemplateLoader
26from ..utils import decode_string
27from ..utils import string_type
28from ..utils import unicode_string
29from ..astutil import Symbol
31from .program import MacroProgram
33try:
34 bytes
35except NameError:
36 bytes = str
39class PageTemplate(BaseTemplate):
40 """Constructor for the page template language.
42 Takes a string input as the only positional argument::
44 template = PageTemplate("<div>Hello, ${name}.</div>")
46 Configuration (keyword arguments):
48 ``auto_reload``
50 Enables automatic reload of templates. This is mostly useful
51 in a development mode since it takes a significant performance
52 hit.
54 ``default_expression``
56 Set the default expression type. The default setting is
57 ``python``.
59 ``encoding``
61 The default text substitution value is a unicode string on
62 Python 2 or simply string on Python 3.
64 Pass an encoding to allow encoded byte string input
65 (e.g. UTF-8).
67 ``boolean_attributes``
69 Attributes included in this set are treated as booleans: if a
70 true value is provided, the attribute value is the attribute
71 name, e.g.::
73 boolean_attributes = {"selected"}
75 If we insert an attribute with the name "selected" and
76 provide a true value, the attribute will be rendered::
78 selected="selected"
80 If a false attribute is provided (including the empty string),
81 the attribute is dropped.
83 The special return value ``default`` drops or inserts the
84 attribute based on the value element attribute value.
86 ``translate``
88 Use this option to set a translation function.
90 Example::
92 def translate(msgid, domain=None, mapping=None, default=None, context=None):
93 ...
94 return translation
96 Note that if ``target_language`` is provided at render time,
97 the translation function must support this argument.
99 ``implicit_i18n_translate``
101 Enables implicit translation for text appearing inside
102 elements. Default setting is ``False``.
104 While implicit translation does work for text that includes
105 expression interpolation, each expression must be simply a
106 variable name (e.g. ``${foo}``); otherwise, the text will not
107 be marked for translation.
109 ``implicit_i18n_attributes``
111 Any attribute contained in this set will be marked for
112 implicit translation. Each entry must be a lowercase string.
114 Example::
116 implicit_i18n_attributes = set(['alt', 'title'])
118 ``on_error_handler``
120 This is an optional exception handler that is invoked during the
121 "on-error" fallback block.
123 ``strict``
125 Enabled by default. If disabled, expressions are only required
126 to be valid at evaluation time.
128 This setting exists to provide compatibility with the
129 reference implementation which compiles expressions at
130 evaluation time.
132 ``trim_attribute_space``
134 If set, additional attribute whitespace will be stripped.
136 ``restricted_namespace``
138 True by default. If set False, ignored all namespace except chameleon default namespaces. It will be useful working with attributes based javascript template renderer like VueJS.
140 Example:
142 <div v-bind:id="dynamicId"></div>
143 <button v-on:click="greet">Greet</button>
144 <button @click="greet">Greet</button>
146 ``tokenizer``
148 None by default. If provided, this tokenizer is used instead of the default
149 (which is selected based on the template mode parameter.)
151 ``value_repr``
153 This can be used to override the default value representation
154 function which is used to format values when formatting an
155 exception output. The function must not raise an exception (it
156 should be safe to call with any value).
158 ``default_marker``
160 This default marker is used as the marker object bound to the `default`
161 name available to any expression. The semantics is such that if an
162 expression evaluates to the marker object, the default action is used;
163 for an attribute expression, this is the static attribute text; for an
164 element this is the static element text. If there is no static text
165 then the default action is similar to an expression result of `None`.
167 Output is unicode on Python 2 and string on Python 3.
169 """
171 expression_types = {
172 'python': PythonExpr,
173 'string': StringExpr,
174 'not': NotExpr,
175 'exists': ExistsExpr,
176 'import': ImportExpr,
177 'structure': StructureExpr,
178 }
180 default_expression = 'python'
182 translate = staticmethod(simple_translate)
184 encoding = None
186 boolean_attributes = set()
188 mode = "xml"
190 implicit_i18n_translate = False
192 implicit_i18n_attributes = set()
194 trim_attribute_space = False
196 enable_data_attributes = False
198 enable_comment_interpolation = True
200 on_error_handler = None
202 restricted_namespace = True
204 tokenizer = None
206 default_marker = Symbol(DEFAULT_MARKER)
208 def __init__(self, body, **config):
209 self.macros = Macros(self)
210 super(PageTemplate, self).__init__(body, **config)
212 def __getitem__(self, name):
213 return self.macros[name]
215 @property
216 def builtins(self):
217 return self._builtins()
219 @property
220 def engine(self):
221 return partial(
222 ExpressionEngine,
223 self.expression_parser,
224 default_marker=self.default_marker,
225 )
227 @property
228 def expression_parser(self):
229 return ExpressionParser(self.expression_types, self.default_expression)
231 def parse(self, body):
232 return MacroProgram(
233 body, self.mode, self.filename,
234 escape=True if self.mode == "xml" else False,
235 default_marker=self.default_marker,
236 boolean_attributes=self.boolean_attributes,
237 implicit_i18n_translate=self.implicit_i18n_translate,
238 implicit_i18n_attributes=self.implicit_i18n_attributes,
239 trim_attribute_space=self.trim_attribute_space,
240 enable_data_attributes=self.enable_data_attributes,
241 enable_comment_interpolation=self.enable_comment_interpolation,
242 restricted_namespace=self.restricted_namespace,
243 tokenizer=self.tokenizer
244 )
246 def render(self, encoding=None, **_kw):
247 """Render template to string.
249 If providd, the ``encoding`` argument overrides the template
250 default value.
252 Additional keyword arguments are passed as template variables.
254 In addition, some also have a special meaning:
256 ``translate``
258 This keyword argument will override the default template
259 translate function.
261 ``target_language``
263 This will be used as the default argument to the translate
264 function if no `i18n:target` value is provided.
266 If not provided, the `translate` function will need to
267 negotiate a language based on the provided context.
268 """
270 translate = _kw.get('translate')
271 if translate is not None:
272 has_translate = True
273 else:
274 has_translate = False
275 translate = self.translate
277 # This should not be necessary, but we include it for
278 # backward compatibility.
279 if translate is None:
280 translate = type(self).translate
282 encoding = encoding if encoding is not None else self.encoding
283 if encoding is not None:
284 def translate(msgid, txl=translate, encoding=encoding, **kwargs):
285 if isinstance(msgid, bytes):
286 msgid = decode_string(msgid, encoding)
287 return txl(msgid, **kwargs)
289 def decode(inst, encoding=encoding):
290 return decode_string(inst, encoding, 'ignore')
291 else:
292 decode = decode_string
294 target_language = _kw.get('target_language')
296 setdefault = _kw.setdefault
297 setdefault("__translate", translate)
298 setdefault("__convert",
299 partial(translate, target_language=target_language))
300 setdefault("__decode", decode)
301 setdefault("target_language", None)
302 setdefault("__on_error_handler", self.on_error_handler)
304 # Make sure we have a repeat dictionary
305 if 'repeat' not in _kw: _kw['repeat'] = RepeatDict({})
307 return super(PageTemplate, self).render(**_kw)
309 def include(self, *args, **kwargs):
310 self.cook_check()
311 self._render(*args, **kwargs)
313 def digest(self, body, names):
314 hex = super(PageTemplate, self).digest(body, names)
315 if isinstance(hex, unicode_string):
316 hex = hex.encode('utf-8')
317 digest = md5(hex)
318 digest.update(';'.join(names).encode('utf-8'))
320 for attr in (
321 'trim_attribute_space',
322 'implicit_i18n_translate',
323 'strict'
324 ):
325 v = getattr(self, attr)
326 digest.update(
327 (";%s=%s" % (attr, str(v))).encode('ascii')
328 )
330 return digest.hexdigest()
332 def _builtins(self):
333 return {
334 'template': self,
335 'macros': self.macros,
336 'nothing': None,
337 }
340class PageTemplateFile(PageTemplate, BaseTemplateFile):
341 """File-based constructor.
343 Takes a string input as the only positional argument::
345 template = PageTemplateFile(absolute_path)
347 Note that the file-based template class comes with the expression
348 type ``load`` which loads templates relative to the provided
349 filename.
351 Below are listed the configuration arguments specific to
352 file-based templates; see the string-based template class for
353 general options and documentation:
355 Configuration (keyword arguments):
357 ``loader_class``
359 The provided class will be used to create the template loader
360 object. The default implementation supports relative and
361 absolute path specs.
363 The class must accept keyword arguments ``search_path``
364 (sequence of paths to search for relative a path spec) and
365 ``default_extension`` (if provided, this should be added to
366 any path spec).
368 ``prepend_relative_search_path``
370 Inserts the path relative to the provided template file path
371 into the template search path.
373 The default setting is ``True``.
375 ``search_path``
377 If provided, this is used as the search path for the ``load:``
378 expression. It must be a string or an iterable yielding a
379 sequence of strings.
381 """
383 expression_types = PageTemplate.expression_types.copy()
384 expression_types['load'] = partial(
385 ProxyExpr, '__loader',
386 ignore_prefix=False
387 )
389 prepend_relative_search_path = True
391 def __init__(self, filename, search_path=None, loader_class=TemplateLoader,
392 **config):
393 super(PageTemplateFile, self).__init__(filename, **config)
395 if search_path is None:
396 search_path = []
397 else:
398 if isinstance(search_path, string_type):
399 search_path = [search_path]
400 else:
401 search_path = list(search_path)
403 # If the flag is set (this is the default), prepend the path
404 # relative to the template file to the search path
405 if self.prepend_relative_search_path:
406 path = dirname(self.filename)
407 search_path.insert(0, path)
409 loader = loader_class(search_path=search_path, **config)
410 template_class = type(self)
412 # Bind relative template loader instance to the same template
413 # class, providing the same keyword arguments.
414 self._loader = loader.bind(template_class)
416 def _builtins(self):
417 d = super(PageTemplateFile, self)._builtins()
418 d['__loader'] = self._loader
419 return d
422class PageTextTemplate(PageTemplate):
423 """Text-based template class.
425 Takes a non-XML input::
427 template = PageTextTemplate("Hello, ${name}.")
429 This is similar to the standard library class ``string.Template``,
430 but uses the expression engine to substitute variables.
431 """
433 mode = "text"
436class PageTextTemplateFile(PageTemplateFile):
437 """File-based constructor."""
439 mode = "text"
441 def render(self, **vars):
442 result = super(PageTextTemplateFile, self).render(**vars)
443 return result.encode(self.encoding or 'utf-8')
446class Macro(object):
447 __slots__ = "include",
449 def __init__(self, render):
450 self.include = render
453class Macros(object):
454 __slots__ = "template",
456 def __init__(self, template):
457 self.template = template
459 def __getitem__(self, name):
460 name = name.replace('-', '_')
461 self.template.cook_check()
463 try:
464 function = getattr(self.template, "_render_%s" % name)
465 except AttributeError:
466 raise KeyError(
467 "Macro does not exist: '%s'." % name)
469 return Macro(function)
471 @property
472 def names(self):
473 self.template.cook_check()
475 result = []
476 for name in self.template.__dict__:
477 if name.startswith('_render_'):
478 result.append(name[8:])
479 return result