[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