Code

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