Code

documentation cleanup
[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 ##############################################################################
14 """Page Template Expression Engine
16 Page Template-specific implementation of TALES, with handlers
17 for Python expressions, string literals, and paths.
20 Modified for Roundup 0.5 release:
22 - Removed all Zope-specific code (doesn't even try to import that stuff now)
23 - Removed all Acquisition
24 - Made traceback info more informative
26 """
27 __docformat__ = 'restructuredtext'
29 __version__='$Revision: 1.9 $'[11:-2]
31 import re, sys
32 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
33      Undefined, Default, _parse_expr
34 from string import strip, split, join, replace, lstrip
36 _engine = None
37 def getEngine():
38     global _engine
39     if _engine is None:
40         from PathIterator import Iterator
41         _engine = Engine(Iterator)
42         installHandlers(_engine)
43     return _engine
45 def installHandlers(engine):
46     reg = engine.registerType
47     pe = PathExpr
48     for pt in ('standard', 'path', 'exists', 'nocall'):
49         reg(pt, pe)
50     reg('string', StringExpr)
51     reg('python', PythonExpr)
52     reg('not', NotExpr)
53     reg('defer', DeferExpr)
55 from PythonExpr import getSecurityManager, PythonExpr
56 try:
57     from zExceptions import Unauthorized
58 except ImportError:
59     Unauthorized = "Unauthorized"
60 def call_with_ns(f, ns, arg=1):
61     if arg==2:
62         return f(None, ns)
63     else:
64         return f(ns)
66 class _SecureModuleImporter:
67     """Simple version of the importer for use with trusted code."""
68     __allow_access_to_unprotected_subobjects__ = 1
69     def __getitem__(self, module):
70         __import__(module)
71         return sys.modules[module]
73 Undefs = (Undefined, AttributeError, KeyError,
74           TypeError, IndexError, Unauthorized)
76 def render(ob, ns):
77     """
78     Calls the object, possibly a document template, or just returns it if
79     not callable.  (From DT_Util.py)
80     """
81     if hasattr(ob, '__render_with_namespace__'):
82         ob = call_with_ns(ob.__render_with_namespace__, ns)
83     else:
84         base = ob
85         if callable(base):
86             try:
87                 if getattr(base, 'isDocTemp', 0):
88                     ob = call_with_ns(ob, ns, 2)
89                 else:
90                     ob = ob()
91             except AttributeError, n:
92                 if str(n) != '__call__':
93                     raise
94     return ob
96 class SubPathExpr:
97     def __init__(self, path):
98         self._path = path = split(strip(path), '/')
99         self._base = base = path.pop(0)
100         if not _valid_name(base):
101             raise CompilerError, 'Invalid variable name "%s"' % base
102         # Parse path
103         self._dp = dp = []
104         for i in range(len(path)):
105             e = path[i]
106             if e[:1] == '?' and _valid_name(e[1:]):
107                 dp.append((i, e[1:]))
108         dp.reverse()
110     def _eval(self, econtext,
111               list=list, isinstance=isinstance, StringType=type('')):
112         vars = econtext.vars
113         path = self._path
114         if self._dp:
115             path = list(path) # Copy!
116             for i, varname in self._dp:
117                 val = vars[varname]
118                 if isinstance(val, StringType):
119                     path[i] = val
120                 else:
121                     # If the value isn't a string, assume it's a sequence
122                     # of path names.
123                     path[i:i+1] = list(val)
124         base = self._base
125         __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
126         if base == 'CONTEXTS':
127             ob = econtext.contexts
128         else:
129             ob = vars[base]
130         if isinstance(ob, DeferWrapper):
131             ob = ob()
132         if path:
133             ob = restrictedTraverse(ob, path, getSecurityManager())
134         return ob
136 class PathExpr:
137     def __init__(self, name, expr, engine):
138         self._s = expr
139         self._name = name
140         self._hybrid = 0
141         paths = split(expr, '|')
142         self._subexprs = []
143         add = self._subexprs.append
144         for i in range(len(paths)):
145             path = lstrip(paths[i])
146             if _parse_expr(path):
147                 # This part is the start of another expression type,
148                 # so glue it back together and compile it.
149                 add(engine.compile(lstrip(join(paths[i:], '|'))))
150                 self._hybrid = 1
151                 break
152             add(SubPathExpr(path)._eval)
154     def _exists(self, econtext):
155         for expr in self._subexprs:
156             try:
157                 expr(econtext)
158             except Undefs:
159                 pass
160             else:
161                 return 1
162         return 0
164     def _eval(self, econtext,
165               isinstance=isinstance, StringType=type(''), render=render):
166         for expr in self._subexprs[:-1]:
167             # Try all but the last subexpression, skipping undefined ones.
168             try:
169                 ob = expr(econtext)
170             except Undefs:
171                 pass
172             else:
173                 break
174         else:
175             # On the last subexpression allow exceptions through, and
176             # don't autocall if the expression was not a subpath.
177             ob = self._subexprs[-1](econtext)
178             if self._hybrid:
179                 return ob
181         if self._name == 'nocall' or isinstance(ob, StringType):
182             return ob
183         # Return the rendered object
184         return render(ob, econtext.vars)
186     def __call__(self, econtext):
187         if self._name == 'exists':
188             return self._exists(econtext)
189         return self._eval(econtext)
191     def __str__(self):
192         return '%s expression %s' % (self._name, `self._s`)
194     def __repr__(self):
195         return '%s:%s' % (self._name, `self._s`)
197             
198 _interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
200 class StringExpr:
201     def __init__(self, name, expr, engine):
202         self._s = expr
203         if '%' in expr:
204             expr = replace(expr, '%', '%%')
205         self._vars = vars = []
206         if '$' in expr:
207             parts = []
208             for exp in split(expr, '$$'):
209                 if parts: parts.append('$')
210                 m = _interp.search(exp)
211                 while m is not None:
212                     parts.append(exp[:m.start()])
213                     parts.append('%s')
214                     vars.append(PathExpr('path', m.group(1) or m.group(2),
215                                          engine))
216                     exp = exp[m.end():]
217                     m = _interp.search(exp)
218                 if '$' in exp:
219                     raise CompilerError, (
220                         '$ must be doubled or followed by a simple path')
221                 parts.append(exp)
222             expr = join(parts, '')
223         self._expr = expr
224         
225     def __call__(self, econtext):
226         vvals = []
227         for var in self._vars:
228             v = var(econtext)
229             if isinstance(v, Exception):
230                 raise v
231             vvals.append(v)
232         return self._expr % tuple(vvals)
234     def __str__(self):
235         return 'string expression %s' % `self._s`
237     def __repr__(self):
238         return 'string:%s' % `self._s`
240 class NotExpr:
241     def __init__(self, name, expr, compiler):
242         self._s = expr = lstrip(expr)
243         self._c = compiler.compile(expr)
244         
245     def __call__(self, econtext):
246         return not econtext.evaluateBoolean(self._c)
248     def __repr__(self):
249         return 'not:%s' % `self._s`
251 class DeferWrapper:
252     def __init__(self, expr, econtext):
253         self._expr = expr
254         self._econtext = econtext
256     def __str__(self):
257         return str(self())
259     def __call__(self):
260         return self._expr(self._econtext)
262 class DeferExpr:
263     def __init__(self, name, expr, compiler):
264         self._s = expr = lstrip(expr)
265         self._c = compiler.compile(expr)
266         
267     def __call__(self, econtext):
268         return DeferWrapper(self._c, econtext)
270     def __repr__(self):
271         return 'defer:%s' % `self._s`
273 class TraversalError:
274     def __init__(self, path, name):
275         self.path = path
276         self.name = name
278 def restrictedTraverse(self, path, securityManager,
279                        get=getattr, has=hasattr, N=None, M=[],
280                        TupleType=type(()) ):
282     REQUEST = {'path': path}
283     REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
284     if not path[0]:
285         # If the path starts with an empty string, go to the root first.
286         self = self.getPhysicalRoot()
287         path.pop(0)
289     path.reverse()
290     object = self
291     #print 'TRAVERSE', (object, path)
292     done = []
293     while path:
294         name = path.pop()
295         __traceback_info__ = TraversalError(done, name)
297 #        if isinstance(name, TupleType):
298 #            object = apply(object, name)
299 #            continue
301 #        if name[0] == '_':
302 #            # Never allowed in a URL.
303 #            raise AttributeError, name
305         # Try an attribute.
306         o = get(object, name, M)
307 #       print '...', (object, name, M, o)
308         if o is M:
309             # Try an item.
310 #           print '... try an item'
311             try:
312                 # XXX maybe in Python 2.2 we can just check whether
313                 # the object has the attribute "__getitem__"
314                 # instead of blindly catching exceptions.
315                 o = object[name]
316             except AttributeError, exc:
317                 if str(exc).find('__getitem__') >= 0:
318                     # The object does not support the item interface.
319                     # Try to re-raise the original attribute error.
320                     # XXX I think this only happens with
321                     # ExtensionClass instances.
322                     get(object, name)
323                 raise
324             except TypeError, exc:
325                 if str(exc).find('unsubscriptable') >= 0:
326                     # The object does not support the item interface.
327                     # Try to re-raise the original attribute error.
328                     # XXX This is sooooo ugly.
329                     get(object, name)
330                 raise
331         #print '... object is now', `o`
332         object = o
333         done.append((name, o))
335     return object