src/pyams_utils/tales.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
equal deleted inserted replaced
-1:000000000000 289:c8e21d7dd685
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 import re
       
    18 
       
    19 # import interfaces
       
    20 from pyams_utils.interfaces.tales import ITALESExtension
       
    21 
       
    22 # import packages
       
    23 from chameleon.astutil import Symbol
       
    24 from chameleon.codegen import template
       
    25 from chameleon.tales import StringExpr
       
    26 from zope.contentprovider.tales import addTALNamespaceData
       
    27 
       
    28 
       
    29 class ContextExprMixin(object):
       
    30     """Mixin-class for expression compilers"""
       
    31 
       
    32     transform = None
       
    33 
       
    34     def __call__(self, target, engine):
       
    35         # Make call to superclass to assign value to target
       
    36         assignment = super(ContextExprMixin, self).__call__(target, engine)
       
    37         transform = template("target = transform(econtext, target)",
       
    38                              target=target,
       
    39                              transform=self.transform)
       
    40         return assignment + transform
       
    41 
       
    42 
       
    43 FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
       
    44 ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
       
    45 
       
    46 
       
    47 def render_extension(econtext, name):
       
    48     """TALES extension renderer
       
    49 
       
    50     See :ref:`tales` for complete description.
       
    51     """
       
    52 
       
    53     def get_value(econtext, arg):
       
    54         """Extract argument value from context
       
    55 
       
    56         Extension expression language is quite simple. Values can be given as
       
    57         positioned strings, integers or named arguments of the same types.
       
    58         """
       
    59         arg = arg.strip()
       
    60         if arg.startswith('"') or arg.startswith("'"):
       
    61             # may be a quoted string...
       
    62             return arg[1:-1]
       
    63         if '=' in arg:
       
    64             key, value = arg.split('=', 1)
       
    65             value = get_value(econtext, value)
       
    66             return {key.strip(): value}
       
    67         try:
       
    68             arg = int(arg)  # check integer value
       
    69         except ValueError:
       
    70             args = arg.split('.')
       
    71             result = econtext.get(args.pop(0))
       
    72             for arg in args:
       
    73                 result = getattr(result, arg)
       
    74             return result
       
    75         else:
       
    76             return arg
       
    77 
       
    78     name = name.strip()
       
    79     context = econtext.get('context')
       
    80     request = econtext.get('request')
       
    81     view = econtext.get('view')
       
    82 
       
    83     args, kwargs = [], {}
       
    84     func_match = FUNCTION_EXPRESSION.match(name)
       
    85     if func_match:
       
    86         name, arguments = func_match.groups()
       
    87         for arg in map(lambda x: get_value(econtext, x), ARGUMENTS_EXPRESSION.findall(arguments)):
       
    88             if isinstance(arg, dict):
       
    89                 kwargs.update(arg)
       
    90             else:
       
    91                 args.append(arg)
       
    92 
       
    93     registry = request.registry
       
    94     extension = registry.queryMultiAdapter((context, request, view), ITALESExtension, name=name)
       
    95     if extension is None:
       
    96         extension = registry.queryMultiAdapter((context, request), ITALESExtension, name=name)
       
    97     if extension is None:
       
    98         extension = registry.queryAdapter(context, ITALESExtension, name=name)
       
    99 
       
   100     # provide a useful error message, if the extension was not found.
       
   101     if extension is None:
       
   102         return None
       
   103 
       
   104     # Insert the data gotten from the context
       
   105     addTALNamespaceData(extension, econtext)
       
   106 
       
   107     return extension.render(*args, **kwargs)
       
   108 
       
   109 
       
   110 class ExtensionExpr(ContextExprMixin, StringExpr):
       
   111     """tales: TALES expression
       
   112 
       
   113     This expression can be used to call a custom named adapter providing ITALESExtension interface.
       
   114     """
       
   115 
       
   116     transform = Symbol(render_extension)