Code

- replaced the content() callback ickiness with Page Template macro usage
[roundup.git] / roundup / cgi / PageTemplates / PageTemplate.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 """Page Template module
15 HTML- and XML-based template objects using TAL, TALES, and METAL.
18 Modified for Roundup 0.5 release:
20 - changed imports to import from roundup.cgi
22 """
24 __version__='$Revision: 1.3 $'[11:-2]
26 import sys
28 from roundup.cgi.TAL.TALParser import TALParser
29 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
30 from roundup.cgi.TAL.TALGenerator import TALGenerator
31 from roundup.cgi.TAL.TALInterpreter import TALInterpreter
32 from Expressions import getEngine
33 from string import join, strip, rstrip, split, replace, lower, find
34 from cStringIO import StringIO
36 class PageTemplate:
37     "Page Templates using TAL, TALES, and METAL"
38      
39     content_type = 'text/html'
40     expand = 0
41     _v_errors = ()
42     _v_warnings = ()
43     _v_program = None
44     _v_macros = None
45     _v_cooked = 0
46     id = '(unknown)'
47     _text = ''
48     _error_start = '<!-- Page Template Diagnostics'
50     def pt_edit(self, text, content_type):
51         if content_type:
52             self.content_type = str(content_type)
53         if hasattr(text, 'read'):
54             text = text.read()
55         self.write(text)
57     def pt_getContext(self):
58         c = {'template': self,
59              'options': {},
60              'nothing': None,
61              'request': None,
62              'modules': ModuleImporter,
63              }
64         parent = getattr(self, 'aq_parent', None)
65         if parent is not None:
66             c['here'] = parent
67             c['container'] = self.aq_inner.aq_parent
68             while parent is not None:
69                 self = parent
70                 parent = getattr(self, 'aq_parent', None)
71             c['root'] = self
72         return c
73     
74     def pt_render(self, source=0, extra_context={}):
75         """Render this Page Template"""
76         if not self._v_cooked:
77             self._cook()
79         __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
81         if self._v_errors:
82             raise PTRuntimeError, 'Page Template %s has errors.' % self.id
83         output = StringIO()
84         c = self.pt_getContext()
85         c.update(extra_context)
87         TALInterpreter(self._v_program, self._v_macros,
88                        getEngine().getContext(c),
89                        output,
90                        tal=not source, strictinsert=0)()
91         return output.getvalue()
93     def __call__(self, *args, **kwargs):
94         if not kwargs.has_key('args'):
95             kwargs['args'] = args
96         return self.pt_render(extra_context={'options': kwargs})
98     def pt_errors(self):
99         if not self._v_cooked:
100             self._cook()
101         err = self._v_errors
102         if err:
103             return err
104         if not self.expand: return
105         try:
106             self.pt_render(source=1)
107         except:
108             return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
109         
110     def pt_warnings(self):
111         if not self._v_cooked:
112             self._cook()
113         return self._v_warnings
115     def pt_macros(self):
116         if not self._v_cooked:
117             self._cook()
118         __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
119         if self._v_errors:
120             raise PTRuntimeError, 'Page Template %s has errors.' % self.id
121         return self._v_macros
123     def __getattr__(self, name):
124         if name == 'macros':
125             return self.pt_macros()
126         raise AttributeError, name
128     def pt_source_file(self):
129         return None  # Unknown.
131     def write(self, text):
132         assert type(text) is type('')
133         if text[:len(self._error_start)] == self._error_start:
134             errend = find(text, '-->')
135             if errend >= 0:
136                 text = text[errend + 4:]
137         if self._text != text:
138             self._text = text
139         self._cook()
141     def read(self):
142         if not self._v_cooked:
143             self._cook()
144         if not self._v_errors:
145             if not self.expand:
146                 return self._text
147             try:
148                 return self.pt_render(source=1)
149             except:
150                 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
151                         (self._error_start, "%s: %s" % sys.exc_info()[:2],
152                          self._text) )
153                                   
154         return ('%s\n %s\n-->\n%s' % (self._error_start,
155                                       join(self._v_errors, '\n '),
156                                       self._text))
158     def _cook(self):
159         """Compile the TAL and METAL statments.
161         Cooking must not fail due to compilation errors in templates.
162         """
163         source_file = self.pt_source_file()
164         if self.html():
165             gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
166             parser = HTMLTALParser(gen)
167         else:
168             gen = TALGenerator(getEngine(), source_file=source_file)
169             parser = TALParser(gen)
171         self._v_errors = ()
172         try:
173             parser.parseString(self._text)
174             self._v_program, self._v_macros = parser.getCode()
175         except:
176             self._v_errors = ["Compilation failed",
177                               "%s: %s" % sys.exc_info()[:2]]
178         self._v_warnings = parser.getWarnings()
179         self._v_cooked = 1
181     def html(self):
182         if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
183             return self.content_type == 'text/html'
184         return self.is_html
186 class _ModuleImporter:
187     def __getitem__(self, module):
188         mod = __import__(module)
189         path = split(module, '.')
190         for name in path[1:]:
191             mod = getattr(mod, name)
192         return mod
194 ModuleImporter = _ModuleImporter()
196 class PTRuntimeError(RuntimeError):
197     '''The Page Template has template errors that prevent it from rendering.'''
198     pass
201 class PageTemplateTracebackSupplement:
202     #__implements__ = ITracebackSupplement
204     def __init__(self, pt):
205         self.object = pt
206         w = pt.pt_warnings()
207         e = pt.pt_errors()
208         if e:
209             w = list(w) + list(e)
210         self.warnings = w