diff --git a/CHANGES.txt b/CHANGES.txt
index b3e9a48..37b9172 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,8 +1,10 @@
Changelog
=========
-2.1.6 (unreleased)
-~~~~~~~~~~~~~~~~~~
+2.2 (unreleased)
+~~~~~~~~~~~~~~~~
+- Update implementation to use component-based template engine
+ configuration, plugging directly into the Zope Toolkit framework.
- Declare RepeatItem as public object with allowed subobjects
[leorochael]
diff --git a/setup.py b/setup.py
index e4259fa..b04b668 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = '2.1.5'
+version = '2.2-dev'
setup(name='five.pt',
version=version,
@@ -25,8 +25,9 @@
zip_safe=False,
install_requires=[
'setuptools',
- 'z3c.pt>=2.1.4',
'sourcecodegen>=0.6.14',
+ 'z3c.pt>=2.1.4',
+ 'zope.pagetemplate>=3.6.2',
],
entry_points="""
[z3c.autoinclude.plugin]
diff --git a/src/five/pt/configure.zcml b/src/five/pt/configure.zcml
index f6a6bb0..ff795e9 100644
--- a/src/five/pt/configure.zcml
+++ b/src/five/pt/configure.zcml
@@ -3,6 +3,8 @@
xmlns:five="http://namespaces.zope.org/five">
-
+
+
+
diff --git a/src/five/pt/engine.py b/src/five/pt/engine.py
new file mode 100644
index 0000000..f83e116
--- /dev/null
+++ b/src/five/pt/engine.py
@@ -0,0 +1,108 @@
+"""Patch legacy template classes.
+
+We patch the ``TALInterpreter`` class as well as the cook-method on
+the pagetemplate base class (which produces the input for the TAL
+interpreter).
+"""
+
+import sys
+
+from zope.tal.talinterpreter import TALInterpreter
+from zope.interface import implements
+from zope.interface import classProvides
+
+from zope.pagetemplate.pagetemplate import PageTemplate
+from zope.pagetemplate.interfaces import IPageTemplateEngine
+from zope.pagetemplate.interfaces import IPageTemplateProgram
+
+from z3c.pt.pagetemplate import PageTemplate as ChameleonPageTemplate
+from z3c.pt.pagetemplate import PageTemplateFile as ChameleonPageTemplateFile
+
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from App.class_init import InitializeClass
+from Products.PageTemplates.Expressions import getEngine
+from Products.PageTemplates import ZRPythonExpr
+
+from chameleon.tales import StringExpr
+from chameleon.tales import NotExpr
+from chameleon.tal import RepeatDict
+
+from z3c.pt.expressions import PythonExpr
+
+from .expressions import PathExpr
+from .expressions import TrustedPathExpr
+from .expressions import ProviderExpr
+from .expressions import NocallExpr
+from .expressions import ExistsExpr
+from .expressions import UntrustedPythonExpr
+
+
+# Declare Chameleon's repeat dictionary public
+RepeatDict.security = ClassSecurityInfo()
+RepeatDict.security.declareObjectPublic()
+RepeatDict.__allow_access_to_unprotected_subobjects__ = True
+
+InitializeClass(RepeatDict)
+
+
+class Program(object):
+ implements(IPageTemplateProgram)
+ classProvides(IPageTemplateEngine)
+
+ # Zope 2 Page Template expressions
+ secure_expression_types = {
+ 'python': UntrustedPythonExpr,
+ 'string': StringExpr,
+ 'not': NotExpr,
+ 'exists': ExistsExpr,
+ 'path': PathExpr,
+ 'provider': ProviderExpr,
+ 'nocall': NocallExpr,
+ }
+
+ # Zope 3 Page Template expressions
+ expression_types = {
+ 'python': PythonExpr,
+ 'string': StringExpr,
+ 'not': NotExpr,
+ 'exists': ExistsExpr,
+ 'path': TrustedPathExpr,
+ 'provider': ProviderExpr,
+ 'nocall': NocallExpr,
+ }
+
+ extra_builtins = {
+ 'modules': ZRPythonExpr._SecureModuleImporter()
+ }
+
+ def __init__(self, template):
+ self.template = template
+
+ def __call__(self, context, macros, tal=True, **options):
+ if tal is False:
+ return self.template.body
+
+ # Swap out repeat dictionary for Chameleon implementation
+ # and store wrapped dictionary in new variable -- this is
+ # in turn used by the secure Python expression
+ # implementation whenever a 'repeat' symbol is found
+ kwargs = context.vars
+ kwargs['wrapped_repeat'] = kwargs['repeat']
+ kwargs['repeat'] = RepeatDict(context.repeat_vars)
+
+ return self.template.render(**kwargs)
+
+ @classmethod
+ def cook(cls, source_file, text, engine, content_type):
+ if engine is getEngine():
+ expression_types = cls.secure_expression_types
+ else:
+ expression_types = cls.expression_types
+
+ template = ChameleonPageTemplate(
+ text, filename=source_file, keep_body=True,
+ expression_types=expression_types,
+ encoding='utf-8', extra_builtins=cls.extra_builtins,
+ )
+
+ return cls(template), template.macros
diff --git a/src/five/pt/patches.py b/src/five/pt/patches.py
deleted file mode 100644
index e34ce1e..0000000
--- a/src/five/pt/patches.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""Patch legacy template classes.
-
-We patch the ``TALInterpreter`` class as well as the cook-method on
-the pagetemplate base class (which produces the input for the TAL
-interpreter).
-"""
-
-import sys
-
-from zope.tal.talinterpreter import TALInterpreter
-from zope.pagetemplate.pagetemplate import PageTemplate
-from z3c.pt.pagetemplate import PageTemplate as ChameleonPageTemplate
-
-from AccessControl.SecurityInfo import ClassSecurityInfo
-from App.class_init import InitializeClass
-from Products.PageTemplates.Expressions import getEngine
-from Products.PageTemplates import ZRPythonExpr
-
-from chameleon.tales import StringExpr
-from chameleon.tales import NotExpr
-from chameleon.tal import RepeatDict
-from chameleon.tal import RepeatItem
-
-from z3c.pt.expressions import PythonExpr
-
-from .expressions import PathExpr
-from .expressions import TrustedPathExpr
-from .expressions import ProviderExpr
-from .expressions import NocallExpr
-from .expressions import ExistsExpr
-from .expressions import UntrustedPythonExpr
-
-
-# Declare Chameleon's repeat objects public
-_public_classes = [
- RepeatDict,
- RepeatItem,
-]
-for cls in _public_classes:
- cls.security = ClassSecurityInfo()
- cls.security.declareObjectPublic()
- cls.__allow_access_to_unprotected_subobjects__ = True
- InitializeClass(cls)
-
-# Zope 2 Page Template expressions
-_secure_expression_types = {
- 'python': UntrustedPythonExpr,
- 'string': StringExpr,
- 'not': NotExpr,
- 'exists': ExistsExpr,
- 'path': PathExpr,
- 'provider': ProviderExpr,
- 'nocall': NocallExpr,
- }
-
-
-# Zope 3 Page Template expressions
-_expression_types = {
- 'python': PythonExpr,
- 'string': StringExpr,
- 'not': NotExpr,
- 'exists': ExistsExpr,
- 'path': TrustedPathExpr,
- 'provider': ProviderExpr,
- 'nocall': NocallExpr,
- }
-
-
-def cook(self):
- program = self._v_program
- if program is None:
- engine = self.pt_getEngine()
- source_file = self.pt_source_file()
-
- if engine is getEngine():
- expression_types = _secure_expression_types
- else:
- expression_types = _expression_types
-
- extra_builtins = {
- 'modules': ZRPythonExpr._SecureModuleImporter()
- }
-
- program = ChameleonPageTemplate(
- "", filename=source_file, keep_body=True,
- expression_types=expression_types,
- encoding='utf-8', extra_builtins=extra_builtins,
- )
-
- self._v_program = program
- self._v_macros = program.macros
-
- try:
- program.cook(self._text)
- except:
- etype, e = sys.exc_info()[:2]
- self._v_errors = [
- "Compilation failed",
- "%s.%s: %s" % (etype.__module__, etype.__name__, e)
- ]
- else:
- self._v_errors = ()
-
- self._v_cooked = 1
-
-
-@staticmethod
-def create_interpreter(cls, *args, **kwargs):
- return ChameleonTALInterpreter(*args, **kwargs)
-
-
-class ChameleonTALInterpreter(object):
- def __init__(self, template, macros, context, stream, tal=True, **kwargs):
- self.template = template
- self.context = context.vars
- self.repeat = context.repeat_vars
- self.stream = stream
- self.tal = tal
-
- def __call__(self):
- if self.tal is False:
- result = self.template.body
- else:
- context = self.context
-
- # Swap out repeat dictionary for Chameleon implementation
- # and store wrapped dictionary in new variable -- this is
- # in turn used by the secure Python expression
- # implementation whenever a 'repeat' symbol is found
- context['wrapped_repeat'] = context['repeat']
- context['repeat'] = RepeatDict(self.repeat)
-
- result = self.template.render(**context)
-
- self.stream.write(result)
-
-
-TALInterpreter.__new__ = create_interpreter
-PageTemplate._cook = cook
diff --git a/src/five/pt/tests/test_patches.py b/src/five/pt/tests/test_engine.py
similarity index 95%
rename from src/five/pt/tests/test_patches.py
rename to src/five/pt/tests/test_engine.py
index 8022b0a..8066b05 100644
--- a/src/five/pt/tests/test_patches.py
+++ b/src/five/pt/tests/test_engine.py
@@ -26,8 +26,7 @@ def test_pagetemplate(self):
# test arguments
template.write(open(os.path.join(path, "options.pt")).read())
- self.assertTrue('Hello world' in template(
- greeting='Hello world'))
+ self.assertTrue('Hello world' in template(greeting='Hello world'))
def test_pagetemplatefile(self):
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
diff --git a/src/five/pt/tests/test_persistenttemplate.py b/src/five/pt/tests/test_persistenttemplate.py
index 62aa35a..263dad5 100644
--- a/src/five/pt/tests/test_persistenttemplate.py
+++ b/src/five/pt/tests/test_persistenttemplate.py
@@ -159,23 +159,13 @@ def test_pt_render_with_macro(self):
def test_avoid_recompilation(self):
template = self._makeOne('foo', simple_i18n)
- macro_template = self._makeOne('macro_outer', macro_outer)
- # templates are only compiled after the first call
- self.assertEqual(getattr(template, '_v_template', _marker), _marker)
- template()
- # or the first fetching of macros
- self.assertEqual(getattr(macro_template, '_v_template', _marker),
- _marker)
- macro_template.macros
-
- template_compiled = template._v_program
- macro_template_compiled = macro_template._v_program
-
- # but they should not be recompiled afterwards
- template()
- macro_template.macros
- self.assertTrue(template_compiled is template._v_program)
- self.assertTrue(macro_template_compiled is macro_template._v_program)
+
+ # Template is already cooked
+ program = template._v_program
+ template.pt_render({})
+
+ # The program does not change
+ self.assertEqual(program, template._v_program)
def test_repeat_object_security(self):
template = self._makeOne('foo', repeat_object)