[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