[Zope-Checkins] CVS: Releases/Zope/lib/python/Products/PageTemplates - ExtSample.py:1.1.2.1 Expressions.py:1.43.46.2 PathIterator.py:1.4.70.1 PathPrefixes.py:1.1.2.2 TALES.py:1.38.4.1 ZopePageTemplate.py:1.45.2.1

Evan Simpson evan at 4-am.com
Fri Aug 29 15:16:08 EDT 2003


Update of /cvs-repository/Releases/Zope/lib/python/Products/PageTemplates
In directory cvs.zope.org:/tmp/cvs-serv25364/lib/python/Products/PageTemplates

Modified Files:
      Tag: evan-pathprefix-branch
	Expressions.py PathIterator.py PathPrefixes.py TALES.py 
	ZopePageTemplate.py 
Added Files:
      Tag: evan-pathprefix-branch
	ExtSample.py 
Log Message:
Added the ability to define prefixes using tal:define="prefix name expr".
Expression types for the prefix namespace are *not* the same as normal
TALES expression types.  There are only two: 'builtin' and 'ext'.
'builtin' allows the built-in prefix types to be renamed, and 'ext'
(for 'external' and/or 'extension') allows access to additional
prefix types defined by trusted code.  An example of this is
"ext:PageTemplates.NumericConversions", defined in "ExtSample.py".

The convention I'm trying to establish is that 'ext' prefix types
use names of the form "<Product>.<Descriptive Name>".

In order to accomodate this new facility, I had to defer the compilation
step for prefixed path segments until they are evaluated.  The compiled
segment is cached, and reused if the mapping from local prefix name to
prefix type doesn't change (which it usually won't).



=== Added File Releases/Zope/lib/python/Products/PageTemplates/ExtSample.py ===
from Products.PageTemplates.PathPrefixes import exts, PathPrefix

def n_compiler(prefix, arg):
    try:
        return globals()['f_' + arg]
    except IndexError:
        raise CompilerError, '%s:%s not found.' % (prefix, arg)
def n_handler(prefix, arg, object, path, econtext):
    return arg(object)
exts['PageTemplates.NumericConversions'] = PathPrefix(n_compiler, n_handler)

f_oct = oct
f_hex = hex
f_abs = abs
f_int = int

def f_odd(i):
    return not bool(i % 2)

def f_even(i):
    return bool(i % 2)

def f_parity(i):
    if i % 2:
        return 'odd'
    return 'even'

def f_letter(i, base=ord('a'), radix=26):
    s = ''
    while 1:
        i, off = divmod(i, radix)
        s = chr(base + off) + s
        if not i: return s

def f_Letter(i):
    return f_letter(i, base=ord('A'))

def f_Roman(i, rnvalues=(
    (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'),
    (100,'C'),(90,'XC'),(50,'L'),(40,'XL'),
    (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ):
    n = abs(int(i))
    s = ''
    for v, r in rnvalues:
        rct, n = divmod(n, v)
        s = s + r * rct
    return s

def f_roman(i):
    return f_Roman(i).lower()



=== Releases/Zope/lib/python/Products/PageTemplates/Expressions.py 1.43.46.1 => 1.43.46.2 ===
--- Releases/Zope/lib/python/Products/PageTemplates/Expressions.py:1.43.46.1	Tue Jul 29 15:55:43 2003
+++ Releases/Zope/lib/python/Products/PageTemplates/Expressions.py	Fri Aug 29 14:15:36 2003
@@ -19,7 +19,7 @@
 
 __version__='$Revision$'[11:-2]
 
-import re, sys
+import re, sys, weakref
 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
      Undefined, Default, _parse_expr
 from Acquisition import aq_base, aq_inner, aq_parent
@@ -112,16 +112,24 @@
                     raise
     return ob
 
-class SubPathHandler:
-    def __init__(self, prefix, arg, handler, do_validate):
+_pph_cache = weakref.WeakValueDictionary()
+
+class PathPrefixHandler(object):
+    '''Instances of this class are called when they are encountered
+    during path traversal.  The current traversal object, path,
+    context, and validator function are passed to it.'''
+    def __call__(self, object, path, econtext, validate):
+        pass
+
+class RegPathPrefixHandler(PathPrefixHandler):
+    def __init__(self, global_name, prefix, arg):
+        self.global_name = global_name
         self.prefix = prefix
         self.arg = arg
-        self.handler = handler
-        self.do_validate = do_validate
     def __call__(self, object, path, econtext, validate):
         arg = self.arg
-        o = self.handler(self.prefix, arg, object, path, econtext)
-        if self.do_validate and not validate(object, object, arg, o):
+        o = self.prefix.handler(self.global_name, arg, object, path, econtext)
+        if self.prefix.do_validate and not validate(object, object, arg, o):
             raise Unauthorized, arg
         return o
 
@@ -131,31 +139,55 @@
         self._base = base = path.pop(0)
         if not _valid_name(base):
             raise CompilerError, 'Invalid variable name "%s"' % base
-        # Parse path
         self._dp = dp = []
-        prefixes = PathPrefixes._subpath_prefixes
+        self._pps = pps = []
+        # Find dynamic and prefixed path segments
         for i in range(len(path)):
             e = path[i]
             if e[:1] == '?' and _valid_name(e[1:]):
                 dp.append((i, e[1:]))
             elif ':' in e:
-                prefix, arg = e.split(':', 1)
-                if not prefixes.has_key(prefix):
-                    raise CompilerError, (
-                        'Unknown prefix "%s"' % prefix)
-                compiler, handler, do_v = prefixes.get(prefix)
-                if compiler is not None:
-                    arg = compiler(prefix, arg)
-                if handler is None:
-                    path[i] = arg
-                else:
-                    path[i] = SubPathHandler(prefix, arg, handler, do_v)
+                pps.append((i, e.split(':', 1)))
         dp.reverse()
+        pps.reverse()
+        self.pmap_cache = {}
 
+    def check_pps(self, prefixes):
+        pmap_cache = self.pmap_cache
+        builtins = PathPrefixes.builtins
+        for i, (local_pname, arg) in self._pps:
+            if prefixes.has_key(local_pname):
+                global_pname, prefix = prefixes[local_pname]
+            elif builtins.has_key(local_pname):
+                global_pname = local_pname
+                prefix = builtins[global_pname]
+            else:
+                raise CompilerError, (
+                    'Unknown prefix "%s"' % local_pname)
+            if prefix == pmap_cache.get(local_pname):
+                # We've already handled this path segment correctly.
+                continue
+            
+            # Remember the new local->global prefix mapping, and
+            # replace the path segment with a new handler.
+            pmap_cache[local_pname] = prefix
+            pph = _pph_cache.get((prefix, arg))
+            if pph is None:
+                if prefix.compiler is not None:
+                    arg = prefix.compiler(global_pname, arg)
+                if prefix.handler is None:
+                    pph = arg
+                else:
+                    pph = RegPathPrefixHandler(global_pname, prefix, arg)
+                _pph_cache[(prefix, arg)] = pph
+            self._path[i] = pph
+            
     def _eval(self, econtext,
               list=list, isinstance=isinstance, StringType=type('')):
         vars = econtext.vars
         path = self._path
+        if self._pps:
+            self.check_pps(econtext.prefixes)
         if self._dp:
             path = list(path) # Copy!
             for i, varname in self._dp:
@@ -335,7 +367,7 @@
             object = object(*name)
             continue
 
-        if isinstance(name, SubPathHandler):
+        if isinstance(name, PathPrefixHandler):
             object = name(object, path, econtext, validate)
             continue
 


=== Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py 1.4 => 1.4.70.1 ===
--- Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py:1.4	Wed Aug 14 18:17:24 2002
+++ Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py	Fri Aug 29 14:15:36 2003
@@ -39,8 +39,8 @@
         name = filter(None, name)
         securityManager = getSecurityManager()
         try:
-            ob1 = restrictedTraverse(ob1, name, securityManager)
-            ob2 = restrictedTraverse(ob2, name, securityManager)
+            ob1 = restrictedTraverse(ob1, name, securityManager, None)
+            ob2 = restrictedTraverse(ob2, name, securityManager, None)
         except Undefs:
             return 0
         return ob1 == ob2


=== Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py 1.1.2.1 => 1.1.2.2 ===
--- Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py:1.1.2.1	Tue Jul 29 15:55:43 2003
+++ Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py	Fri Aug 29 14:15:36 2003
@@ -1,38 +1,69 @@
 from TALES import _valid_name, CompilerError
 
-_subpath_prefixes = {}
+class PrefixRegistry(dict):
+    def __setitem__(self, k, v):
+        if not isinstance(v, PathPrefix):
+            raise ValueError, '%s is not a PathPrefix' % `v`
+        dict.__setitem__(self, str(k), v)
+    setdefault = update = copy = None
+
+builtins = PrefixRegistry()
+exts = PrefixRegistry()
 
 def initialize():
     global guarded_getattr
-    from Expressions import guarded_getattr
+    from Expressions import guarded_getattr, _engine
+    reg = _engine.registerPrefixType
+    reg('builtin', PrefixExpr)
+    reg('ext', PrefixExpr)
+
+class PrefixExpr:
+    def __init__(self, name, expr, compiler):
+        self._name = name
+        self._s = expr.strip()
+        if name == 'builtin':
+            self.registry = builtins
+        elif name == 'ext':
+            self.registry = exts
+        else:
+            raise CompilerError, ('PrefixExpr called by unknown name %s' 
+                                  % name)
+    def __call__(self, econtext):
+        try:
+            return self.registry[self._s]
+        except IndexError:
+            raise NameError, ('%s is not a %s: path prefix' %
+                              (`self._s`, self._name))
+    def __repr__(self):
+        return '%s:%s' % (self._name, `self._s`)
 
-def registerSubPathPrefix(prefix, compiler=None, handler=None,
-                          do_validate=0):
-    '''Register a prefix for subpath expressions.
+class PathPrefix(object):
+    '''Implementation of a prefix for path expression segments.
 
-    A prefixed subpath is a subpath of the form "{px}:{arg}",
-    where {px} is the name of a prefix, and {arg} is an arbitrary
+    A prefixed path segment has the form "{px}:{arg}",
+    where {px} is the local name of a prefix, and {arg} is an arbitrary
     (and possibly empty) argument for the prefix.
 
-    When a subpath is prefixed, during compilation the compiler
+    The first time a prefixed path segment is traversed, the compiler
     (if any) for the prefix is called with {px} and {arg} as
     arguments, and the returned value replaces {arg} in further
     processing.  If no handler is provided, {arg} replaces the
-    subpath.
+    path segment.
 
-    If a handler is provided, it is called during traversal
+    If a handler is provided, it is called each time the segment
+    is traversed (after the compiler, on the first traversal),
     with {prefix}, {arg}, the current traversal object, the list
     of remaining path elements, and the expression context. The value
     returned by the handler replaces the current traversal object.
     If do_validate is true, the security validator is checked.
     '''
-    if not _valid_name(prefix):
-        raise ValueError, (
-            'Invalid subpath prefix "%s"' % prefix)
-    if compiler is None and handler is None:
-        raise ValueError, ("registerSubPathPrefix requires either "
-                           "a compiler or a handler, or both.")
-    _subpath_prefixes[str(prefix)] = (compiler, handler, do_validate)
+    def __init__(self, compiler=None, handler=None, do_validate=0):
+        if compiler is None and handler is None:
+            raise ValueError, ("A PathPrefix requires either "
+                               "a compiler or a handler, or both.")
+        self.compiler = compiler
+        self.handler = handler
+        self.do_validate = do_validate
 
 # 'var:x' is replaced with the value of variable 'x'
 def var_compiler(prefix, arg):
@@ -44,7 +75,7 @@
 def var_handler(prefix, arg, object, path, econtext):
     path.append(econtext.vars[arg])
     return object
-registerSubPathPrefix('var', var_compiler, var_handler)
+builtins['var'] = PathPrefix(var_compiler, var_handler)
 
 # 'call:' calls the current object.
 # 'call:x, y, x' passes variables 'x', 'y', and 'z' as arguments.
@@ -54,22 +85,22 @@
         if not _valid_name(name):
             raise CompilerError, ('"%s" is not a valid variable name'
                                   % name)
-    return args
+    return tuple(args)
 def call_handler(prefix, arg, object, path, econtext):
     args = [econtext.vars[name] for name in arg]
     return object(*args)
-registerSubPathPrefix('call',  call_compiler, call_handler)
+builtins['call'] = PathPrefix(call_compiler, call_handler)
 
 # 'key:foo' tries to fetch key 'foo' of the current object.
 def key_handler(prefix, arg, object, path, econtext):
     return object[arg]
-registerSubPathPrefix('key', handler=key_handler, do_validate=1)
+builtins['key'] = PathPrefix(handler=key_handler, do_validate=1)
 
 # 'item:6' tries to fetch integer key '6' of the current object.
 def item_compiler(prefix, arg):
     return int(arg)
-registerSubPathPrefix('item', compiler=item_compiler,
-                         handler=key_handler, do_validate=1)
+builtins['item'] = PathPrefix(compiler=item_compiler,
+                              handler=key_handler, do_validate=1)
 # 'attr:foo' tries to fetch attribute 'foo' of the current object.
 def attr_compiler(prefix, arg):
     arg = arg.strip()
@@ -79,25 +110,30 @@
     return arg
 def attr_handler(prefix, arg, object, path, econtext):
     return guarded_getattr(object, arg)
-registerSubPathPrefix('attr', attr_compiler, attr_handler)
+builtins['attr'] = PathPrefix(attr_compiler, attr_handler)
 
-# 'fmt:dollars_and_cents' calls standard PythonScript library
+# 'standard:dollars_and_cents' calls standard PythonScript library
 # function 'dollars_and_cents' on the current object.
-
-# 'fmt:%.2f' uses the Python formatting operator to format the
-# current object as a floating point number with two decimal places.
 try:
     from Products.PythonScripts import standard
-    _fmt_names = ('whole_dollars', 'dollars_and_cents',
+    _std_names = ('whole_dollars', 'dollars_and_cents',
                   'structured_text', 'restructured_text',
                   'sql_quote', 'html_quote', 'url_quote',
                   'url_quote_plus', 'newline_to_br',
                   'thousands_commas', 'url_unquote',
                   'url_unquote_plus', 'urlencode')
 except:
-    _fmt_names = ()
+    _std_names = None
+
+if _std_names:
+    def std_handler(prefix, arg, object, path, econtext):
+        if arg in _std_names:
+            return getattr(standard, arg)(object)
+        raise NameError, '%s is not a standard Script function' % `arg`
+    builtins['standard'] = PathPrefix(handler=std_handler)
+
+# 'fmt:%.2f' uses the Python formatting operator to format the
+# current object as a floating point number with two decimal places.
 def fmt_handler(prefix, arg, object, path, econtext):
-    if arg in _fmt_names:
-        return getattr(standard, arg)(object)
     return arg % object
-registerSubPathPrefix('fmt', handler=fmt_handler)
+builtins['fmt'] = PathPrefix(handler=fmt_handler)


=== Releases/Zope/lib/python/Products/PageTemplates/TALES.py 1.38 => 1.38.4.1 ===
--- Releases/Zope/lib/python/Products/PageTemplates/TALES.py:1.38	Wed May 14 17:55:14 2003
+++ Releases/Zope/lib/python/Products/PageTemplates/TALES.py	Fri Aug 29 14:15:36 2003
@@ -102,6 +102,7 @@
 
     def __init__(self, Iterator=None):
         self.types = {}
+        self.ptypes = {}
         if Iterator is not None:
             self.Iterator = Iterator
 
@@ -118,20 +119,47 @@
     def getTypes(self):
         return self.types
 
+    def registerPrefixType(self, name, handler):
+        if not _valid_name(name):
+            raise RegistrationError, 'Invalid Prefix type "%s".' % name
+        ptypes = self.ptypes
+        if ptypes.has_key(name):
+            raise RegistrationError, (
+                'Multiple registrations for Prefix type "%s".' %
+                name)
+        ptypes[name] = handler
+
+    def getPrefixTypes(self):
+        return self.ptypes
+
     def compile(self, expression):
         m = _parse_expr(expression)
         if m:
-            type = m.group(1)
+            etype = m.group(1)
             expr = expression[m.end():]
         else:
-            type = "standard"
+            etype = "standard"
             expr = expression
         try:
-            handler = self.types[type]
+            handler = self.types[etype]
+        except KeyError:
+            raise CompilerError, (
+                'Unrecognized expression type "%s".' % etype)
+        return handler(etype, expr, self)
+
+    def compilePrefix(self, expression):
+        m = _parse_expr(expression)
+        if m:
+            ptype = m.group(1)
+            expr = expression[m.end():]
+        else:
+            ptype = None
+        try:
+            handler = self.ptypes[ptype]
         except KeyError:
             raise CompilerError, (
-                'Unrecognized expression type "%s".' % type)
-        return handler(type, expr, self)
+                'Unrecognized prefix type "%s".' % ptype)
+        return expr, handler(ptype, expr, self)
 
     def getContext(self, contexts=None, **kwcontexts):
         if contexts is not None:
@@ -169,6 +197,7 @@
         self.global_vars = gv = contexts.copy()
         self.local_vars = lv = {}
         self.vars = self._context_class(gv, lv)
+        self.prefixes = {}
 
         # Keep track of what needs to be popped as each scope ends.
         self._scope_stack = []
@@ -200,6 +229,11 @@
 
     def setGlobal(self, name, value):
         self.global_vars[name] = value
+
+    def setPrefix(self, name, expr):
+        __traceback_supplement__ = (TALESTracebackSupplement, self, expr)
+        pname, expr = self._compiler.compilePrefix(expr)
+        self.prefixes[name] = pname, expr(self)
 
     def setRepeat(self, name, expr):
         expr = self.evaluate(expr)


=== Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py 1.45 => 1.45.2.1 ===
--- Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py:1.45	Sun Jul  6 06:43:56 2003
+++ Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py	Fri Aug 29 14:15:36 2003
@@ -361,4 +361,4 @@
         )
     context.registerHelp()
     context.registerHelpTitle('Zope Help')
-
+    import ExtSample




More information about the Zope-Checkins mailing list