Updated "provider" expression to be able to pass arguments to "update()" method
authorThierry Florac <tflorac@ulthar.net>
Fri, 18 Jan 2019 13:58:47 +0100
changeset 43 4bbf32d81a21
parent 42 512b92eeaa79
child 44 95e64220b38c
Updated "provider" expression to be able to pass arguments to "update()" method
src/pyams_viewlet/provider.py
--- a/src/pyams_viewlet/provider.py	Fri Jan 18 10:23:14 2019 +0100
+++ b/src/pyams_viewlet/provider.py	Fri Jan 18 13:58:47 2019 +0100
@@ -24,26 +24,79 @@
 from pyams_utils.tales import ContextExprMixin
 
 
+FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
+ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
+
 CONTENT_PROVIDER_NAME = re.compile('([A-Za-z0-9_\-\.]+)')
 
 
 def render_content_provider(econtext, name):
-    match = CONTENT_PROVIDER_NAME.match(name.strip())
-    if match:
-        name = match.groups()[0]
-    else:
-        raise ContentProviderLookupError(name)
+    """TALES provider: content provider
+
+    This TALES expression is used to render a registered "content provider", which
+    is an adapter providing IContentProvider interface; adapter lookup is based on
+    current context, request and view.
+
+    The requested provider can be called with our without arguments, like in
+    ${structure:provider:my_provider} or ${structure:provider:my_provider(arg1, arg2)}.
+    In the second form, arguments will be passed to the "update" 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:
+                result = getattr(result, arg)
+            return result
+        else:
+            return arg
+
+    name = name.strip()
     context = econtext.get('context')
     request = econtext.get('request')
     if isinstance(request, PyramidPublisherRequest):
         request = request._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)
+    else:
+        match = CONTENT_PROVIDER_NAME.match(name)
+        if match:
+            name = match.groups()[0]
+        else:
+            raise ContentProviderLookupError(name)
+
     registry = request.registry
     provider = registry.queryMultiAdapter((context, request, view), IContentProvider, name=name)
 
-    # provide a useful error message, if the provider was not found.
+    # raise an exception if the provider was not found.
     if provider is None:
         raise ContentProviderLookupError(name)
 
@@ -56,7 +109,7 @@
 
     # Stage 1: Do the state update
     registry.notify(BeforeUpdateEvent(provider, request))
-    provider.update()
+    provider.update(*args, **kwargs)
 
     # Stage 2: Render the HTML content
     return provider.render()