summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMalthe Borch <mborch@gmail.com>2011-08-14 08:00:31 +0000
committerMalthe Borch <mborch@gmail.com>2011-08-14 08:00:31 +0000
commitb951f9ec834ff7f1ee2ccde58f9ee472097d61fb (patch)
treed4ccd6e172bc2a6907df0cde469310ed75cee961 /src
parenta077116d36992bb867adee165819f82d993bea3e (diff)
downloadzope-pagetemplate-b951f9ec834ff7f1ee2ccde58f9ee472097d61fb.tar.gz
Abstract out the template engine and program interfaces and allow implementation replacement via a utility component registration.
Diffstat (limited to 'src')
-rw-r--r--src/zope/pagetemplate/interfaces.py61
-rw-r--r--src/zope/pagetemplate/pagetemplate.py91
-rw-r--r--src/zope/pagetemplate/tests/test_basictemplate.py43
3 files changed, 169 insertions, 26 deletions
diff --git a/src/zope/pagetemplate/interfaces.py b/src/zope/pagetemplate/interfaces.py
index c627fad..19cbaf7 100644
--- a/src/zope/pagetemplate/interfaces.py
+++ b/src/zope/pagetemplate/interfaces.py
@@ -98,8 +98,67 @@ class IPageTemplateSubclassing(IPageTemplate):
Subclasses might override this to influence the decision about
whether compilation is necessary.
"""
-
+
content_type = Attribute("The content-type of the generated output")
expand = Attribute(
"Flag indicating whether the read method should expand macros")
+
+
+class IPageTemplateEngine(Interface):
+ """Template engine implementation.
+
+ The engine must provide a ``cook`` method to return a cooked
+ template program from a source input.
+ """
+
+ def cook(source_file, text, engine, content_type):
+ """Parse text and return prepared template program.
+
+ Note that while ``source_file`` is provided to name the source
+ of the input ``text``, it should not be relied on to be an
+ actual filename (it may be an application-specific, virtual
+ path).
+ """
+
+
+class IPageTemplateProgram(Interface):
+ """Cooked template program."""
+
+ macros = Attribute(
+ "Template macros.")
+
+ def __call__(
+ context, debug=0, wrap=60, metal=1, tal=1, showtal=-1,
+ strictinsert=1, stackLimit=100, i18nInterpolate=1,
+ sourceAnnotations=0):
+ """Render template in the provided template ``context``.
+
+ Optional arguments:
+
+ debug -- enable debugging output to sys.stderr (off by default).
+
+ wrap -- try to wrap attributes on opening tags to this number of
+ column (default: 60).
+
+ metal -- enable METAL macro processing (on by default).
+
+ tal -- enable TAL processing (on by default).
+
+ showtal -- do not strip away TAL directives. A special value of
+ -1 (which is the default setting) enables showtal when TAL
+ processing is disabled, and disables showtal when TAL processing is
+ enabled. Note that you must use 0, 1, or -1; true boolean values
+ are not supported (for historical reasons).
+
+ strictinsert -- enable TAL processing and stricter HTML/XML
+ checking on text produced by structure inserts (on by default).
+ Note that Zope turns this value off by default.
+
+ stackLimit -- set macro nesting limit (default: 100).
+
+ i18nInterpolate -- enable i18n translations (default: on).
+
+ sourceAnnotations -- enable source annotations with HTML comments
+ (default: off).
+ """
diff --git a/src/zope/pagetemplate/pagetemplate.py b/src/zope/pagetemplate/pagetemplate.py
index f9ed040..0b3bfcc 100644
--- a/src/zope/pagetemplate/pagetemplate.py
+++ b/src/zope/pagetemplate/pagetemplate.py
@@ -21,16 +21,20 @@ from zope.tal.htmltalparser import HTMLTALParser
from zope.tal.talgenerator import TALGenerator
from zope.tal.talinterpreter import TALInterpreter
from zope.tales.engine import Engine
+from zope.component import queryUtility
# Don't use cStringIO here! It's not unicode aware.
from StringIO import StringIO
from zope.pagetemplate.interfaces import IPageTemplateSubclassing
+from zope.pagetemplate.interfaces import IPageTemplateEngine
+from zope.pagetemplate.interfaces import IPageTemplateProgram
from zope.interface import implements
-
+from zope.interface import classProvides
_default_options = {}
_error_start = '<!-- Page Template Diagnostics'
+
class PageTemplate(object):
"""Page Templates using TAL, TALES, and METAL.
@@ -61,14 +65,13 @@ class PageTemplate(object):
content_type = 'text/html'
expand = 1
_v_errors = ()
- _v_program = None
- _v_macros = None
_v_cooked = 0
+ _v_program = None
_text = ''
def macros(self):
self._cook_check()
- return self._v_macros
+ return self._v_program.macros
macros = property(macros)
@@ -101,17 +104,20 @@ class PageTemplate(object):
showtal=False):
"""Render this Page Template"""
self._cook_check()
- __traceback_supplement__ = (PageTemplateTracebackSupplement,
- self, namespace)
+
+ __traceback_supplement__ = (
+ PageTemplateTracebackSupplement, self, namespace
+ )
+
if self._v_errors:
raise PTRuntimeError(str(self._v_errors))
- output = StringIO(u'')
context = self.pt_getEngineContext(namespace)
- TALInterpreter(self._v_program, self._v_macros,
- context, output, tal=not source, showtal=showtal,
- strictinsert=0, sourceAnnotations=sourceAnnotations)()
- return output.getvalue()
+
+ return self._v_program(
+ context, tal=not source, showtal=showtal,
+ sourceAnnotations=sourceAnnotations
+ )
def pt_errors(self, namespace):
self._cook_check()
@@ -151,8 +157,9 @@ class PageTemplate(object):
try:
# This gets called, if macro expansion is turned on.
# Note that an empty dictionary is fine for the context at
- # this point, since we are not evaluating the template.
- return self.pt_render(self.pt_getContext(self, request), source=1)
+ # this point, since we are not evaluating the template.
+ context = self.pt_getContext(self, request)
+ return self.pt_render(context, source=1)
except:
return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
(_error_start, "%s: %s" % sys.exc_info()[:2],
@@ -175,23 +182,25 @@ class PageTemplate(object):
Cooking must not fail due to compilation errors in templates.
"""
- engine = self.pt_getEngine()
+
+ pt_engine = self.pt_getEngine()
source_file = self.pt_source_file()
- if self.content_type == 'text/html':
- gen = TALGenerator(engine, xml=0, source_file=source_file)
- parser = HTMLTALParser(gen)
- else:
- gen = TALGenerator(engine, source_file=source_file)
- parser = TALParser(gen)
self._v_errors = ()
+
try:
- parser.parseString(self._text)
- self._v_program, self._v_macros = parser.getCode()
+ engine = queryUtility(
+ IPageTemplateEngine, default=PageTemplateEngine
+ )
+ self._v_program = engine.cook(
+ source_file, self._text, pt_engine, self.content_type)
except:
etype, e = sys.exc_info()[:2]
- self._v_errors = ["Compilation failed",
- "%s.%s: %s" % (etype.__module__, etype.__name__, e)]
+ self._v_errors = [
+ "Compilation failed",
+ "%s.%s: %s" % (etype.__module__, etype.__name__, e)
+ ]
+
self._v_cooked = 1
@@ -200,6 +209,40 @@ class PTRuntimeError(RuntimeError):
pass
+class PageTemplateEngine(object):
+ """Page template engine that uses the TAL interpreter to render."""
+
+ implements(IPageTemplateProgram)
+ classProvides(IPageTemplateEngine)
+
+ def __init__(self, program, macros):
+ self.macros = macros
+ self._program = program
+
+ def __call__(self, context, **options):
+ output = StringIO(u'')
+ interpreter = TALInterpreter(
+ self._program, self.macros, context,
+ stream=output, **options
+ )
+ interpreter()
+ return output.getvalue()
+
+ @classmethod
+ def cook(cls, source_file, text, engine, content_type):
+ if content_type == 'text/html':
+ gen = TALGenerator(engine, xml=0, source_file=source_file)
+ parser = HTMLTALParser(gen)
+ else:
+ gen = TALGenerator(engine, source_file=source_file)
+ parser = TALParser(gen)
+
+ parser.parseString(text)
+ program, macros = parser.getCode()
+
+ return cls(program, macros)
+
+
class PageTemplateTracebackSupplement(object):
#implements(ITracebackSupplement)
diff --git a/src/zope/pagetemplate/tests/test_basictemplate.py b/src/zope/pagetemplate/tests/test_basictemplate.py
index d77f665..87a697f 100644
--- a/src/zope/pagetemplate/tests/test_basictemplate.py
+++ b/src/zope/pagetemplate/tests/test_basictemplate.py
@@ -17,13 +17,17 @@ import unittest
from zope.pagetemplate.tests import util
import zope.pagetemplate.pagetemplate
-
+import zope.component.testing
class BasicTemplateTests(unittest.TestCase):
def setUp(self):
+ zope.component.testing.setUp(self)
self.t = zope.pagetemplate.pagetemplate.PageTemplate()
+ def tearDown(self):
+ zope.component.testing.tearDown(self)
+
def test_if_in_var(self):
# DTML test 1: if, in, and var:
pass # for unittest
@@ -73,6 +77,43 @@ class BasicTemplateTests(unittest.TestCase):
else:
self.fail("expected PTRuntimeError")
+ def test_engine_utility_registration(self):
+ self.t.write("foo")
+ output = self.t.pt_render({})
+ self.assertEqual(output, 'foo')
+
+ from zope.pagetemplate.interfaces import IPageTemplateEngine
+ from zope.component import provideUtility
+
+ class DummyProgram(object):
+ def __init__(*args):
+ self.args = args
+
+ def __call__(*args, **kwargs):
+ return self.args, args, kwargs
+
+ class DummyEngine(object):
+ cook = DummyProgram
+
+ provideUtility(DummyEngine, IPageTemplateEngine)
+ self.t._cook()
+
+ # "Render" and unpack arguments passed for verification
+ ((cls, source_file, text, engine, content_type),
+ (program, context),
+ options) = \
+ self.t.pt_render({})
+
+ self.assertEqual(source_file, None)
+ self.assertEqual(text, 'foo')
+ self.assertEqual(content_type, 'text/html')
+ self.assertTrue(isinstance(program, DummyProgram))
+ self.assertEqual(options, {
+ 'tal': True,
+ 'showtal': False,
+ 'sourceAnnotations': False
+ })
+
def test_batches_and_formatting(self):
# DTML test 3: batches and formatting:
pass # for unittest