• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / zope.pagetemplate / 933136959

pending completion
933136959

push

github

GitHub
Config with pure python (#28)

113 of 125 branches covered (90.4%)

Branch coverage included in aggregate %.

15 of 15 new or added lines in 5 files covered. (100.0%)

921 of 936 relevant lines covered (98.4%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.42
/src/zope/pagetemplate/pagetemplate.py
1
##############################################################################
2
#
3
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
4
# All Rights Reserved.
5
#
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
12
#
13
##############################################################################
14
"""Page Template module
1✔
15

16
HTML- and XML-based template objects using TAL, TALES, and METAL.
17
"""
18
import sys
1✔
19
import six
1✔
20
from zope.tal.talparser import TALParser
1✔
21
from zope.tal.htmltalparser import HTMLTALParser
1✔
22
from zope.tal.talgenerator import TALGenerator
1✔
23
from zope.tal.talinterpreter import TALInterpreter
1✔
24
from zope.tales.engine import Engine
1✔
25
from zope.component import queryUtility
1✔
26

27
from zope.pagetemplate.interfaces import IPageTemplateSubclassing
1✔
28
from zope.pagetemplate.interfaces import IPageTemplateEngine
1✔
29
from zope.pagetemplate.interfaces import IPageTemplateProgram
1✔
30
from zope.interface import implementer
1✔
31
from zope.interface import provider
1✔
32

33
_default_options = {}
1✔
34

35

36
class StringIO(list):
1✔
37
    # Unicode aware append-only version of StringIO.
38
    write = list.append
1✔
39

40
    def __init__(self, value=None):
1✔
41
        list.__init__(self)
1✔
42
        if value is not None:
1!
43
            self.append(value)
1✔
44

45
    def getvalue(self):
1✔
46
        return u''.join(self)
1✔
47

48

49
@implementer(IPageTemplateSubclassing)
1✔
50
class PageTemplate(object):
1✔
51
    """
52
    Page Templates using TAL, TALES, and METAL.
53

54
    **Subclassing**
55

56
    This class implements :class:`~zope.pagetemplate.interfaces.IPageTemplateSubclassing`.
57

58
    The following methods have certain internal responsibilities.
59

60
    ``pt_getContext(**keywords)``
61
        Should ignore keyword arguments that it doesn't care about,
62
        and construct the namespace passed to the TALES expression
63
        engine.  This method is free to use the keyword arguments it
64
        receives.
65

66
    ``pt_render(namespace, source=False, sourceAnnotations=False, showtal=False)``
67
        Responsible the TAL interpreter to perform the rendering.  The
68
        namespace argument is a mapping which defines the top-level
69
        namespaces passed to the TALES expression engine.
70

71
    ``__call__(*args, **keywords)``
72
        Calls pt_getContext() to construct the top-level namespace
73
        passed to the TALES expression engine, then calls pt_render()
74
        to perform the rendering.
75
    """  # noqa: E501 line too long
76

77
    _error_start = '<!-- Page Template Diagnostics'
1✔
78
    _error_end = '-->'
1✔
79
    _newline = '\n'
1✔
80

81
    content_type = 'text/html'
1✔
82
    expand = 1
1✔
83
    _v_errors = ()
1✔
84
    _v_cooked = 0
1✔
85
    _v_macros = None
1✔
86
    _v_program = None
1✔
87
    _text = ''
1✔
88

89
    @property
1✔
90
    def macros(self):
1✔
91
        self._cook_check()
1✔
92
        return self._v_macros
1✔
93

94
    def pt_edit(self, text, content_type):
1✔
95
        if content_type:
1✔
96
            self.content_type = str(content_type)
1✔
97
        if hasattr(text, 'read'):
1✔
98
            text = text.read()
1✔
99
        self.write(text)
1✔
100

101
    def pt_getContext(self, args=(), options=_default_options, **ignored):
1✔
102
        rval = {'template': self,
1✔
103
                'options': options,
104
                'args': args,
105
                'nothing': None,
106
                }
107
        rval.update(self.pt_getEngine().getBaseNames())
1✔
108
        return rval
1✔
109

110
    def __call__(self, *args, **kwargs):
1✔
111
        return self.pt_render(self.pt_getContext(args, kwargs))
1✔
112

113
    def pt_getEngineContext(self, namespace):
1✔
114
        return self.pt_getEngine().getContext(namespace)
1✔
115

116
    def pt_getEngine(self):
1✔
117
        return Engine
1✔
118

119
    def pt_render(self, namespace, source=False, sourceAnnotations=False,
1✔
120
                  showtal=False):
121
        """Render this Page Template"""
122
        self._cook_check()
1✔
123

124
        __traceback_supplement__ = (
1✔
125
            PageTemplateTracebackSupplement, self, namespace
126
        )
127

128
        if self._v_errors:
1✔
129
            raise PTRuntimeError(str(self._v_errors))
1✔
130

131
        context = self.pt_getEngineContext(namespace)
1✔
132

133
        return self._v_program(
1✔
134
            context, self._v_macros, tal=not source, showtal=showtal,
135
            strictinsert=0, sourceAnnotations=sourceAnnotations
136
        )
137

138
    def pt_errors(self, namespace, check_macro_expansion=True):
1✔
139
        self._cook_check()
1✔
140
        err = self._v_errors
1✔
141
        if err:
1✔
142
            return err
1✔
143
        if check_macro_expansion:
1✔
144
            try:
1✔
145
                self.pt_render(namespace, source=1)
1✔
146
            except Exception:
1✔
147
                return (
1✔
148
                    'Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
149

150
    def _convert(self, string, text):
1✔
151
        """Adjust the string type to the type of text"""
152
        if isinstance(
1✔
153
                text,
154
                six.binary_type) and not isinstance(
155
                string,
156
                six.binary_type):
157
            return string.encode('utf-8')
1✔
158

159
        if isinstance(
1!
160
                text,
161
                six.text_type) and not isinstance(
162
                string,
163
                six.text_type):
164
            return string.decode('utf-8')
×
165

166
        return string
1✔
167

168
    def write(self, text):
1✔
169
        # We accept both, since the text can either come from a file (and the
170
        # parser will take care of the encoding) or from a TTW template, in
171
        # which case we already have unicode.
172
        assert isinstance(text, (six.string_types, six.binary_type))
1✔
173

174
        def bs(s):
1✔
175
            """Bytes or str"""
176
            return self._convert(s, text)
1✔
177

178
        if text.startswith(bs(self._error_start)):
1✔
179
            errend = text.find(bs(self._error_end))
1✔
180
            if errend >= 0:
1!
181
                text = text[errend + 3:]
1✔
182
                if text[:1] == bs(self._newline):
1!
183
                    text = text[1:]
1✔
184
        if self._text != text:
1✔
185
            self._text = text
1✔
186

187
        # Always cook on an update, even if the source is the same;
188
        # the content-type might have changed.
189
        self._cook()
1✔
190

191
    def read(self, request=None):
1✔
192
        """Gets the source, sometimes with macros expanded."""
193
        self._cook_check()
1✔
194

195
        def bs(s):
1✔
196
            """Bytes or str"""
197
            return self._convert(s, self._text)
1✔
198
        if not self._v_errors:
1✔
199
            if not self.expand:
1✔
200
                return self._text
1✔
201
            try:
1✔
202
                # This gets called, if macro expansion is turned on.
203
                # Note that an empty dictionary is fine for the context at
204
                # this point, since we are not evaluating the template.
205
                context = self.pt_getContext(self, request)
1✔
206
                return self.pt_render(context, source=1)
1✔
207
            except BaseException:
1✔
208
                return (bs('%s\n Macro expansion failed\n %s\n-->\n' %
1✔
209
                           (self._error_start, "%s: %s" %
210
                            sys.exc_info()[:2])) + self._text)
211

212
        return bs('%s\n %s\n-->\n' % (self._error_start,
1✔
213
                                      '\n'.join(self._v_errors))) + \
214
            self._text
215

216
    def pt_source_file(self):
1✔
217
        """To be overridden."""
218
        return None
1✔
219

220
    def _cook_check(self):
1✔
221
        if not self._v_cooked:
1✔
222
            self._cook()
1✔
223

224
    def _cook(self):
1✔
225
        """Compile the TAL and METAL statments.
226

227
        Cooking must not fail due to compilation errors in templates.
228
        """
229

230
        pt_engine = self.pt_getEngine()
1✔
231
        source_file = self.pt_source_file()
1✔
232

233
        self._v_errors = ()
1✔
234

235
        try:
1✔
236
            engine = queryUtility(
1✔
237
                IPageTemplateEngine, default=PageTemplateEngine
238
            )
239
            self._v_program, self._v_macros = engine.cook(
1✔
240
                source_file, self._text, pt_engine, self.content_type)
241
        except BaseException:
1✔
242
            etype, e = sys.exc_info()[:2]
1✔
243
            try:
1✔
244
                self._v_errors = [
1✔
245
                    "Compilation failed",
246
                    "%s.%s: %s" % (etype.__module__, etype.__name__, e)
247
                ]
248
            finally:
249
                del e
1✔
250

251
        self._v_cooked = 1
1✔
252

253

254
class PTRuntimeError(RuntimeError):
1✔
255
    '''The Page Template has template errors that prevent it from rendering.'''
256
    pass
1✔
257

258

259
@implementer(IPageTemplateProgram)
1✔
260
@provider(IPageTemplateEngine)
1✔
261
class PageTemplateEngine(object):
1✔
262
    """
263
    Page template engine that uses the TAL interpreter to render.
264

265
    This class implements
266
    :class:`zope.pagetemplate.interfaces.IPageTemplateProgram`.
267
    """
268

269
    def __init__(self, program):
1✔
270
        self.program = program
1✔
271

272
    def __call__(self, context, macros, **options):
1✔
273
        output = StringIO(u'')
1✔
274
        interpreter = TALInterpreter(
1✔
275
            self.program, macros, context,
276
            stream=output, **options
277
        )
278
        interpreter()
1✔
279
        return output.getvalue()
1✔
280

281
    @classmethod
1✔
282
    def cook(cls, source_file, text, engine, content_type):
1✔
283
        if content_type == 'text/html':
1✔
284
            gen = TALGenerator(engine, xml=0, source_file=source_file)
1✔
285
            parser = HTMLTALParser(gen)
1✔
286
        else:
287
            gen = TALGenerator(engine, source_file=source_file)
1✔
288
            parser = TALParser(gen)
1✔
289

290
        parser.parseString(text)
1✔
291
        program, macros = parser.getCode()
1✔
292

293
        return cls(program), macros
1✔
294

295

296
# @implementer(ITracebackSupplement)
297
class PageTemplateTracebackSupplement(object):
1✔
298

299
    def __init__(self, pt, namespace):
1✔
300
        self.manageable_object = pt
1✔
301
        self.warnings = []
1✔
302
        try:
1✔
303
            e = pt.pt_errors(namespace, check_macro_expansion=False)
1✔
304
        except TypeError:
1✔
305
            # Old page template.
306
            e = pt.pt_errors(namespace)
1✔
307
        if e:
1✔
308
            self.warnings.extend(e)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc