Code

Add config-option "nosy" to messages_to_author setting in [nosy] section
[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 # Modified for Roundup:
14
15 # 1. changed imports to import from roundup.cgi
16 # 2. implemented ustr as str (removes import from DocumentTemplate)
17 # 3. removed import and use of Unauthorized from zExceptions
18 """TALES
20 An implementation of a generic TALES engine
21 """
23 __version__='$Revision: 1.9 $'[11:-2]
25 import re, sys
26 from roundup.cgi import ZTUtils
27 from weakref import ref
28 from MultiMapping import MultiMapping
29 from GlobalTranslationService import getGlobalTranslationService
31 ustr = str
33 StringType = type('')
35 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
36 _parse_expr = re.compile(r"(%s):" % NAME_RE).match
37 _valid_name = re.compile('%s$' % NAME_RE).match
39 class TALESError(Exception):
40     """Error during TALES expression evaluation"""
42 class Undefined(TALESError):
43     '''Exception raised on traversal of an undefined path'''
45 class RegistrationError(Exception):
46     '''TALES Type Registration Error'''
48 class CompilerError(Exception):
49     '''TALES Compiler Error'''
51 class Default:
52     '''Retain Default'''
53 Default = Default()
55 class SafeMapping(MultiMapping):
56     '''Mapping with security declarations and limited method exposure.
58     Since it subclasses MultiMapping, this class can be used to wrap
59     one or more mapping objects.  Restricted Python code will not be
60     able to mutate the SafeMapping or the wrapped mappings, but will be
61     able to read any value.
62     '''
63     __allow_access_to_unprotected_subobjects__ = 1
64     push = pop = None
66     _push = MultiMapping.push
67     _pop = MultiMapping.pop
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_ref = ref(context)
76     def next(self):
77         if ZTUtils.Iterator.next(self):
78             context = self._context_ref()
79             if context is not None:
80                 context.setLocal(self.name, self.item)
81             return 1
82         return 0
85 class ErrorInfo:
86     """Information about an exception passed to an on-error handler."""
87     __allow_access_to_unprotected_subobjects__ = 1
89     def __init__(self, err, position=(None, None)):
90         if isinstance(err, Exception):
91             self.type = err.__class__
92             self.value = err
93         else:
94             self.type = err
95             self.value = None
96         self.lineno = position[0]
97         self.offset = position[1]
100 class Engine:
101     '''Expression Engine
103     An instance of this class keeps a mutable collection of expression
104     type handlers.  It can compile expression strings by delegating to
105     these handlers.  It can provide an expression Context, which is
106     capable of holding state and evaluating compiled expressions.
107     '''
108     Iterator = Iterator
110     def __init__(self, Iterator=None):
111         self.types = {}
112         if Iterator is not None:
113             self.Iterator = Iterator
115     def registerType(self, name, handler):
116         if not _valid_name(name):
117             raise RegistrationError, 'Invalid Expression type "%s".' % name
118         types = self.types
119         if types.has_key(name):
120             raise RegistrationError, (
121                 'Multiple registrations for Expression type "%s".' %
122                 name)
123         types[name] = handler
125     def getTypes(self):
126         return self.types
128     def compile(self, expression):
129         m = _parse_expr(expression)
130         if m:
131             type = m.group(1)
132             expr = expression[m.end():]
133         else:
134             type = "standard"
135             expr = expression
136         try:
137             handler = self.types[type]
138         except KeyError:
139             raise CompilerError, (
140                 'Unrecognized expression type "%s".' % type)
141         return handler(type, expr, self)
143     def getContext(self, contexts=None, **kwcontexts):
144         if contexts is not None:
145             if kwcontexts:
146                 kwcontexts.update(contexts)
147             else:
148                 kwcontexts = contexts
149         return Context(self, kwcontexts)
151     def getCompilerError(self):
152         return CompilerError
154 class Context:
155     '''Expression Context
157     An instance of this class holds context information that it can
158     use to evaluate compiled expressions.
159     '''
161     _context_class = SafeMapping
162     position = (None, None)
163     source_file = None
165     def __init__(self, compiler, contexts):
166         self._compiler = compiler
167         self.contexts = contexts
168         contexts['nothing'] = None
169         contexts['default'] = Default
171         self.repeat_vars = rv = {}
172         # Wrap this, as it is visible to restricted code
173         contexts['repeat'] = rep =  self._context_class(rv)
174         contexts['loop'] = rep # alias
176         self.global_vars = gv = contexts.copy()
177         self.local_vars = lv = {}
178         self.vars = self._context_class(gv, lv)
180         # Keep track of what needs to be popped as each scope ends.
181         self._scope_stack = []
183     def getCompiler(self):
184         return self._compiler
186     def beginScope(self):
187         self._scope_stack.append([self.local_vars.copy()])
189     def endScope(self):
190         scope = self._scope_stack.pop()
191         self.local_vars = lv = scope[0]
192         v = self.vars
193         v._pop()
194         v._push(lv)
195         # Pop repeat variables, if any
196         i = len(scope) - 1
197         while i:
198             name, value = scope[i]
199             if value is None:
200                 del self.repeat_vars[name]
201             else:
202                 self.repeat_vars[name] = value
203             i = i - 1
205     def setLocal(self, name, value):
206         self.local_vars[name] = value
208     def setGlobal(self, name, value):
209         self.global_vars[name] = value
211     def setRepeat(self, name, expr):
212         expr = self.evaluate(expr)
213         if not expr:
214             return self._compiler.Iterator(name, (), self)
215         it = self._compiler.Iterator(name, expr, self)
216         old_value = self.repeat_vars.get(name)
217         self._scope_stack[-1].append((name, old_value))
218         self.repeat_vars[name] = it
219         return it
221     def evaluate(self, expression,
222                  isinstance=isinstance, StringType=StringType):
223         if isinstance(expression, StringType):
224             expression = self._compiler.compile(expression)
225         __traceback_supplement__ = (
226             TALESTracebackSupplement, self, expression)
227         return expression(self)
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         if isinstance(text, unicode):
237             return text
238         else:
239             return ustr(text)
241     def evaluateStructure(self, expr):
242         return self.evaluate(expr)
243     evaluateStructure = evaluate
245     def evaluateMacro(self, expr):
246         # XXX Should return None or a macro definition
247         return self.evaluate(expr)
248     evaluateMacro = evaluate
250     def createErrorInfo(self, err, position):
251         return ErrorInfo(err, position)
253     def getDefault(self):
254         return Default
256     def setSourceFile(self, source_file):
257         self.source_file = source_file
259     def setPosition(self, position):
260         self.position = position
262     def translate(self, domain, msgid, mapping=None,
263                   context=None, target_language=None, default=None):
264         if context is None:
265             context = self.contexts.get('here')
266         return getGlobalTranslationService().translate(
267             domain, msgid, mapping=mapping,
268             context=context,
269             default=default,
270             target_language=target_language)
272 class TALESTracebackSupplement:
273     """Implementation of ITracebackSupplement"""
274     def __init__(self, context, expression):
275         self.context = context
276         self.source_url = context.source_file
277         self.line = context.position[0]
278         self.column = context.position[1]
279         self.expression = repr(expression)
281     def getInfo(self, as_html=0):
282         import pprint
283         data = self.context.contexts.copy()
284         s = pprint.pformat(data)
285         if not as_html:
286             return '   - Names:\n      %s' % s.replace('\n', '\n      ')
287         else:
288             from cgi import escape
289             return '<b>Names:</b><pre>%s</pre>' % (escape(s))
292 class SimpleExpr:
293     '''Simple example of an expression type handler'''
294     def __init__(self, name, expr, engine):
295         self._name = name
296         self._expr = expr
297     def __call__(self, econtext):
298         return self._name, self._expr
299     def __repr__(self):
300         return '<SimpleExpr %s %s>' % (self._name, `self._expr`)