[Checkins] SVN: z3c.pt/trunk/ Implemented TALES function namespaces.

Malthe Borch mborch at gmail.com
Mon Mar 2 04:57:37 EST 2009


Log message for revision 97414:
  Implemented TALES function namespaces.

Changed:
  U   z3c.pt/trunk/CHANGES.txt
  U   z3c.pt/trunk/src/z3c/pt/README.txt
  U   z3c.pt/trunk/src/z3c/pt/expressions.py
  A   z3c.pt/trunk/src/z3c/pt/namespaces.py
  A   z3c.pt/trunk/src/z3c/pt/tests/function_namespaces.pt
  U   z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py
  U   z3c.pt/trunk/src/z3c/pt/tests/view.pt

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2009-03-02 09:47:32 UTC (rev 97413)
+++ z3c.pt/trunk/CHANGES.txt	2009-03-02 09:57:37 UTC (rev 97414)
@@ -3,6 +3,8 @@
 
 In next release
 
+- Implemented TALES function namespaces. [sidnei, malthe]
+
 - Catch ``NameError`` in exists-traverser (return false). [malthe]
 
 - Catch ``NameError`` in exists-evaluator (return false). [malthe]

Modified: z3c.pt/trunk/src/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/README.txt	2009-03-02 09:47:32 UTC (rev 97413)
+++ z3c.pt/trunk/src/z3c/pt/README.txt	2009-03-02 09:57:37 UTC (rev 97414)
@@ -90,6 +90,7 @@
     <span>context</span>
     <span>request</span>
     <span>test</span>
+    <span>test</span>
   </div>
 
 The exercise is similar for the file-based variant.
@@ -101,6 +102,7 @@
     <span>context</span>
     <span>request</span>
     <span>test</span>
+    <span>test</span>
   </div>
 
 For compatibility reasons, view templates may be called with an
@@ -112,6 +114,7 @@
     <span>alt_context</span>
     <span>alt_request</span>
     <span>test</span>
+    <span>test</span>
   </div>
 
 Dollar-Interpolation
@@ -279,3 +282,98 @@
   <div xmlns="http://www.w3.org/1999/xhtml">
     <span>I don't exist?</span>
   </div>
+
+TALES Function Namespaces
+-------------------------
+
+As described on http://wiki.zope.org/zope3/talesns.html, it is
+possible to implement custom TALES Namespace Adapters. We also support
+low-level TALES Function Namespaces (which the TALES Namespace
+Adapters build upon).
+
+  >>> import datetime
+  >>> import zope.interface
+  >>> import zope.component
+  >>> from zope.traversing.interfaces import ITraversable
+  >>> from zope.traversing.interfaces import IPathAdapter
+  >>> from zope.tales.interfaces import ITALESFunctionNamespace
+  >>> from z3c.pt.namespaces import function_namespaces
+
+  >>> class ns1(object):
+  ...     zope.interface.implements(ITALESFunctionNamespace)
+  ...     def __init__(self, context):
+  ...         self.context = context
+  ...     def parent(self):
+  ...         return self.context.parent
+
+  >>> function_namespaces.registerFunctionNamespace('ns1', ns1)
+
+  >>> class ns2(object):
+  ...     def __init__(self, context):
+  ...         self.context = context
+  ...     def upper(self):
+  ...         return self.context.upper()
+
+  >>> zope.component.getGlobalSiteManager().registerAdapter(
+  ...     ns2, [zope.interface.Interface], IPathAdapter, 'ns2')
+
+  >>> class ns3(object):
+  ...     def __init__(self, context):
+  ...         self.context = context
+  ...     def fullDateTime(self):
+  ...         return self.context.strftime('%Y-%m-%d %H:%M:%S')
+
+  >>> zope.component.getGlobalSiteManager().registerAdapter(
+  ...     ns3, [zope.interface.Interface], IPathAdapter, 'ns3')
+
+
+A really corner-ish case from a legacy application: the TALES
+Namespace Adapter doesn't have a callable function but traverses the
+remaining path instead::
+
+  >>> class ns4(object):
+  ...     zope.interface.implements(ITraversable)
+  ...     def __init__(self, context):
+  ...         self.context = context
+  ...     def traverse(self, name, furtherPath):
+  ...         if len(furtherPath) == 1:
+  ...              name = '%s/%s' % (name, furtherPath.pop())
+  ...         return 'traversed: ' + name
+
+  >>> zope.component.getGlobalSiteManager().registerAdapter(
+  ...     ns4, [zope.interface.Interface], IPathAdapter, 'ns4')
+
+  >>> class Ob(object):
+  ...     def __init__(self, title, date, parent=None, child=None):
+  ...         self.title = title
+  ...         self.date = date
+  ...         self.parent = parent
+  ...         self.child = child
+
+  >>> child = Ob('child', datetime.datetime(2008, 12, 30, 13, 48, 0, 0))
+  >>> father = Ob('father', datetime.datetime(1978, 12, 30, 13, 48, 0, 0))
+  >>> grandpa = Ob('grandpa', datetime.datetime(1948, 12, 30, 13, 48, 0, 0))
+
+  >>> child.parent = father
+  >>> father.child = child
+  >>> father.parent = grandpa
+  >>> grandpa.child = father
+
+  >>> class View(object):
+  ...     request = u'request'
+  ...     context = father
+  ...
+  ...     def __repr__(self):
+  ...         return 'view'
+
+  >>> view = View()
+  >>> template = ViewPageTemplateFile(path+'/function_namespaces.pt')
+  >>> print template.bind(view)()
+  <div xmlns="http://www.w3.org/1999/xhtml">
+    <span>GRANDPA</span>
+    <span>2008-12-30 13:48:00</span>
+    <span>traversed: link:main</span>
+    <span>traversed: page/another</span>
+    <span>traversed: zope.Public</span>
+    <span>traversed: text-to-html</span>
+  </div>

Modified: z3c.pt/trunk/src/z3c/pt/expressions.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/expressions.py	2009-03-02 09:47:32 UTC (rev 97413)
+++ z3c.pt/trunk/src/z3c/pt/expressions.py	2009-03-02 09:57:37 UTC (rev 97414)
@@ -1,20 +1,24 @@
-import parser
 import re
-
-import zope.interface
-import zope.component
+import namespaces
 import zope.event
 
 from zope.traversing.adapters import traversePathElement
 from zope.contentprovider.interfaces import IContentProvider
 from zope.contentprovider.interfaces import ContentProviderLookupError
-from zope.contentprovider.interfaces import BeforeUpdateEvent
 
+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 types import MethodType
+
 _marker = object()
+_valid_name = re.compile(r"[a-zA-Z][a-zA-Z0-9_]*$").match
 
 def identity(x):
     return x
@@ -28,7 +32,8 @@
         if cp is None:
             raise ContentProviderLookupError(name)
 
-        zope.event.notify(BeforeUpdateEvent(cp, request))
+        if BeforeUpdateEvent is not None:
+            zope.event.notify(BeforeUpdateEvent(cp, request))
         cp.update()
         return cp.render()
 
@@ -39,15 +44,20 @@
     def __call__(self, base, request, call, *path_items):
         """See ``zope.app.pagetemplate.engine``."""
 
-        length = len(path_items)
-        if length:
-            i = 0
-            while i < length:
-                name = path_items[i]
-                i += 1
+        if bool(path_items):
+            path_items = list(path_items)
+            
+            while len(path_items):
+                name = path_items.pop(0)
+                ns = ':' in name
+                if ns is True:
+                    namespace, name = name.split(':', 1)
+                    base = namespaces.function_namespaces[namespace](base)
                 next = getattr(base, name, _marker)
                 if next is not _marker:
                     base = next
+                    if ns is True and isinstance(base, MethodType):
+                        base = base()
                     continue
                 else:
                     # special-case dicts for performance reasons
@@ -55,7 +65,7 @@
                         base = base[name]
                     else:
                         base = traversePathElement(
-                            base, name, path_items[i:], request=request)
+                            base, name, path_items, request=request)
 
                 if not isinstance(base, (basestring, tuple, list)):
                     base = self.proxify(base)
@@ -78,8 +88,8 @@
 
 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_@\-\.+/]*)*$')
+        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_]+')
@@ -267,8 +277,7 @@
             (value, types.value('False')))
         parts.exceptions = NameError,
         return parts
-    
+
 exists_translator = ExistsTranslator()
 path_translator = PathTranslator()
 provider_translator = ProviderTranslator()
-

Added: z3c.pt/trunk/src/z3c/pt/namespaces.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/namespaces.py	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/namespaces.py	2009-03-02 09:57:37 UTC (rev 97414)
@@ -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()
+

Added: z3c.pt/trunk/src/z3c/pt/tests/function_namespaces.pt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/tests/function_namespaces.pt	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/tests/function_namespaces.pt	2009-03-02 09:57:37 UTC (rev 97414)
@@ -0,0 +1,9 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:tal="http://xml.zope.org/namespaces/tal">
+  <span tal:content="context/ns1:parent/title/ns2:upper" />
+  <span tal:content="context/ns1:parent/child/child/date/ns3:fullDateTime" />
+  <span tal:content="context/ns4:link:main" />
+  <span tal:content="context/ns4:page/another" />
+  <span tal:content="context/ns4:zope.Public" />
+  <span tal:content="context/ns4:text-to-html" />
+</div>

Modified: z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2009-03-02 09:47:32 UTC (rev 97413)
+++ z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2009-03-02 09:57:37 UTC (rev 97414)
@@ -16,7 +16,7 @@
 
 def test_suite():
     filesuites = 'README.txt',
-    testsuites = 'z3c.pt.expressions',
+    testsuites = 'z3c.pt.expressions', 'z3c.pt.namespaces'
 
     config.DISK_CACHE = False
 

Modified: z3c.pt/trunk/src/z3c/pt/tests/view.pt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/tests/view.pt	2009-03-02 09:47:32 UTC (rev 97413)
+++ z3c.pt/trunk/src/z3c/pt/tests/view.pt	2009-03-02 09:57:37 UTC (rev 97414)
@@ -4,4 +4,6 @@
   <span tal:content="context" />
   <span tal:content="request" />
   <span tal:content="options/test" />
+  <span tal:condition="not:not:exists:options"
+  	tal:content="options/test/lower" />
 </div>



More information about the Checkins mailing list