[Zope3-checkins] CVS: Zope3/src/zope/tales - expressions.py:1.2 tales.py:1.2

Matt Hamilton matth@netsight.co.uk
Tue, 15 Apr 2003 11:54:48 -0400


Update of /cvs-repository/Zope3/src/zope/tales
In directory cvs.zope.org:/tmp/cvs-serv10941/src/zope/tales

Modified Files:
	expressions.py tales.py 
Log Message:
Implemented namespace:function notation for TALES expressions.  

This involved refactoring the expression compiler and evaluation methods. 




=== Zope3/src/zope/tales/expressions.py 1.1 => 1.2 ===
--- Zope3/src/zope/tales/expressions.py:1.1	Mon Apr 14 08:15:51 2003
+++ Zope3/src/zope/tales/expressions.py	Tue Apr 15 11:54:17 2003
@@ -18,7 +18,7 @@
 __metaclass__ = type # All classes are new style when run with Python 2.2+
 
 import re, sys
-from types import StringTypes
+from types import StringTypes, TupleType
 
 from zope.tales.tales import ExpressionEngine
 from zope.tales.tales import CompilerError
@@ -29,6 +29,7 @@
 Undefs = (Undefined, AttributeError, KeyError, TypeError, IndexError)
 
 _marker = object()
+namespace_re = re.compile('(\w+):(.+)')
 
 def simpleTraverse(object, path_items, econtext):
     """Traverses a sequence of names, first trying attributes then items.
@@ -46,34 +47,66 @@
 
 
 class SubPathExpr:
-    def __init__(self, path, traverser):
-        self._path = path = str(path).strip().split('/')
-        self._base = base = path.pop(0)
+    def __init__(self, path, traverser, engine):
         self._traverser = traverser
-        if not _valid_name(base):
-            raise CompilerError, 'Invalid variable name "%s"' % base
+        self._engine = engine
+        
         # Parse path
-        self._dp = dp = []
-        for i in range(len(path)):
-            e = path[i]
-            if e[:1] == '?' and _valid_name(e[1:]):
-                dp.append((i, e[1:]))
-        dp.reverse()
+        compiledpath = []
+        currentpath = []
+        for element in str(path).strip().split('/'):
+            if element.startswith('?'):
+                if currentpath:
+                    compiledpath.append(tuple(currentpath))
+                    currentpath=[]
+                if not _valid_name(element[1:]):
+                    raise CompilerError, 'Invalid variable name "%s"' % element[1:]
+                compiledpath.append(element[1:])
+            else:
+                match = namespace_re.match(element)
+                if match:
+                    if currentpath:
+                        compiledpath.append(tuple(currentpath))
+                        currentpath=[]
+                    namespace, functionname = match.groups()
+                    if not _valid_name(namespace):
+                        raise CompilerError, 'Invalid namespace name "%s"' % namespace
+                    if not _valid_name(functionname):
+                        raise CompilerError, 'Invalid function name "%s"' % functionname
+                    try:
+                        compiledpath.append(self._engine.getFunctionNamespace(namespace))
+                    except KeyError:
+                        raise CompilerError, 'Unknown namespace "%s"' % namespace
+                    currentpath.append(functionname)
+                else:
+                    currentpath.append(element)
+
+        if currentpath:
+            compiledpath.append(tuple(currentpath))
+            
+        first = compiledpath[0]
+        base = first[0]
+        
+        if callable(first):
+            # check for initial function
+            raise CompilerError,'Namespace function specified in first subpath element'
+        elif isinstance(first,StringTypes):
+            # check for initial ?
+            raise CompilerError,'Dynamic name specified in first subpath element'
+        
+        if not _valid_name(base):
+            raise CompilerError, 'Invalid variable name "%s"' % element
+        self._base = base
+        compiledpath[0]=first[1:]
+        self._compiled_path = tuple(compiledpath)
 
+        
     def _eval(self, econtext,
               list=list, isinstance=isinstance):
         vars = econtext.vars
-        path = self._path
-        if self._dp:
-            path = list(path) # Copy!
-            for i, varname in self._dp:
-                val = vars[varname]
-                if isinstance(val, StringTypes):
-                    path[i] = val
-                else:
-                    # If the value isn't a string, assume it's a sequence
-                    # of path names.
-                    path[i:i+1] = list(val)
+
+        compiled_path = self._compiled_path
+
         base = self._base
         if base == 'CONTEXTS':  # Special base name
             ob = econtext.contexts
@@ -81,8 +114,21 @@
             ob = vars[base]
         if isinstance(ob, DeferWrapper):
             ob = ob()
-        if path:
-            ob = self._traverser(ob, path, econtext)
+
+        for element in compiled_path:
+            if isinstance(element,TupleType):
+                ob = self._traverser(ob, element, econtext)
+            elif isinstance(element,StringTypes):
+                val = vars[element]
+                # If the value isn't a string, assume it's a sequence
+                # of path names.
+                if isinstance(val,StringTypes):
+                    val = (val,)
+                ob = self._traverser(ob, val, econtext)
+            elif callable(element):
+                ob = element(ob)
+            else:
+                raise "Waagh!"
         return ob
 
 
@@ -113,7 +159,7 @@
                 # so glue it back together and compile it.
                 add(engine.compile('|'.join(paths[i:]).lstrip()))
                 break
-            add(SubPathExpr(path, traverser)._eval)
+            add(SubPathExpr(path, traverser, engine)._eval)
 
     def _exists(self, econtext):
         for expr in self._subexprs:


=== Zope3/src/zope/tales/tales.py 1.1 => 1.2 ===
--- Zope3/src/zope/tales/tales.py:1.1	Mon Apr 14 08:15:51 2003
+++ Zope3/src/zope/tales/tales.py	Tue Apr 15 11:54:17 2003
@@ -100,8 +100,49 @@
     def __init__(self):
         self.types = {}
         self.base_names = {}
+        self.namespaces = {}
         self.iteratorFactory = Iterator
 
+    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:
+
+              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]
+
     def registerType(self, name, handler):
         if not _valid_name(name):
             raise RegistrationError, (
@@ -226,7 +267,7 @@
         if isinstance(expression, str):
             expression = self._engine.compile(expression)
         __traceback_supplement__ = (
-            TALESTracebackSupplement, self, expression)
+           TALESTracebackSupplement, self, expression)
         return expression(self)
 
     evaluateValue = evaluate