Code

ac8aec9e638160a6c2bc998c4775b8a997f3e5f8
[roundup.git] / roundup / cgi / PageTemplates / TALES.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 """TALES
15 An implementation of a generic TALES engine
17 Modified for Roundup 0.5 release:
19 - changed imports to import from roundup.cgi
20 """
21 __docformat__ = 'restructuredtext'
23 __version__='$Revision: 1.6 $'[11:-2]
25 import re, sys
26 from roundup.cgi import ZTUtils
27 from MultiMapping import MultiMapping
29 StringType = type('')
31 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
32 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
33 _valid_name = re.compile('%s$' % NAME_RE).match
35 class TALESError(Exception):
36     """Error during TALES expression evaluation"""
38 class Undefined(TALESError):
39     '''Exception raised on traversal of an undefined path'''
41 class RegistrationError(Exception):
42     '''TALES Type Registration Error'''
44 class CompilerError(Exception):
45     '''TALES Compiler Error'''
47 class Default:
48     '''Retain Default'''
49 Default = Default()
51 _marker = []
53 class SafeMapping(MultiMapping):
54     '''Mapping with security declarations and limited method exposure.
56     Since it subclasses MultiMapping, this class can be used to wrap
57     one or more mapping objects.  Restricted Python code will not be
58     able to mutate the SafeMapping or the wrapped mappings, but will be
59     able to read any value.
60     '''
61     __allow_access_to_unprotected_subobjects__ = 1
62     push = pop = None
64     _push = MultiMapping.push
65     _pop = MultiMapping.pop
67     def has_get(self, key, _marker=[]):
68         v = self.get(key, _marker)
69         return v is not _marker, v
71 class Iterator(ZTUtils.Iterator):
72     def __init__(self, name, seq, context):
73         ZTUtils.Iterator.__init__(self, seq)
74         self.name = name
75         self._context = context
77     def next(self):
78         if ZTUtils.Iterator.next(self):
79             self._context.setLocal(self.name, self.item)
80             return 1
81         return 0
84 class ErrorInfo:
85     """Information about an exception passed to an on-error handler."""
86     __allow_access_to_unprotected_subobjects__ = 1
88     def __init__(self, err, position=(None, None)):
89         if isinstance(err, Exception):
90             self.type = err.__class__
91             self.value = err
92         else:
93             self.type = err
94             self.value = None
95         self.lineno = position[0]
96         self.offset = position[1]
99 class Engine:
100     '''Expression Engine
102     An instance of this class keeps a mutable collection of expression
103     type handlers.  It can compile expression strings by delegating to
104     these handlers.  It can provide an expression Context, which is
105     capable of holding state and evaluating compiled expressions.
106     '''
107     Iterator = Iterator
109     def __init__(self, Iterator=None):
110         self.types = {}
111         if Iterator is not None:
112             self.Iterator = Iterator
114     def registerType(self, name, handler):
115         if not _valid_name(name):
116             raise RegistrationError, 'Invalid Expression type "%s".' % name
117         types = self.types
118         if types.has_key(name):
119             raise RegistrationError, (
120                 'Multiple registrations for Expression type "%s".' %
121                 name)
122         types[name] = handler
124     def getTypes(self):
125         return self.types
127     def compile(self, expression):
128         m = _parse_expr(expression)
129         if m:
130             type = m.group(1)
131             expr = expression[m.end():]
132         else:
133             type = "standard"
134             expr = expression
135         try:
136             handler = self.types[type]
137         except KeyError:
138             raise CompilerError, (
139                 'Unrecognized expression type "%s".' % type)
140         return handler(type, expr, self)
141     
142     def getContext(self, contexts=None, **kwcontexts):
143         if contexts is not None:
144             if kwcontexts:
145                 kwcontexts.update(contexts)
146             else:
147                 kwcontexts = contexts
148         return Context(self, kwcontexts)
150     def getCompilerError(self):
151         return CompilerError
153 class Context:
154     '''Expression Context
156     An instance of this class holds context information that it can
157     use to evaluate compiled expressions.
158     '''
160     _context_class = SafeMapping
161     position = (None, None)
162     source_file = None
164     def __init__(self, compiler, contexts):
165         self._compiler = compiler
166         self.contexts = contexts
167         contexts['nothing'] = None
168         contexts['default'] = Default
170         self.repeat_vars = rv = {}
171         # Wrap this, as it is visible to restricted code
172         contexts['repeat'] = rep =  self._context_class(rv)
173         contexts['loop'] = rep # alias
175         self.global_vars = gv = contexts.copy()
176         self.local_vars = lv = {}
177         self.vars = self._context_class(gv, lv)
179         # Keep track of what needs to be popped as each scope ends.
180         self._scope_stack = []
182     def getCompiler(self):
183         return self._compiler
185     def beginScope(self):
186         self._scope_stack.append([self.local_vars.copy()])
188     def endScope(self):
189         scope = self._scope_stack.pop()
190         self.local_vars = lv = scope[0]
191         v = self.vars
192         v._pop()
193         v._push(lv)
194         # Pop repeat variables, if any
195         i = len(scope) - 1
196         while i:
197             name, value = scope[i]
198             if value is None:
199                 del self.repeat_vars[name]
200             else:
201                 self.repeat_vars[name] = value
202             i = i - 1
204     def setLocal(self, name, value):
205         self.local_vars[name] = value
207     def setGlobal(self, name, value):
208         self.global_vars[name] = value
210     def setRepeat(self, name, expr):
211         expr = self.evaluate(expr)
212         if not expr:
213             return self._compiler.Iterator(name, (), self)
214         it = self._compiler.Iterator(name, expr, self)
215         old_value = self.repeat_vars.get(name)
216         self._scope_stack[-1].append((name, old_value))
217         self.repeat_vars[name] = it
218         return it
220     def evaluate(self, expression,
221                  isinstance=isinstance, StringType=StringType):
222         if isinstance(expression, StringType):
223             expression = self._compiler.compile(expression)
224         __traceback_supplement__ = (
225             TALESTracebackSupplement, self, expression)
226         v = expression(self)
227         return v
229     evaluateValue = evaluate
230     evaluateBoolean = evaluate
232     def evaluateText(self, expr):
233         text = self.evaluate(expr)
234         if text is Default or text is None:
235             return text
236         return str(text)
238     def evaluateStructure(self, expr):
239         return self.evaluate(expr)
240     evaluateStructure = evaluate
242     def evaluateMacro(self, expr):
243         # XXX Should return None or a macro definition
244         return self.evaluate(expr)
245     evaluateMacro = evaluate
247     def createErrorInfo(self, err, position):
248         return ErrorInfo(err, position)
250     def getDefault(self):
251         return Default
253     def setSourceFile(self, source_file):
254         self.source_file = source_file
256     def setPosition(self, position):
257         self.position = position
261 class TALESTracebackSupplement:
262     """Implementation of ITracebackSupplement"""
263     def __init__(self, context, expression):
264         self.context = context
265         self.source_url = context.source_file
266         self.line = context.position[0]
267         self.column = context.position[1]
268         self.expression = repr(expression)
270     def getInfo(self, as_html=0):
271         import pprint
272         data = self.context.contexts.copy()
273         s = pprint.pformat(data)
274         if not as_html:
275             return '   - Names:\n      %s' % string.replace(s, '\n', '\n      ')
276         else:
277             from cgi import escape
278             return '<b>Names:</b><pre>%s</pre>' % (escape(s))
279         return None
283 class SimpleExpr:
284     '''Simple example of an expression type handler'''
285     def __init__(self, name, expr, engine):
286         self._name = name
287         self._expr = expr
288     def __call__(self, econtext):
289         return self._name, self._expr
290     def __repr__(self):
291         return '<SimpleExpr %s %s>' % (self._name, `self._expr`)