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