Code

Add config-option "nosy" to messages_to_author setting in [nosy] section
[roundup.git] / roundup / cgi / PageTemplates / Expressions.py
1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.0 (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 # Modified for Roundup:
14
15 # 1. removed all Zope-specific code (doesn't even try to import that stuff now)
16 # 2. removed all Acquisition
17 # 3. removed blocking of leading-underscore URL components
19 """Page Template Expression Engine
21 Page Template-specific implementation of TALES, with handlers
22 for Python expressions, string literals, and paths.
23 """
25 __version__='$Revision: 1.12 $'[11:-2]
27 import re, sys
28 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
29      Undefined, Default, _parse_expr
32 _engine = None
33 def getEngine():
34     global _engine
35     if _engine is None:
36         from PathIterator import Iterator
37         _engine = Engine(Iterator)
38         installHandlers(_engine)
39     return _engine
41 def installHandlers(engine):
42     reg = engine.registerType
43     pe = PathExpr
44     for pt in ('standard', 'path', 'exists', 'nocall'):
45         reg(pt, pe)
46     reg('string', StringExpr)
47     reg('python', PythonExpr)
48     reg('not', NotExpr)
49     reg('defer', DeferExpr)
51 from PythonExpr import getSecurityManager, PythonExpr
52 guarded_getattr = getattr
53 try:
54     from zExceptions import Unauthorized
55 except ImportError:
56     class Unauthorized(Exception):
57         pass
59 def acquisition_security_filter(orig, inst, name, v, real_validate):
60     if real_validate(orig, inst, name, v):
61         return 1
62     raise Unauthorized, name
64 def call_with_ns(f, ns, arg=1):
65     if arg==2:
66         return f(None, ns)
67     else:
68         return f(ns)
70 class _SecureModuleImporter:
71     """Simple version of the importer for use with trusted code."""
72     __allow_access_to_unprotected_subobjects__ = 1
73     def __getitem__(self, module):
74         __import__(module)
75         return sys.modules[module]
77 SecureModuleImporter = _SecureModuleImporter()
79 Undefs = (Undefined, AttributeError, KeyError,
80           TypeError, IndexError, Unauthorized)
82 def render(ob, ns):
83     """
84     Calls the object, possibly a document template, or just returns it if
85     not callable.  (From DT_Util.py)
86     """
87     if hasattr(ob, '__render_with_namespace__'):
88         ob = call_with_ns(ob.__render_with_namespace__, ns)
89     else:
90         base = ob
91         if callable(base):
92             try:
93                 if getattr(base, 'isDocTemp', 0):
94                     ob = call_with_ns(ob, ns, 2)
95                 else:
96                     ob = ob()
97             except AttributeError, n:
98                 if str(n) != '__call__':
99                     raise
100     return ob
102 class SubPathExpr:
103     def __init__(self, path):
104         self._path = path = path.strip().split('/')
105         self._base = base = path.pop(0)
106         if base and not _valid_name(base):
107             raise CompilerError, 'Invalid variable name "%s"' % base
108         # Parse path
109         self._dp = dp = []
110         for i in range(len(path)):
111             e = path[i]
112             if e[:1] == '?' and _valid_name(e[1:]):
113                 dp.append((i, e[1:]))
114         dp.reverse()
116     def _eval(self, econtext,
117               list=list, isinstance=isinstance, StringType=type('')):
118         vars = econtext.vars
119         path = self._path
120         if self._dp:
121             path = list(path) # Copy!
122             for i, varname in self._dp:
123                 val = vars[varname]
124                 if isinstance(val, StringType):
125                     path[i] = val
126                 else:
127                     # If the value isn't a string, assume it's a sequence
128                     # of path names.
129                     path[i:i+1] = list(val)
130         base = self._base
131         __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
132         if base == 'CONTEXTS' or not base:
133             ob = econtext.contexts
134         else:
135             ob = vars[base]
136         if isinstance(ob, DeferWrapper):
137             ob = ob()
138         if path:
139             ob = restrictedTraverse(ob, path, getSecurityManager())
140         return ob
142 class PathExpr:
143     def __init__(self, name, expr, engine):
144         self._s = expr
145         self._name = name
146         self._hybrid = 0
147         paths = expr.split('|')
148         self._subexprs = []
149         add = self._subexprs.append
150         for i in range(len(paths)):
151             path = paths[i].lstrip()
152             if _parse_expr(path):
153                 # This part is the start of another expression type,
154                 # so glue it back together and compile it.
155                 add(engine.compile(('|'.join(paths[i:]).lstrip())))
156                 self._hybrid = 1
157                 break
158             add(SubPathExpr(path)._eval)
160     def _exists(self, econtext):
161         for expr in self._subexprs:
162             try:
163                 expr(econtext)
164             except Undefs:
165                 pass
166             else:
167                 return 1
168         return 0
170     def _eval(self, econtext,
171               isinstance=isinstance, StringType=type(''), render=render):
172         for expr in self._subexprs[:-1]:
173             # Try all but the last subexpression, skipping undefined ones.
174             try:
175                 ob = expr(econtext)
176             except Undefs:
177                 pass
178             else:
179                 break
180         else:
181             # On the last subexpression allow exceptions through, and
182             # don't autocall if the expression was not a subpath.
183             ob = self._subexprs[-1](econtext)
184             if self._hybrid:
185                 return ob
187         if self._name == 'nocall' or isinstance(ob, StringType):
188             return ob
189         # Return the rendered object
190         return render(ob, econtext.vars)
192     def __call__(self, econtext):
193         if self._name == 'exists':
194             return self._exists(econtext)
195         return self._eval(econtext)
197     def __str__(self):
198         return '%s expression %s' % (self._name, `self._s`)
200     def __repr__(self):
201         return '%s:%s' % (self._name, `self._s`)
204 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
206 class StringExpr:
207     def __init__(self, name, expr, engine):
208         self._s = expr
209         if '%' in expr:
210             expr = expr.replace('%', '%%')
211         self._vars = vars = []
212         if '$' in expr:
213             parts = []
214             for exp in expr.split('$$'):
215                 if parts: parts.append('$')
216                 m = _interp.search(exp)
217                 while m is not None:
218                     parts.append(exp[:m.start()])
219                     parts.append('%s')
220                     vars.append(PathExpr('path', m.group(1) or m.group(2),
221                                          engine))
222                     exp = exp[m.end():]
223                     m = _interp.search(exp)
224                 if '$' in exp:
225                     raise CompilerError, (
226                         '$ must be doubled or followed by a simple path')
227                 parts.append(exp)
228             expr = ''.join(parts)
229         self._expr = expr
231     def __call__(self, econtext):
232         vvals = []
233         for var in self._vars:
234             v = var(econtext)
235             # I hope this isn't in use anymore.
236             ## if isinstance(v, Exception):
237             ##     raise v
238             vvals.append(v)
239         return self._expr % tuple(vvals)
241     def __str__(self):
242         return 'string expression %s' % `self._s`
244     def __repr__(self):
245         return 'string:%s' % `self._s`
247 class NotExpr:
248     def __init__(self, name, expr, compiler):
249         self._s = expr = expr.lstrip()
250         self._c = compiler.compile(expr)
252     def __call__(self, econtext):
253         # We use the (not x) and 1 or 0 formulation to avoid changing
254         # the representation of the result in Python 2.3, where the
255         # result of "not" becomes an instance of bool.
256         return (not econtext.evaluateBoolean(self._c)) and 1 or 0
258     def __repr__(self):
259         return 'not:%s' % `self._s`
261 class DeferWrapper:
262     def __init__(self, expr, econtext):
263         self._expr = expr
264         self._econtext = econtext
266     def __str__(self):
267         return str(self())
269     def __call__(self):
270         return self._expr(self._econtext)
272 class DeferExpr:
273     def __init__(self, name, expr, compiler):
274         self._s = expr = expr.lstrip()
275         self._c = compiler.compile(expr)
277     def __call__(self, econtext):
278         return DeferWrapper(self._c, econtext)
280     def __repr__(self):
281         return 'defer:%s' % `self._s`
283 class TraversalError:
284     def __init__(self, path, name):
285         self.path = path
286         self.name = name
290 def restrictedTraverse(object, path, securityManager,
291                        get=getattr, has=hasattr, N=None, M=[],
292                        TupleType=type(()) ):
294     REQUEST = {'path': path}
295     REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
296     path.reverse()
297     validate = securityManager.validate
298     __traceback_info__ = REQUEST
299     done = []
300     while path:
301         name = path.pop()
302         __traceback_info__ = TraversalError(done, name)
304         if isinstance(name, TupleType):
305             object = object(*name)
306             continue
308         if not name:
309             # Skip directly to item access
310             o = object[name]
311             # Check access to the item.
312             if not validate(object, object, name, o):
313                 raise Unauthorized, name
314             object = o
315             continue
317         # Try an attribute.
318         o = guarded_getattr(object, name, M)
319         if o is M:
320             # Try an item.
321             try:
322                 # XXX maybe in Python 2.2 we can just check whether
323                 # the object has the attribute "__getitem__"
324                 # instead of blindly catching exceptions.
325                 o = object[name]
326             except AttributeError, exc:
327                 if str(exc).find('__getitem__') >= 0:
328                     # The object does not support the item interface.
329                     # Try to re-raise the original attribute error.
330                     # XXX I think this only happens with
331                     # ExtensionClass instances.
332                     guarded_getattr(object, name)
333                 raise
334             except TypeError, exc:
335                 if str(exc).find('unsubscriptable') >= 0:
336                     # The object does not support the item interface.
337                     # Try to re-raise the original attribute error.
338                     # XXX This is sooooo ugly.
339                     guarded_getattr(object, name)
340                 raise
341         done.append((name, o))
342         object = o
344     return object