[Checkins] SVN: zope.pagetemplate/trunk/ Abstract out the template engine and program interfaces and allow implementation replacement via a utility component registration.

Malthe Borch mborch at gmail.com
Sun Aug 14 04:00:32 EDT 2011


Log message for revision 122584:
  Abstract out the template engine and program interfaces and allow implementation replacement via a utility component registration.

Changed:
  U   zope.pagetemplate/trunk/CHANGES.txt
  U   zope.pagetemplate/trunk/src/zope/pagetemplate/interfaces.py
  U   zope.pagetemplate/trunk/src/zope/pagetemplate/pagetemplate.py
  U   zope.pagetemplate/trunk/src/zope/pagetemplate/tests/test_basictemplate.py

-=-
Modified: zope.pagetemplate/trunk/CHANGES.txt
===================================================================
--- zope.pagetemplate/trunk/CHANGES.txt	2011-08-13 20:05:30 UTC (rev 122583)
+++ zope.pagetemplate/trunk/CHANGES.txt	2011-08-14 08:00:31 UTC (rev 122584)
@@ -2,6 +2,11 @@
 CHANGES
 =======
 
+3.6.0 (unreleased)
+
+- Abstract out the template engine and program interfaces and allow
+  implementation replacement via a utility component registration.
+
 3.5.3 (unreleased)
 ------------------
 

Modified: zope.pagetemplate/trunk/src/zope/pagetemplate/interfaces.py
===================================================================
--- zope.pagetemplate/trunk/src/zope/pagetemplate/interfaces.py	2011-08-13 20:05:30 UTC (rev 122583)
+++ zope.pagetemplate/trunk/src/zope/pagetemplate/interfaces.py	2011-08-14 08:00:31 UTC (rev 122584)
@@ -98,8 +98,67 @@
         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).
+        """

Modified: zope.pagetemplate/trunk/src/zope/pagetemplate/pagetemplate.py
===================================================================
--- zope.pagetemplate/trunk/src/zope/pagetemplate/pagetemplate.py	2011-08-13 20:05:30 UTC (rev 122583)
+++ zope.pagetemplate/trunk/src/zope/pagetemplate/pagetemplate.py	2011-08-14 08:00:31 UTC (rev 122584)
@@ -21,16 +21,20 @@
 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 @@
     content_type = 'text/html'
     expand = 1
     _v_errors = ()
+    _v_cooked = 0
     _v_program = None
-    _v_macros = None
-    _v_cooked = 0
     _text = ''
 
     def macros(self):
         self._cook_check()
-        return self._v_macros
+        return self._v_program.macros
 
     macros = property(macros)
 
@@ -101,18 +104,21 @@
                   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()
         err = self._v_errors
@@ -151,8 +157,9 @@
             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 @@
 
         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 @@
     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)
 

Modified: zope.pagetemplate/trunk/src/zope/pagetemplate/tests/test_basictemplate.py
===================================================================
--- zope.pagetemplate/trunk/src/zope/pagetemplate/tests/test_basictemplate.py	2011-08-13 20:05:30 UTC (rev 122583)
+++ zope.pagetemplate/trunk/src/zope/pagetemplate/tests/test_basictemplate.py	2011-08-14 08:00:31 UTC (rev 122584)
@@ -17,13 +17,17 @@
 
 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 @@
         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



More information about the checkins mailing list