diff options
author | Malthe Borch <mborch@gmail.com> | 2011-08-14 08:00:31 +0000 |
---|---|---|
committer | Malthe Borch <mborch@gmail.com> | 2011-08-14 08:00:31 +0000 |
commit | b951f9ec834ff7f1ee2ccde58f9ee472097d61fb (patch) | |
tree | d4ccd6e172bc2a6907df0cde469310ed75cee961 /src | |
parent | a077116d36992bb867adee165819f82d993bea3e (diff) | |
download | zope-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.py | 61 | ||||
-rw-r--r-- | src/zope/pagetemplate/pagetemplate.py | 91 | ||||
-rw-r--r-- | src/zope/pagetemplate/tests/test_basictemplate.py | 43 |
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 |