--- 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()