diff -r 000000000000 -r c8e21d7dd685 src/pyams_utils/tales.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_utils/tales.py Wed Dec 05 12:45:56 2018 +0100 @@ -0,0 +1,116 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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. +# + +__docformat__ = 'restructuredtext' + + +# import standard library +import re + +# import interfaces +from pyams_utils.interfaces.tales import ITALESExtension + +# import packages +from chameleon.astutil import Symbol +from chameleon.codegen import template +from chameleon.tales import StringExpr +from zope.contentprovider.tales import addTALNamespaceData + + +class ContextExprMixin(object): + """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('(.+)\((.+)\)', re.MULTILINE | re.DOTALL) +ARGUMENTS_EXPRESSION = re.compile('[^(,)]+') + + +def render_extension(econtext, name): + """TALES extension renderer + + See :ref:`tales` for complete description. + """ + + 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: + 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) + + # provide a useful error message, if the extension was not found. + if extension is None: + return None + + # Insert the data gotten from the context + addTALNamespaceData(extension, econtext) + + return extension.render(*args, **kwargs) + + +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)