Source code for pyams_utils.tales

#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#

"""PyAMS_utils.tales module

This module provides a custom TALES extension engine, which allows you to define custom
TALES expressions which can be used from Chameleon or Zope templates.
"""

import re

from chameleon.astutil import Symbol
from chameleon.codegen import template
from chameleon.tales import StringExpr
from zope.contentprovider.tales import addTALNamespaceData

from pyams_utils.interfaces.tales import ITALESExtension


__docformat__ = 'restructuredtext'


[docs]class ContextExprMixin: """Mixin-class for expression compilers""" transform = None def __call__(self, target, engine): # Make call to superclass to assign value to target assignment = super(ContextExprMixin, self).__call__(target, engine) transform = template("target = transform(econtext, target)", target=target, transform=self.transform) return assignment + transform
FUNCTION_EXPRESSION = re.compile(r'(.+)\((.+)\)', re.MULTILINE | re.DOTALL) ARGUMENTS_EXPRESSION = re.compile(r'[^(,)]+')
[docs]def render_extension(econtext, name): """TALES extension renderer See :ref:`tales` for complete description. The requested extension can be called with our without arguments, like in ${structure:tales:my_expression} or ${structure:tales:my_expression(arg1, arg2)}. In the second form, arguments will be passed to the "render" method; arguments can be static (like strings or integers), or can be variables defined into current template context; other Python expressions including computations or functions calls are actually not supported, but dotted syntax is supported to access inner attributes of variables. """ def get_value(econtext, arg): """Extract argument value from context Extension expression language is quite simple. Values can be given as positioned strings, integers or named arguments of the same types. """ arg = arg.strip() if arg.startswith('"') or arg.startswith("'"): # may be a quoted string... return arg[1:-1] if '=' in arg: key, value = arg.split('=', 1) value = get_value(econtext, value) return {key.strip(): value} try: arg = int(arg) # check integer value except ValueError: args = arg.split('.') result = econtext.get(args.pop(0)) for arg in args: # pylint: disable=redefined-argument-from-local result = getattr(result, arg) return result else: return arg name = name.strip() context = econtext.get('context') request = econtext.get('request') view = econtext.get('view') args, kwargs = [], {} func_match = FUNCTION_EXPRESSION.match(name) if func_match: name, arguments = func_match.groups() for arg in map(lambda x: get_value(econtext, x), ARGUMENTS_EXPRESSION.findall(arguments)): if isinstance(arg, dict): kwargs.update(arg) else: args.append(arg) registry = request.registry extension = registry.queryMultiAdapter((context, request, view), ITALESExtension, name=name) if extension is None: extension = registry.queryMultiAdapter((context, request), ITALESExtension, name=name) if extension is None: extension = registry.queryAdapter(context, ITALESExtension, name=name) # return an empty string if the extension was not found. if extension is None: return '' # Insert the data gotten from the context addTALNamespaceData(extension, econtext) return extension.render(*args, **kwargs)
[docs]class ExtensionExpr(ContextExprMixin, StringExpr): """tales: TALES expression This expression can be used to call a custom named adapter providing ITALESExtension interface. """ transform = Symbol(render_extension)