[Checkins] SVN: megrok.chameleon/trunk/ Merge changes from z3c.pt-less branch back into trunk.
Uli Fouquet
uli at gnufix.de
Wed Mar 3 06:14:19 EST 2010
Log message for revision 109598:
Merge changes from z3c.pt-less branch back into trunk.
Changed:
U megrok.chameleon/trunk/CHANGES.txt
U megrok.chameleon/trunk/README.txt
U megrok.chameleon/trunk/setup.py
U megrok.chameleon/trunk/src/megrok/chameleon/README.txt
U megrok.chameleon/trunk/src/megrok/chameleon/components.py
U megrok.chameleon/trunk/src/megrok/chameleon/configure.zcml
A megrok.chameleon/trunk/src/megrok/chameleon/expressions.py
A megrok.chameleon/trunk/src/megrok/chameleon/namespaces.py
U megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app.py
U megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/expressions.cpt
A megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macromaster.cpt
A megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macrouser.cpt
-=-
Modified: megrok.chameleon/trunk/CHANGES.txt
===================================================================
--- megrok.chameleon/trunk/CHANGES.txt 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/CHANGES.txt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -4,6 +4,14 @@
0.5 (unreleased)
================
+* Added tests to show usage of macros with ``megrok.chameleon``.
+
+* Removed dependency from ``z3c.pt`` by copying the relevant bits over
+ and registering them locally.
+
+ Drop support for ``exists('varname')`` expressions. The regular
+ TALES expression ``exists: varname/path`` can still be used.
+
* Switch to use ``Chameleon`` instead of ``chameleon.*`` packages.
0.4 (2010-02-23)
Modified: megrok.chameleon/trunk/README.txt
===================================================================
--- megrok.chameleon/trunk/README.txt 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/README.txt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -9,7 +9,8 @@
For more information on Grok and Chameleon templates see:
- http://grok.zope.org/
-- http://pypi.python.org/pypi/chameleon.zpt
+- http://chameleon.repoze.org/
+- http://pypi.python.org/pypi/Chameleon
- http://pypi.python.org/pypi/chameleon.genshi
.. contents::
@@ -17,7 +18,7 @@
Requirements
============
-- Chameleon templates (`chameleon.zpt`).
+- Chameleon templates (`Chameleon`).
- Chameleon genshi templates (`chameleon.genshi`).
- Grok v1.0a1 or later, or five.grok 1.0 or later.
@@ -53,10 +54,10 @@
something like::
Getting distribution for 'megrok.chameleon'.
- Got megrok.chameleon 0.2.
+ Got megrok.chameleon 0.5.
That's all. You can now start using Chameleon page templates in your
-Grok application!
+Grok application.
Usage
Modified: megrok.chameleon/trunk/setup.py
===================================================================
--- megrok.chameleon/trunk/setup.py 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/setup.py 2010-03-03 11:14:18 UTC (rev 109598)
@@ -8,8 +8,11 @@
'grokcore.view',
'Chameleon',
'chameleon.genshi',
- 'z3c.pt',
'lxml', # Needed by chameleon.genshi
+ 'zope.component',
+ 'zope.contentprovider',
+ 'zope.event',
+ 'zope.traversing',
]
tests_require = [
Modified: megrok.chameleon/trunk/src/megrok/chameleon/README.txt
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/README.txt 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/src/megrok/chameleon/README.txt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -6,7 +6,7 @@
:Test-Layer: functional
With `megrok.chameleon` you can use templates parsed and rendered by
-`chameleon`. Currently Zope page templates and Genshi templates are
+`Chameleon`_. Currently Zope page templates and Genshi templates are
supported.
Chameleon Zope page templates
@@ -31,15 +31,15 @@
Beside this, most rules for regular Zope page templates apply also to
chameleon page templates.
-See the `chameleon.zpt`_ page for more information.
+See the `Chameleon`_ page for more information.
-.. _chameleon.zpt: http://pypi.python.org/pypi/chameleon.zpt
+.. _Chameleon: http://chameleon.repoze.org/docs/latest/zpt.html
Prerequisites
-------------
Before we can see the templates in action, we care for correct
-registration and set some used variables::
+registration and set some used variables:
>>> import os
>>> testdir = os.path.join(os.path.dirname(__file__), 'tests')
@@ -48,7 +48,7 @@
We register everything. Before we can grok our fixture, we have to
grok the `megrok.chameleon` package. This way the new template types
-are registered with the framework::
+are registered with the framework:
>>> import grokcore.view
>>> grokcore.view.testing.grok('megrok.chameleon')
@@ -62,7 +62,7 @@
>>> manfred = Mammoth()
>>> getRootFolder()['manfred'] = manfred
-Furthermore we prepare for getting the different views on manfred::
+Furthermore we prepare for getting the different views on manfred:
>>> from zope.publisher.browser import TestRequest
>>> from zope.component import getMultiAdapter
@@ -71,7 +71,7 @@
Simple templates
----------------
-We prepared a plain cavepainting view. The template looks like this::
+We prepared a plain cavepainting view. The template looks like this:
>>> cavepainting_cpt = os.path.join(template_dir, 'cavepainting.cpt')
>>> print open(cavepainting_cpt, 'rb').read()
@@ -81,7 +81,7 @@
</body>
</html>
-The rendered view looks like this::
+The rendered view looks like this:
>>> view = getMultiAdapter((manfred, request),
... name='cavepainting')
@@ -97,7 +97,7 @@
A template can access variables like ``view``, ``context`` and its
methods and attributes. The ``food`` view does exactly this. The
-template looks like this::
+template looks like this:
>>> food_cpt = os.path.join(template_dir, 'food.cpt')
>>> print open(food_cpt, 'rb').read()
@@ -114,7 +114,7 @@
</body>
</html>
-The rendered view looks like this::
+The rendered view looks like this:
>>> view = getMultiAdapter((manfred, request), name='food')
>>> print view()
@@ -174,7 +174,7 @@
* ``static``
the static dir of the application
-as we can see, when we look at the ``vars.cpt`` from our fixture::
+as we can see, when we look at the ``vars.cpt`` from our fixture:
>>> cpt_file = os.path.join(template_dir, 'vars.cpt')
>>> print open(cpt_file, 'rb').read()
@@ -199,7 +199,7 @@
</body>
</html>
-and render it::
+and render it:
>>> view = getMultiAdapter((manfred, request), name='vars')
>>> print view()
@@ -244,7 +244,7 @@
inline = components.ChameleonPageTemplate(
"<html><body>ME GROK HAS INLINES! ${view.sometext}</body></html>")
-If we render this view we get::
+If we render this view we get:
>>> view = getMultiAdapter((manfred, request), name='inline')
>>> print view()
@@ -253,32 +253,38 @@
TAL expressions
---------------
+Starting with ``megrok.chameleon`` 0.5 we deploy the all-in-one
+`Chameleon`_ package.
+
What TAL/TALES expressions in templates are supported depends mainly
-from the installed version of `chameleon.zpt`.
+from the installed version of `Chameleon`, while we support some
+additional, Zope-related TALES expressions.
A list of all supported expressions and statements can be found at the
-`chameleon.zpt documentation <http://chameleon.repoze.org/docs/zpt/>`_.
+`chameleon.zpt documentation
+<http://chameleon.repoze.org/docs/latest/zpt.html>`_. The additional
+TALES expressions provided by ``megrok.chameleon`` are:
-Furthermore `megrok.chameleon` currently comes with support for
-`z3c.pt`, a package that supports the more Zope specific expressions
-often used in page templates.
+* ``exists``
+ Tell whether a name exists in the templates' namespace.
-These include, for instance, support for viewlets, etc. The set of
-additional language constructs supported with this package can be seen
-at the `z3c.pt documentation
-<http://chameleon.repoze.org/docs/z3c/>`_. Please note, that
-`megrok.chameleon` templates (a.k.a. CPT templates), different to
-`z3c.pt` still use Python expressions by default.
+* ``not``
+ Evaluate the trailing expression to a boolean value and invert it.
-.. warning:: `z3c.pt` support might be factored out in future.
+* ``path``
+ Handle the trailing expression as a path and not as a
+ Python expression.
- While it is nice to have support for all the additional expressions
- provided by `z3c.pt`, using this package means a lot of more
- dependencies which might be unwanted in certain cases.
+* ``provider``
+ Support for viewlet providers.
- We therefore think about factoring additional z3c.pt support out to
- a separate package in not too far future.
+.. warning:: `z3c.pt` support has been dropped with
+ ``megrok.chameleon`` 0.5.
+.. note:: Starting with ``megrok.chameleon`` 0.5 support for the
+ Python expression ``exists()`` has been dropped. The TALES
+ expression ``exists: path/to/something`` is still available.
+
In our ``app.py`` we defined a special view for showing some special
expressions. This also includes a viewlet::
@@ -302,25 +308,10 @@
def render(self):
return 'Hello from viewlet'
+Now we can make use of the TALES expressions ``not:``, ``path:``,
+``exists:`` and ``provider:`` in the ``expressions.cpt`` template of
+our fixture:
-At least the following TAL/TALES expressions are supported by time of
-writing this:
-
-* ``exists``
- Tell whether a name exists in the templates' namespace.
-
-* ``not``
- Evaluate the trailing expression to a boolean value and invert it.
-
-* ``path``
- Handle the trailing expression as a path and not as a
- Python expression.
-
-* ``provider``
- Support for viewlet providers.
-
-as we can see, when we look at the ``expressions.cpt`` from our fixture::
-
>>> cpt_file = os.path.join(template_dir, 'expressions.cpt')
>>> print open(cpt_file, 'rb').read()
<html>
@@ -331,9 +322,6 @@
<div tal:condition="exists: food">
${food}
</div>
- <div tal:condition="exists('food')">
- ${food}
- </div>
<BLANKLINE>
<!-- We support `not` -->
<div tal:content="not: food" />
@@ -351,7 +339,7 @@
</body>
</html>
-and render it::
+and render it:
>>> view = getMultiAdapter((manfred, request), name='expressions')
>>> print view()
@@ -362,9 +350,6 @@
<div>
Yummy Dinoburger
</div>
- <div>
- Yummy Dinoburger
- </div>
<BLANKLINE>
<!-- We support `not` -->
<div>False</div>
@@ -382,21 +367,112 @@
</body>
</html>
+Macros
+------
-Clean up::
+With ``megrok.chameleon`` we can also use macros, although it is a bit
+different from regular Zope page templates.
+We can define macros like this:
+
+ >>> cpt_file = os.path.join(template_dir, 'macromaster.cpt')
+ >>> print open(cpt_file, 'rb').read()
+ <p xmlns:metal="http://xml.zope.org/namespaces/metal"
+ metal:define-macro="hello">
+ Hello from <b metal:define-slot="name">macro master</b>
+ </p>
+
+The defined macro ``hello`` can be rendered in another Chameleon
+template with the METAL attribute ``use-macro``.
+
+To refer to a local macro, i.e. a macros defined in the same template,
+you can use something like::
+
+ <div metal:use-macro="template.macros['<macro-name>']">
+ Replaced by macro
+ </div>
+
+where ``<macro-name>`` must be an existing macro name.
+
+To refer to macros in external templates, you must use the ``path:``
+expression like this::
+
+ <div metal:use-macro="path:
+ context/@@<viewname>/template/macros/<macro-name>">
+ Replaced by external macro
+ </div>
+
+where ``<viewname>`` refers to an existing view on ``context`` and
+``macro-name`` again refers to an existing macro in the specified template.
+
+Note, that this is different from how you refer to macros in standard
+Zope page templates. The short notation ``view/macros/<macro-name>``
+works only with regular Zope page templates.
+
+The following template makes use of both methods:
+
+ >>> cpt_file = os.path.join(template_dir, 'macrouser.cpt')
+ >>> print open(cpt_file, 'rb').read()
+ <html xmlns:metal="http://xml.zope.org/namespaces/metal">
+ <body>
+ <p metal:define-macro="hello">
+ Hi there from macro user!
+ </p>
+ <div metal:use-macro="template.macros['hello']">
+ Fill this
+ </div>
+ <BLANKLINE>
+ <div metal:use-macro="path: context/@@macromaster/template/macros/hello">
+ <b metal:fill-slot="name">user slot</b>
+ Fill this too
+ </div>
+ </body>
+ </html>
+
+When rendered also the slot defined in the master template is filled
+by macro user content:
+
+ >>> cpt_file = os.path.join(template_dir, 'macrouser.cpt')
+ >>> view = getMultiAdapter((manfred, request), name='macrouser')
+ >>> print view()
+ <html>
+ <body>
+ <p>
+ Hi there from macro user!
+ </p>
+ <p>
+ Hi there from macro user!
+ </p>
+ <BLANKLINE>
+ <BLANKLINE>
+ <p>
+ Hello from <b>user slot</b>
+ <BLANKLINE>
+ </p>
+ </body>
+ </html>
+
+
+Clean up:
+
>>> del getRootFolder()['manfred']
Differences from regular Zope page templates
--------------------------------------------
+* Macros are referenced differently. See appropriate section above.
+* Expressions are parsed in ``Python-mode`` by default. This means,
+ instead of ``tal:content="view/value"`` you must use
+ ``tal:content="view.value"``. Every occurence of TAL-expressions
+ starting with ``python:`` now can be shortened by skipping this
+ marker.
Chameleon Genshi templates
==========================
-Chameleon provides supprt for Genshi templates which can be used from
+Chameleon provides support for Genshi templates which can be used from
grok writing templates with the ``.cg`` filename extension.
Genshi text templates can be used with the ``.cgt`` filename
@@ -415,7 +491,7 @@
-------------
Before we can see the templates in action, we care for correct
-registration and set some used variables::
+registration and set some used variables:
>>> import os
>>> testdir = os.path.join(os.path.dirname(__file__), 'tests')
@@ -424,19 +500,19 @@
We register everything. Before we can grok our fixture, we have to
grok the `megrok.chameleon` package. This way the new template types
-are registered with the framework::
+are registered with the framework:
>>> grokcore.view.testing.grok('megrok.chameleon')
>>> grokcore.view.testing.grok('megrok.chameleon.tests.genshi_fixture')
We create a mammoth, which should provide us a bunch of Genshi driven
-views and put it in the database to setup location info::
+views and put it in the database to setup location info:
>>> from megrok.chameleon.tests.genshi_fixture.app import Mammoth
>>> manfred = Mammoth()
>>> getRootFolder()['manfred'] = manfred
-Furthermore we prepare for getting the different views on manfred::
+Furthermore we prepare for getting the different views on manfred:
>>> from zope.publisher.browser import TestRequest
>>> from zope.component import getMultiAdapter
@@ -446,7 +522,7 @@
Simple templates
----------------
-We prepared a plain cavepainting view. The template looks like this::
+We prepared a plain cavepainting view. The template looks like this:
>>> cavepainting_cg = os.path.join(template_dir, 'cavepainting.cg')
>>> print open(cavepainting_cg, 'rb').read()
@@ -456,7 +532,7 @@
</body>
</html>
-The rendered view looks like this::
+The rendered view looks like this:
>>> view = getMultiAdapter((manfred, request),
... name='cavepainting')
@@ -473,7 +549,7 @@
A template can access variables like ``view``, ``context`` and its
methods and attributes. The ``food`` view does exactly this. The
-template looks like this::
+template looks like this:
>>> food_cg = os.path.join(template_dir, 'food.cg')
>>> print open(food_cg, 'rb').read()
@@ -485,7 +561,7 @@
</body>
</html>
-The rendered view looks like this::
+The rendered view looks like this:
>>> view = getMultiAdapter((manfred, request), name='food')
>>> print view()
@@ -502,7 +578,7 @@
-------------------------
With genshi support we can also include other templates. The
-``gatherer`` view looks like this::
+``gatherer`` view looks like this:
>>> gatherer_cg = os.path.join(template_dir, 'gatherer.cg')
>>> print open(gatherer_cg, 'rb').read()
@@ -514,14 +590,14 @@
</html>
Apparently here we include a template called ``berries.cg``. It looks
-like this::
+like this:
>>> berries_cg = os.path.join(template_dir, 'berries.cg')
>>> print open(berries_cg, 'rb').read()
<strong>Lovely blueberries!</strong>
-When we render the former template, we get::
+When we render the former template, we get:
>>> view = getMultiAdapter((manfred, request), name='gatherer')
>>> print view()
@@ -536,7 +612,7 @@
--------------
Also genshi text templates are supported. We have a template that
-looks like so::
+looks like so:
>>> hunter_cgt = os.path.join(template_dir, 'hunter.cgt')
>>> print open(hunter_cgt, 'rb').read()
@@ -545,7 +621,7 @@
Note, that this template has the ``.cgt`` (= **c**\ ameleon **g**\ enshi
**t**\ ext template) file extension.
-If we render it, all expressions are substituted::
+If we render it, all expressions are substituted:
>>> view = getMultiAdapter((manfred, request), name='hunter')
>>> print view()
Modified: megrok.chameleon/trunk/src/megrok/chameleon/components.py
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/components.py 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/src/megrok/chameleon/components.py 2010-03-03 11:14:18 UTC (rev 109598)
@@ -20,7 +20,6 @@
from grokcore.component import GlobalUtility, implements, name
from grokcore.view import interfaces
from grokcore.view.components import GrokTemplate
-from z3c.pt.pagetemplate import evaluate_exists
#
# Chameleon Zope Page Templates...
@@ -48,7 +47,6 @@
namespace.update(dict(
template=self,
nothing=None,
- exists=evaluate_exists,
))
return namespace
Modified: megrok.chameleon/trunk/src/megrok/chameleon/configure.zcml
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/configure.zcml 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/src/megrok/chameleon/configure.zcml 2010-03-03 11:14:18 UTC (rev 109598)
@@ -5,7 +5,6 @@
<include package="grokcore.view" file="meta-minimal.zcml" />
<include package="grokcore.view" />
<include package="chameleon.zpt" />
- <include package="z3c.pt" />
<grok:grok package="." />
</configure>
Copied: megrok.chameleon/trunk/src/megrok/chameleon/expressions.py (from rev 109597, megrok.chameleon/branches/ulif-z3c.pt-less/src/megrok/chameleon/expressions.py)
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/expressions.py (rev 0)
+++ megrok.chameleon/trunk/src/megrok/chameleon/expressions.py 2010-03-03 11:14:18 UTC (rev 109598)
@@ -0,0 +1,299 @@
+import grokcore.component as grok
+import re
+import zope.event
+
+from zope.traversing.adapters import traversePathElement
+from zope.contentprovider.interfaces import IContentProvider
+from zope.contentprovider.interfaces import ContentProviderLookupError
+from zope.traversing.interfaces import ITraversable
+
+try:
+ from zope.contentprovider.interfaces import BeforeUpdateEvent
+except ImportError:
+ BeforeUpdateEvent = None
+
+from chameleon.core import types
+from chameleon.zpt import expressions
+from chameleon.zpt.interfaces import IExpressionTranslator
+
+from megrok.chameleon import namespaces
+
+
+_marker = object()
+_valid_name = re.compile(r"[a-zA-Z][a-zA-Z0-9_]*$").match
+
+def identity(x):
+ return x
+
+class ContentProviderTraverser(object):
+ def __call__(self, context, request, view, name):
+ cp = zope.component.queryMultiAdapter(
+ (context, request, view), IContentProvider, name=name)
+
+ # provide a useful error message, if the provider was not found.
+ if cp is None:
+ raise ContentProviderLookupError(name)
+
+ if BeforeUpdateEvent is not None:
+ zope.event.notify(BeforeUpdateEvent(cp, request))
+ cp.update()
+ return cp.render()
+
+class ZopeTraverser(object):
+ def __init__(self, proxify=identity):
+ self.proxify = proxify
+
+ def __call__(self, base, request, call, *path_items):
+ """See ``zope.app.pagetemplate.engine``."""
+
+ if bool(path_items):
+ path_items = list(path_items)
+ path_items.reverse()
+
+ while len(path_items):
+ name = path_items.pop()
+ ns = ':' in name
+ if ns is True:
+ namespace, name = name.split(':', 1)
+ base = namespaces.function_namespaces[namespace](base)
+ if ITraversable.providedBy(base):
+ base = self.proxify(traversePathElement(
+ base, name, path_items, request=request))
+ continue
+
+ # special-case dicts for performance reasons
+ if isinstance(base, dict):
+ next = base.get(name, _marker)
+ else:
+ next = getattr(base, name, _marker)
+
+ if next is not _marker:
+ base = next
+ if ns is True and isinstance(base, types.MethodType):
+ base = base()
+ continue
+ else:
+ base = traversePathElement(
+ base, name, path_items, request=request)
+
+ if not isinstance(base, (basestring, tuple, list)):
+ base = self.proxify(base)
+
+ if call and getattr(base, '__call__', _marker) is not _marker:
+ return base()
+
+ return base
+
+class ZopeExistsTraverser(ZopeTraverser):
+ exceptions = AttributeError, LookupError, TypeError
+
+ def __call__(self, base, request, call, *args, **kwargs):
+ try:
+ return ZopeTraverser.__call__(
+ self, base, request, False, *args, **kwargs) is not None
+ except self.exceptions:
+ return False
+ return True
+
+class PathTranslator(expressions.ExpressionTranslator):
+ path_regex = re.compile(
+ r'^((nocall|not):\s*)*([A-Za-z_][A-Za-z0-9_:]*)'+
+ r'(/[?A-Za-z_@\-+][?A-Za-z0-9_@\-\.+/:]*)*$')
+
+ interpolation_regex = re.compile(
+ r'\?[A-Za-z][A-Za-z0-9_]+')
+
+ path_traverse = ZopeTraverser()
+ scope = 'request'
+
+ symbol = '_path'
+
+ def translate(self, string, escape=None):
+ """
+ >>> translate = PathTranslator().translate
+
+ >>> translate("") is None
+ True
+
+ >>> translate("nocall: a")
+ value('a')
+
+ >>> translate("nothing")
+ value('None')
+
+ >>> translate("a/b")
+ value("_path(a, request, True, 'b')")
+
+ Verify allowed character set.
+
+ >>> translate("image_path/++res++/@@hello.html")
+ value("_path(image_path, request, True, '++res++', '@@hello.html')")
+
+ >>> translate("context/@@view")
+ value("_path(context, request, True, '@@view')")
+
+ >>> translate("nocall: context/@@view")
+ value("_path(context, request, False, '@@view')")
+
+ >>> translate("context/?view")
+ value("_path(context, request, True, '%s' % (view,))")
+
+ >>> translate("context/@@?view")
+ value("_path(context, request, True, '@@%s' % (view,))")
+ """
+
+ if not string:
+ return None
+
+ if not self.path_regex.match(string.strip()):
+ raise SyntaxError("Not a valid path-expression: %s." % string)
+
+ nocall = False
+
+ while string:
+ m = self.re_pragma.match(string)
+ if m is None:
+ break
+
+ string = string[m.end():]
+ pragma = m.group('pragma').lower()
+
+ if pragma == 'nocall':
+ nocall = True
+ else:
+ raise ValueError("Invalid pragma: %s" % pragma)
+
+ parts = string.strip().split('/')
+
+ # map 'nothing' to 'None'
+ parts = map(lambda part: part == 'nothing' and 'None' or part, parts)
+
+ components = []
+ for part in parts[1:]:
+ interpolation_args = []
+
+ def replace(match):
+ start, end = match.span()
+ interpolation_args.append(
+ part[start+1:end])
+ return "%s"
+
+ while True:
+ part, count = self.interpolation_regex.subn(replace, part)
+ if count == 0:
+ break
+
+ if len(interpolation_args):
+ component = "%s %% (%s,)" % (
+ repr(part), ", ".join(interpolation_args))
+ else:
+ component = repr(str(part))
+
+ components.append(component)
+
+ base = parts[0]
+
+ if not components:
+ if len(parts) == 1 and (nocall or base == 'None'):
+ value = types.value('%s' % base)
+ return value
+ else:
+ components = ()
+
+ value = types.value(
+ '%s(%s, %s, %s, %s)' % \
+ (self.symbol, base, self.scope, not nocall, ', '.join(components)))
+
+ value.symbol_mapping[self.symbol] = self.path_traverse
+
+ return value
+
+
+class NotTranslator(expressions.ExpressionTranslator, grok.Adapter):
+ grok.name('not')
+ grok.context(IExpressionTranslator)
+ grok.provides(IExpressionTranslator)
+
+ recursive = True
+
+ def __init__(self, translator):
+ self.translator = translator
+
+ def tales(self, string, escape=None):
+ """
+ >>> tales = NotTranslator(path_translator).tales
+
+ >>> tales("abc/def/ghi")
+ value("not(_path(abc, request, True, 'def', 'ghi'))")
+
+ >>> tales("abc | def")
+ parts(value('not(_path(abc, request, True, ))'),
+ value('not(_path(def, request, True, ))'))
+
+ >>> tales("abc | not: def")
+ parts(value('not(_path(abc, request, True, ))'),
+ value('not(not(_path(def, request, True, )))'))
+
+ >>> tales("abc | not: def | ghi")
+ parts(value('not(_path(abc, request, True, ))'),
+ value('not(not(_path(def, request, True, )))'),
+ value('not(not(_path(ghi, request, True, )))'))
+ """
+
+ value = self.translator.tales(string, escape=escape)
+ if isinstance(value, types.value):
+ value = (value,)
+
+ parts = []
+ for part in value:
+ factory = type(part)
+ value = factory("not(%s)" % part)
+ value.symbol_mapping.update(part.symbol_mapping)
+ parts.append(value)
+
+ if len(parts) == 1:
+ return parts[0]
+
+ return types.parts(parts)
+
+class ProviderTranslator(expressions.ExpressionTranslator):
+ provider_regex = re.compile(r'^[A-Za-z][A-Za-z0-9_\.-]*$')
+
+ symbol = '_get_content_provider'
+ content_provider_traverser = ContentProviderTraverser()
+
+ def translate(self, string, escape=None):
+ if self.provider_regex.match(string) is None:
+ raise SyntaxError(
+ "%s is not a valid content provider name." % string)
+
+ value = types.value("%s(context, request, view, '%s')" % \
+ (self.symbol, string))
+ value.symbol_mapping[self.symbol] = self.content_provider_traverser
+ return value
+
+class ExistsTranslator(PathTranslator):
+ """Implements string translation expression."""
+
+ symbol = '_path_exists'
+
+ path_traverse = ZopeExistsTraverser()
+
+ def translate(self, *args, **kwargs):
+ value = super(ExistsTranslator, self).translate(*args, **kwargs)
+ if value is None:
+ return
+
+ assert isinstance(value, types.value)
+ parts = types.parts(
+ (value, types.value('False')))
+ parts.exceptions = NameError,
+ return parts
+
+exists_translator = ExistsTranslator()
+path_translator = PathTranslator()
+provider_translator = ProviderTranslator()
+
+grok.global_utility(ExistsTranslator, name='exists')
+grok.global_utility(PathTranslator, name='path')
+grok.global_utility(ProviderTranslator, name='provider')
Copied: megrok.chameleon/trunk/src/megrok/chameleon/namespaces.py (from rev 109597, megrok.chameleon/branches/ulif-z3c.pt-less/src/megrok/chameleon/namespaces.py)
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/namespaces.py (rev 0)
+++ megrok.chameleon/trunk/src/megrok/chameleon/namespaces.py 2010-03-03 11:14:18 UTC (rev 109598)
@@ -0,0 +1,94 @@
+import zope.component
+from zope.traversing.interfaces import IPathAdapter
+
+class AdapterNamespaces(object):
+ """Simulate tales function namespaces with adapter lookup.
+
+ When we are asked for a namespace, we return an object that
+ actually computes an adapter when called:
+
+ To demonstrate this, we need to register an adapter:
+
+ >>> def adapter1(ob):
+ ... return 1
+ >>> zope.component.getGlobalSiteManager().registerAdapter(
+ ... adapter1, [zope.interface.Interface], IPathAdapter, 'a1')
+
+ Now, with this adapter in place, we can try out the namespaces:
+
+ >>> ob = object()
+ >>> namespaces = AdapterNamespaces()
+ >>> namespace = namespaces['a1']
+ >>> namespace(ob)
+ 1
+ >>> namespace = namespaces['a2']
+ >>> namespace(ob)
+ Traceback (most recent call last):
+ ...
+ KeyError: 'a2'
+ """
+
+ def __init__(self):
+ self.namespaces = {}
+
+ def __getitem__(self, name):
+ namespace = self.namespaces.get(name)
+ if namespace is None:
+ def namespace(object):
+ try:
+ return zope.component.getAdapter(object, IPathAdapter, name)
+ except zope.component.ComponentLookupError:
+ raise KeyError(name)
+
+ self.namespaces[name] = namespace
+ return namespace
+
+
+ def registerFunctionNamespace(self, namespacename, namespacecallable):
+ """Register a function namespace
+
+ namespace - a string containing the name of the namespace to
+ be registered
+
+ namespacecallable - a callable object which takes the following
+ parameter:
+
+ context - the object on which the functions
+ provided by this namespace will
+ be called
+
+ This callable should return an object which
+ can be traversed to get the functions provided
+ by the this namespace.
+
+ example:
+
+ class stringFuncs(object):
+
+ def __init__(self,context):
+ self.context = str(context)
+
+ def upper(self):
+ return self.context.upper()
+
+ def lower(self):
+ return self.context.lower()
+
+ engine.registerFunctionNamespace('string',stringFuncs)
+ """
+ self.namespaces[namespacename] = namespacecallable
+
+
+ def getFunctionNamespace(self, namespacename):
+ """ Returns the function namespace """
+ return self.namespaces[namespacename]
+
+try:
+ # If zope.app.pagetemplates is available, use the adapter
+ # registered with the main zope.app.pagetemplates engine so that
+ # we don't need to re-register them.
+ from zope.app.pagetemplates.engine import Engine
+ function_namespaces = Engine.namespaces
+except (ImportError, AttributeError):
+ function_namespaces = AdapterNamespaces()
+
Modified: megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app.py
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app.py 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app.py 2010-03-03 11:14:18 UTC (rev 109598)
@@ -39,3 +39,13 @@
grokcore.viewlet.viewletmanager(MainArea)
def render(self):
return 'Hello from viewlet'
+
+class MacroMaster(grokcore.view.View):
+ """A view with a template that contains macro defs.
+ """
+ pass
+
+class MacroUser(grokcore.view.View):
+ """A view with a template that uses macros.
+ """
+ pass
Modified: megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/expressions.cpt
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/expressions.cpt 2010-03-03 11:09:32 UTC (rev 109597)
+++ megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/expressions.cpt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -6,9 +6,6 @@
<div tal:condition="exists: food">
${food}
</div>
- <div tal:condition="exists('food')">
- ${food}
- </div>
<!-- We support `not` -->
<div tal:content="not: food" />
Copied: megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macromaster.cpt (from rev 109597, megrok.chameleon/branches/ulif-z3c.pt-less/src/megrok/chameleon/tests/cpt_fixture/app_templates/macromaster.cpt)
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macromaster.cpt (rev 0)
+++ megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macromaster.cpt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -0,0 +1,4 @@
+<p xmlns:metal="http://xml.zope.org/namespaces/metal"
+ metal:define-macro="hello">
+ Hello from <b metal:define-slot="name">macro master</b>
+</p>
Copied: megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macrouser.cpt (from rev 109597, megrok.chameleon/branches/ulif-z3c.pt-less/src/megrok/chameleon/tests/cpt_fixture/app_templates/macrouser.cpt)
===================================================================
--- megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macrouser.cpt (rev 0)
+++ megrok.chameleon/trunk/src/megrok/chameleon/tests/cpt_fixture/app_templates/macrouser.cpt 2010-03-03 11:14:18 UTC (rev 109598)
@@ -0,0 +1,15 @@
+<html xmlns:metal="http://xml.zope.org/namespaces/metal">
+<body>
+ <p metal:define-macro="hello">
+ Hi there from macro user!
+ </p>
+ <div metal:use-macro="template.macros['hello']">
+ Fill this
+ </div>
+
+ <div metal:use-macro="path: context/@@macromaster/template/macros/hello">
+ <b metal:fill-slot="name">user slot</b>
+ Fill this too
+ </div>
+</body>
+</html>
More information about the checkins
mailing list