summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt6
-rw-r--r--docs/api/template.rst22
-rw-r--r--docs/conf.py4
-rw-r--r--docs/narr/templates.rst10
-rw-r--r--repoze/bfg/chameleon_text.py85
-rw-r--r--repoze/bfg/tests/fixtures/minimal.txt1
-rw-r--r--repoze/bfg/tests/fixtures/nonminimal.txt1
-rw-r--r--repoze/bfg/tests/test_chameleon_text.py226
8 files changed, 347 insertions, 8 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 705c2a65b..33c924c2a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,8 @@
-Next release
+0.4.9 (11/17/2008)
+
+ - Add chameleon text template API (chameleon ${name} renderings
+ where the template does not need to be wrapped in any containing
+ XML).
- Change docs to explain install in terms of a virtualenv
(unconditionally).
diff --git a/docs/api/template.rst b/docs/api/template.rst
index 8cf63c91b..8685073c3 100644
--- a/docs/api/template.rst
+++ b/docs/api/template.rst
@@ -3,12 +3,12 @@
:mod:`repoze.bfg` Built-in Templating Facilties
===============================================
-Three templating facilities are provided by :mod:`repoze.bfg` "out of
-the box": :term:`ZPT` -style, :term:`Genshi` -style, and :term:`XSLT`
-templating.
+Four templating facilities are provided by :mod:`repoze.bfg` "out of
+the box": :term:`ZPT` -style, :term:`Genshi` -style, text templating,
+and :term:`XSLT` templating.
-ZPT-style and Genshi-style templates are in :mod:`repoze.bfg` are
-supported by the :term:`Chameleon` (nee :term:`z3c.pt`) templating
+ZPT-style, Genshi-style, and text templates are in :mod:`repoze.bfg`
+are supported by the :term:`Chameleon` (nee :term:`z3c.pt`) templating
engine, which contains alternate implementations of both the ZPT and
Genshi language specifications.
@@ -32,6 +32,7 @@ statement, e.g.:
from repoze.chameleon_zpt import render_template as zpt_render
from repoze.chameleon_genshi import render_template as genshi_render
+ from repoze.chameleon_text import render_template as text_render
:mod:`repoze.bfg.chameleon_zpt`
-------------------------------
@@ -58,6 +59,17 @@ statement, e.g.:
.. autofunction:: render_template_to_response
+:mod:`repoze.bfg.chameleon_text`
+----------------------------------
+
+.. automodule:: repoze.bfg.chameleon_text
+
+ .. autofunction:: get_template
+
+ .. autofunction:: render_template
+
+ .. autofunction:: render_template_to_response
+
:mod:`repoze.bfg.xslt`
----------------------
diff --git a/docs/conf.py b/docs/conf.py
index d494ce20a..7d3991698 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = '2008, Agendaless Consulting'
# other places throughout the built documents.
#
# The short X.Y version.
-version = '0.4.8'
+version = '0.4.9'
# The full version, including alpha/beta/rc tags.
-release = '0.4.8'
+release = '0.4.9'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 4878e728c..48a9ac52e 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -136,6 +136,16 @@ application's configuration section, e.g.::
use = egg:MyProject#app
reload_templates = true
+Using Text Templates
+--------------------
+
+:mod:`repoze.bfg` also allows for the use of templates which are
+composed entirely of non-XML text via :term:`Chameleon`. To do so,
+you can create templates that are entirely composed of text except for
+``${name}`` -style substitution points. The rendering API is a mirror
+of the ZPT rendering facility, it's just imported from another place;
+see :ref:`template_module` for more information.
+
Templating with other Templating Languages
------------------------------------------
diff --git a/repoze/bfg/chameleon_text.py b/repoze/bfg/chameleon_text.py
new file mode 100644
index 000000000..e00106e5c
--- /dev/null
+++ b/repoze/bfg/chameleon_text.py
@@ -0,0 +1,85 @@
+from webob import Response
+
+from zope.component import queryUtility
+
+from zope.interface import classProvides
+from zope.interface import implements
+
+from repoze.bfg.interfaces import ITemplateRenderer
+from repoze.bfg.interfaces import ITemplateRendererFactory
+from repoze.bfg.interfaces import ISettings
+
+from repoze.bfg.templating import renderer_from_cache
+
+from chameleon.core.template import TemplateFile
+from chameleon.zpt.language import Parser
+
+class TextTemplateFile(TemplateFile):
+ default_parser = Parser()
+
+ def __init__(self, filename, parser=None, format=None,
+ doctype=None, **kwargs):
+ if parser is None:
+ parser = self.default_parser
+ super(TextTemplateFile, self).__init__(filename, parser, format,
+ doctype, **kwargs)
+
+class TextTemplateRenderer(object):
+ classProvides(ITemplateRendererFactory)
+ implements(ITemplateRenderer)
+
+ def __init__(self, path, auto_reload=False):
+ self.template = TextTemplateFile(
+ path,
+ format='text',
+ auto_reload=auto_reload
+ )
+
+ def implementation(self):
+ return self.template
+
+ def __call__(self, **kw):
+ return self.template(**kw)
+
+def _auto_reload():
+ settings = queryUtility(ISettings)
+ auto_reload = settings and settings.reload_templates
+ return auto_reload
+
+def get_renderer(path):
+ """ Return a callable ``ITemplateRenderer`` object representing a
+ ``chameleon`` text template at the package-relative path (may also
+ be absolute)."""
+ auto_reload = _auto_reload()
+ renderer = renderer_from_cache(path, TextTemplateRenderer,
+ auto_reload=auto_reload)
+ return renderer
+
+def get_template(path):
+ """ Return a ``chameleon`` text template at the package-relative
+ path (may also be absolute)."""
+ auto_reload = _auto_reload()
+ renderer = renderer_from_cache(path, TextTemplateRenderer,
+ auto_reload=auto_reload)
+ return renderer.implementation()
+
+def render_template(path, **kw):
+ """ Render a ``chameleon`` text template at the package-relative
+ path (may also be absolute) using the kwargs in ``*kw`` as
+ top-level names and return a string."""
+ auto_reload = _auto_reload()
+ renderer = renderer_from_cache(path, TextTemplateRenderer,
+ auto_reload=auto_reload)
+ return renderer(**kw)
+
+def render_template_to_response(path, **kw):
+ """ Render a ``chameleon`` text template at the package-relative
+ path (may also be absolute) using the kwargs in ``*kw`` as
+ top-level names and return a Response object with the body as the
+ template result."""
+ auto_reload = _auto_reload()
+ renderer = renderer_from_cache(path, TextTemplateRenderer,
+ auto_reload=auto_reload)
+ result = renderer(**kw)
+ return Response(result)
+
diff --git a/repoze/bfg/tests/fixtures/minimal.txt b/repoze/bfg/tests/fixtures/minimal.txt
new file mode 100644
index 000000000..18832d351
--- /dev/null
+++ b/repoze/bfg/tests/fixtures/minimal.txt
@@ -0,0 +1 @@
+Hello.
diff --git a/repoze/bfg/tests/fixtures/nonminimal.txt b/repoze/bfg/tests/fixtures/nonminimal.txt
new file mode 100644
index 000000000..9de95ec92
--- /dev/null
+++ b/repoze/bfg/tests/fixtures/nonminimal.txt
@@ -0,0 +1 @@
+Hello, ${name}!
diff --git a/repoze/bfg/tests/test_chameleon_text.py b/repoze/bfg/tests/test_chameleon_text.py
new file mode 100644
index 000000000..8e53b7ff9
--- /dev/null
+++ b/repoze/bfg/tests/test_chameleon_text.py
@@ -0,0 +1,226 @@
+import unittest
+
+from zope.testing.cleanup import cleanUp
+
+class Base:
+ def setUp(self):
+ cleanUp()
+
+ def tearDown(self):
+ cleanUp()
+
+ def _zcmlConfigure(self):
+ import repoze.bfg
+ import zope.configuration.xmlconfig
+ zope.configuration.xmlconfig.file('configure.zcml', package=repoze.bfg)
+
+ def _getTemplatePath(self, name):
+ import os
+ here = os.path.abspath(os.path.dirname(__file__))
+ return os.path.join(here, 'fixtures', name)
+
+class TextTemplateRendererTests(unittest.TestCase, Base):
+ def setUp(self):
+ Base.setUp(self)
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ def _getTargetClass(self):
+ from repoze.bfg.chameleon_text import TextTemplateRenderer
+ return TextTemplateRenderer
+
+ def _makeOne(self, *arg, **kw):
+ klass = self._getTargetClass()
+ return klass(*arg, **kw)
+
+ def test_instance_implements_ITemplate(self):
+ from zope.interface.verify import verifyObject
+ from repoze.bfg.interfaces import ITemplateRenderer
+ path = self._getTemplatePath('minimal.txt')
+ verifyObject(ITemplateRenderer, self._makeOne(path))
+
+ def test_class_implements_ITemplate(self):
+ from zope.interface.verify import verifyClass
+ from repoze.bfg.interfaces import ITemplateRenderer
+ verifyClass(ITemplateRenderer, self._getTargetClass())
+
+ def test_call(self):
+ self._zcmlConfigure()
+ minimal = self._getTemplatePath('minimal.txt')
+ instance = self._makeOne(minimal)
+ result = instance()
+ self.failUnless(isinstance(result, str))
+ self.assertEqual(result, 'Hello.\n')
+
+ def test_call_nonminimal(self):
+ self._zcmlConfigure()
+ nonminimal = self._getTemplatePath('nonminimal.txt')
+ instance = self._makeOne(nonminimal)
+ result = instance(name='Chris')
+ self.failUnless(isinstance(result, str))
+ self.assertEqual(result, 'Hello, Chris!\n')
+
+ def test_implementation(self):
+ self._zcmlConfigure()
+ minimal = self._getTemplatePath('minimal.txt')
+ instance = self._makeOne(minimal)
+ result = instance.implementation()()
+ self.failUnless(isinstance(result, str))
+ self.assertEqual(result, 'Hello.\n')
+
+class RenderTemplateTests(unittest.TestCase, Base):
+ def setUp(self):
+ Base.setUp(self)
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ def _getFUT(self):
+ from repoze.bfg.chameleon_text import render_template
+ return render_template
+
+ def test_it(self):
+ self._zcmlConfigure()
+ minimal = self._getTemplatePath('minimal.txt')
+ render = self._getFUT()
+ result = render(minimal)
+ self.failUnless(isinstance(result, str))
+ self.assertEqual(result, 'Hello.\n')
+
+class RenderTemplateToResponseTests(unittest.TestCase, Base):
+ def setUp(self):
+ Base.setUp(self)
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ def _getFUT(self):
+ from repoze.bfg.chameleon_text import render_template_to_response
+ return render_template_to_response
+
+ def test_minimal(self):
+ self._zcmlConfigure()
+ minimal = self._getTemplatePath('minimal.txt')
+ render = self._getFUT()
+ result = render(minimal)
+ from webob import Response
+ self.failUnless(isinstance(result, Response))
+ self.assertEqual(result.app_iter, ['Hello.\n'])
+ self.assertEqual(result.status, '200 OK')
+ self.assertEqual(len(result.headerlist), 2)
+
+class GetRendererTests(unittest.TestCase, Base):
+ def setUp(self):
+ Base.setUp(self)
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ def _getFUT(self):
+ from repoze.bfg.chameleon_text import get_renderer
+ return get_renderer
+
+ def test_nonabs_registered(self):
+ from zope.component import getGlobalSiteManager
+ from zope.component import queryUtility
+ from repoze.bfg.chameleon_text import TextTemplateRenderer
+ from repoze.bfg.interfaces import ITemplateRenderer
+ minimal = self._getTemplatePath('minimal.txt')
+ utility = TextTemplateRenderer(minimal)
+ gsm = getGlobalSiteManager()
+ gsm.registerUtility(utility, ITemplateRenderer, name=minimal)
+ get = self._getFUT()
+ result = get(minimal)
+ self.assertEqual(result, utility)
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility)
+
+ def test_nonabs_unregistered(self):
+ from zope.component import getGlobalSiteManager
+ from zope.component import queryUtility
+ from repoze.bfg.chameleon_text import TextTemplateRenderer
+ from repoze.bfg.interfaces import ITemplateRenderer
+ minimal = self._getTemplatePath('minimal.txt')
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), None)
+ utility = TextTemplateRenderer(minimal)
+ gsm = getGlobalSiteManager()
+ gsm.registerUtility(utility, ITemplateRenderer, name=minimal)
+ get = self._getFUT()
+ result = get(minimal)
+ self.assertEqual(result, utility)
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility)
+
+ def test_testing(self):
+ from zope.component import getGlobalSiteManager
+ from repoze.bfg.interfaces import ITestingTemplateRenderer
+ class Dummy:
+ template = object()
+ def implementation(self):
+ return self.template
+ gsm = getGlobalSiteManager()
+ utility = Dummy()
+ gsm.registerUtility(utility, ITestingTemplateRenderer, name='foo')
+ get = self._getFUT()
+ result = get('foo')
+ self.failUnless(result is utility)
+
+class GetTemplateTests(unittest.TestCase, Base):
+ def setUp(self):
+ Base.setUp(self)
+
+ def tearDown(self):
+ Base.tearDown(self)
+
+ def _getFUT(self):
+ from repoze.bfg.chameleon_text import get_template
+ return get_template
+
+ def test_nonabs_registered(self):
+ self._zcmlConfigure()
+ from zope.component import getGlobalSiteManager
+ from zope.component import queryUtility
+ from repoze.bfg.chameleon_text import TextTemplateRenderer
+ from repoze.bfg.interfaces import ITemplateRenderer
+ minimal = self._getTemplatePath('minimal.txt')
+ utility = TextTemplateRenderer(minimal)
+ gsm = getGlobalSiteManager()
+ gsm.registerUtility(utility, ITemplateRenderer, name=minimal)
+ get = self._getFUT()
+ result = get(minimal)
+ self.assertEqual(result.filename, minimal)
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility)
+
+ def test_nonabs_unregistered(self):
+ self._zcmlConfigure()
+ from zope.component import getGlobalSiteManager
+ from zope.component import queryUtility
+ from repoze.bfg.chameleon_text import TextTemplateRenderer
+ from repoze.bfg.interfaces import ITemplateRenderer
+ minimal = self._getTemplatePath('minimal.txt')
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), None)
+ utility = TextTemplateRenderer(minimal)
+ gsm = getGlobalSiteManager()
+ gsm.registerUtility(utility, ITemplateRenderer, name=minimal)
+ get = self._getFUT()
+ result = get(minimal)
+ self.assertEqual(result.filename, minimal)
+ self.assertEqual(queryUtility(ITemplateRenderer, minimal), utility)
+
+ def test_testing(self):
+ from zope.component import getGlobalSiteManager
+ from repoze.bfg.interfaces import ITestingTemplateRenderer
+ class Dummy:
+ template = object()
+ def implementation(self):
+ return self.template
+ gsm = getGlobalSiteManager()
+ utility = Dummy()
+ gsm.registerUtility(utility, ITestingTemplateRenderer, name='foo')
+ get = self._getFUT()
+ result = get('foo')
+ self.failUnless(result is utility.template)
+
+
+
+
+