Code

more doc, bugfix in Batch
[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 """
22 __version__='$Revision: 1.2 $'[11:-2]
24 import re, sys
25 from roundup.cgi import ZTUtils
26 from MultiMapping import MultiMapping
28 StringType = type('')
30 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
31 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
32 _valid_name = re.compile('%s$' % NAME_RE).match
34 class TALESError(Exception):
35     """Error during TALES expression evaluation"""
37 class Undefined(TALESError):
38     '''Exception raised on traversal of an undefined path'''
40 class RegistrationError(Exception):
41     '''TALES Type Registration Error'''
43 class CompilerError(Exception):
44     '''TALES Compiler Error'''
46 class Default:
47     '''Retain Default'''
48 Default = Default()
50 _marker = []
52 class SafeMapping(MultiMapping):
53     '''Mapping with security declarations and limited method exposure.
55     Since it subclasses MultiMapping, this class can be used to wrap
56     one or more mapping objects.  Restricted Python code will not be
57     able to mutate the SafeMapping or the wrapped mappings, but will be
58     able to read any value.
59     '''
60     __allow_access_to_unprotected_subobjects__ = 1
61     push = pop = None
63     _push = MultiMapping.push
64     _pop = MultiMapping.pop
66     def has_get(self, key, _marker=[]):
67         v = self.get(key, _marker)
68         return v is not _marker, v
70 class Iterator(ZTUtils.Iterator):
71     def __init__(self, name, seq, context):
72         ZTUtils.Iterator.__init__(self, seq)
73         self.name = name
74         self._context = context
76     def next(self):
77         if ZTUtils.Iterator.next(self):
78             self._context.setLocal(self.name, self.item)
79             return 1
80         return 0
83 class ErrorInfo:
84     """Information about an exception passed to an on-error handler."""
85     __allow_access_to_unprotected_subobjects__ = 1
87     def __init__(self, err, position=(None, None)):
88         if isinstance(err, Exception):
89             self.type = err.__class__
90             self.value = err
91         else:
92             self.type = err
93             self.value = None
94         self.lineno = position[0]
95         self.offset = position[1]
98 class Engine:
99     '''Expression Engine
101     An instance of this class keeps a mutable collection of expression
102     type handlers.  It can compile expression strings by delegating to
103     these handlers.  It can provide an expression Context, which is
104     capable of holding state and evaluating compiled expressions.
105     '''
106     Iterator = Iterator
108     def __init__(self, Iterator=None):
109         self.types = {}
110         if Iterator is not None:
111             self.Iterator = Iterator
113     def registerType(self, name, handler):
114         if not _valid_name(name):
115             raise RegistrationError, 'Invalid Expression type "%s".' % name
116         types = self.types
117         if types.has_key(name):
118             raise RegistrationError, (
119                 'Multiple registrations for Expression type "%s".' %
120                 name)
121         types[name] = handler
123     def getTypes(self):
124         return self.types
126     def compile(self, expression):
127         m = _parse_expr(expression)
128         if m:
129             type = m.group(1)
130             expr = expression[m.end():]
131         else:
132             type = "standard"
133             expr = expression
134         try:
135             handler = self.types[type]
136         except KeyError:
137             raise CompilerError, (
138                 'Unrecognized expression type "%s".' % type)
139         return handler(type, expr, self)
140     
141     def getContext(self, contexts=None, **kwcontexts):
142         if contexts is not None:
143             if kwcontexts:
144                 kwcontexts.update(contexts)
145             else:
146                 kwcontexts = contexts
147         return Context(self, kwcontexts)
149     def getCompilerError(self):
150         return CompilerError
152 class Context:
153     '''Expression Context
155     An instance of this class holds context information that it can
156     use to evaluate compiled expressions.
157     '''
159     _context_class = SafeMapping
160     position = (None, None)
161     source_file = None
163     def __init__(self, engine, contexts):
164         self._engine = engine
165         self.contexts = contexts
166         contexts['nothing'] = None
167         contexts['default'] = Default
169         self.repeat_vars = rv = {}
170         # Wrap this, as it is visible to restricted code
171         contexts['repeat'] = rep =  self._context_class(rv)
172         contexts['loop'] = rep # alias
174         self.global_vars = gv = contexts.copy()
175         self.local_vars = lv = {}
176         self.vars = self._context_class(gv, lv)
178         # Keep track of what needs to be popped as each scope ends.
179         self._scope_stack = []
181     def beginScope(self):
182         self._scope_stack.append([self.local_vars.copy()])
184     def endScope(self):
185         scope = self._scope_stack.pop()
186         self.local_vars = lv = scope[0]
187         v = self.vars
188         v._pop()
189         v._push(lv)
190         # Pop repeat variables, if any
191         i = len(scope) - 1
192         while i:
193             name, value = scope[i]
194             if value is None:
195                 del self.repeat_vars[name]
196             else:
197                 self.repeat_vars[name] = value
198             i = i - 1
200     def setLocal(self, name, value):
201         self.local_vars[name] = value
203     def setGlobal(self, name, value):
204         self.global_vars[name] = value
206     def setRepeat(self, name, expr):
207         expr = self.evaluate(expr)
208         if not expr:
209             return self._engine.Iterator(name, (), self)
210         it = self._engine.Iterator(name, expr, self)
211         old_value = self.repeat_vars.get(name)
212         self._scope_stack[-1].append((name, old_value))
213         self.repeat_vars[name] = it
214         return it
216     def evaluate(self, expression,
217                  isinstance=isinstance, StringType=StringType):
218         if isinstance(expression, StringType):
219             expression = self._engine.compile(expression)
220         __traceback_supplement__ = (
221             TALESTracebackSupplement, self, expression)
222         v = expression(self)
223         return v
225     evaluateValue = evaluate
227     def evaluateBoolean(self, expr):
228         return not not self.evaluate(expr)
230     def evaluateText(self, expr, None=None):
231         text = self.evaluate(expr)
232         if text is Default or text is None:
233             return text
234         return str(text)
236     def evaluateStructure(self, expr):
237         return self.evaluate(expr)
238     evaluateStructure = evaluate
240     def evaluateMacro(self, expr):
241         # XXX Should return None or a macro definition
242         return self.evaluate(expr)
243     evaluateMacro = evaluate
245     def createErrorInfo(self, err, position):
246         return ErrorInfo(err, position)
248     def getDefault(self):
249         return Default
251     def setSourceFile(self, source_file):
252         self.source_file = source_file
254     def setPosition(self, position):
255         self.position = position
259 class TALESTracebackSupplement:
260     """Implementation of ITracebackSupplement"""
261     def __init__(self, context, expression):
262         self.context = context
263         self.source_url = context.source_file
264         self.line = context.position[0]
265         self.column = context.position[1]
266         self.expression = repr(expression)
268     def getInfo(self, as_html=0):
269         import pprint
270         data = self.context.contexts.copy()
271         s = pprint.pformat(data)
272         if not as_html:
273             return '   - Names:\n      %s' % string.replace(s, '\n', '\n      ')
274         else:
275             from cgi import escape
276             return '<b>Names:</b><pre>%s</pre>' % (escape(s))
277         return None
281 class SimpleExpr:
282     '''Simple example of an expression type handler'''
283     def __init__(self, name, expr, engine):
284         self._name = name
285         self._expr = expr
286     def __call__(self, econtext):
287         return self._name, self._expr
288     def __repr__(self):
289         return '<SimpleExpr %s %s>' % (self._name, `self._expr`)