[Zope3-checkins] CVS: Zope3/src/zope/tales - __init__.py:1.1 engine.py:1.1 expressions.py:1.1 pythonexpr.py:1.1 tales.py:1.1
Matt Hamilton
matth@netsight.co.uk
Mon, 14 Apr 2003 08:15:52 -0400
Update of /cvs-repository/Zope3/src/zope/tales
In directory cvs.zope.org:/tmp/cvs-serv8388/src/zope/tales
Added Files:
__init__.py engine.py expressions.py pythonexpr.py tales.py
Log Message:
Refactored TALES to move all of the TALES code out of the pagetemplate directory and into its own package.
=== Added File Zope3/src/zope/tales/__init__.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Template Attribute Language - Expression Syntax"""
=== Added File Zope3/src/zope/tales/engine.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Expression engine configuration and registration.
Each expression engine can have its own expression types and base names.
$Id: engine.py,v 1.1 2003/04/14 12:15:51 matth Exp $
"""
from zope.tales.tales import ExpressionEngine, RegistrationError
from zope.tales.expressions import PathExpr, StringExpr, NotExpr, DeferExpr
from zope.tales.expressions import SimpleModuleImporter
from zope.tales.pythonexpr import PythonExpr
def Engine():
e = ExpressionEngine()
reg = e.registerType
for pt in PathExpr._default_type_names:
reg(pt, PathExpr)
reg('string', StringExpr)
reg('python', PythonExpr)
reg('not', NotExpr)
reg('defer', DeferExpr)
e.registerBaseName('modules', SimpleModuleImporter())
return e
Engine = Engine()
=== Added File Zope3/src/zope/tales/expressions.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Basic Page Template expression types.
$Id: expressions.py,v 1.1 2003/04/14 12:15:51 matth Exp $
"""
__metaclass__ = type # All classes are new style when run with Python 2.2+
import re, sys
from types import StringTypes
from zope.tales.tales import ExpressionEngine
from zope.tales.tales import CompilerError
from zope.tales.tales import RegistrationError
from zope.tales.tales import _valid_name, _parse_expr, NAME_RE, Undefined
from zope.tales.pythonexpr import PythonExpr
Undefs = (Undefined, AttributeError, KeyError, TypeError, IndexError)
_marker = object()
def simpleTraverse(object, path_items, econtext):
"""Traverses a sequence of names, first trying attributes then items.
"""
for name in path_items:
next = getattr(object, name, _marker)
if next is not _marker:
object = next
elif hasattr(object, '__getitem__'):
object = object[name]
else:
raise NameError, name
return object
class SubPathExpr:
def __init__(self, path, traverser):
self._path = path = str(path).strip().split('/')
self._base = base = path.pop(0)
self._traverser = traverser
if not _valid_name(base):
raise CompilerError, 'Invalid variable name "%s"' % base
# 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()
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)
base = self._base
if base == 'CONTEXTS': # Special base name
ob = econtext.contexts
else:
ob = vars[base]
if isinstance(ob, DeferWrapper):
ob = ob()
if path:
ob = self._traverser(ob, path, econtext)
return ob
class PathExpr:
"""One or more subpath expressions, separated by '|'.
"""
# _default_type_names contains the expression type names this
# class is usually registered for.
_default_type_names = (
'standard',
'path',
'exists',
'nocall',
)
def __init__(self, name, expr, engine, traverser=simpleTraverse):
self._s = expr
self._name = name
paths = expr.split('|')
self._subexprs = []
add = self._subexprs.append
for i in range(len(paths)):
path = paths[i].lstrip()
if _parse_expr(path):
# This part is the start of another expression type,
# so glue it back together and compile it.
add(engine.compile('|'.join(paths[i:]).lstrip()))
break
add(SubPathExpr(path, traverser)._eval)
def _exists(self, econtext):
for expr in self._subexprs:
try:
expr(econtext)
except Undefs:
pass
else:
return 1
return 0
def _eval(self, econtext):
for expr in self._subexprs[:-1]:
# Try all but the last subexpression, skipping undefined ones.
try:
ob = expr(econtext)
except Undefs:
pass
else:
break
else:
# On the last subexpression allow exceptions through.
ob = self._subexprs[-1](econtext)
if self._name == 'nocall':
return ob
# Call the object if it is callable.
if hasattr(ob, '__call__'):
return ob()
return ob
def __call__(self, econtext):
if self._name == 'exists':
return self._exists(econtext)
return self._eval(econtext)
def __str__(self):
return '%s expression (%s)' % (self._name, `self._s`)
def __repr__(self):
return '<PathExpr %s:%s>' % (self._name, `self._s`)
_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})
class StringExpr:
def __init__(self, name, expr, engine):
self._s = expr
if '%' in expr:
expr = expr.replace('%', '%%')
self._vars = vars = []
if '$' in expr:
# Use whatever expr type is registered as "path".
path_type = engine.getTypes()['path']
parts = []
for exp in expr.split('$$'):
if parts: parts.append('$')
m = _interp.search(exp)
while m is not None:
parts.append(exp[:m.start()])
parts.append('%s')
vars.append(path_type(
'path', m.group(1) or m.group(2), engine))
exp = exp[m.end():]
m = _interp.search(exp)
if '$' in exp:
raise CompilerError, (
'$ must be doubled or followed by a simple path')
parts.append(exp)
expr = ''.join(parts)
self._expr = expr
def __call__(self, econtext):
vvals = []
for var in self._vars:
v = var(econtext)
vvals.append(v)
return self._expr % tuple(vvals)
def __str__(self):
return 'string expression (%s)' % `self._s`
def __repr__(self):
return '<StringExpr %s>' % `self._s`
class NotExpr:
def __init__(self, name, expr, engine):
self._s = expr = expr.lstrip()
self._c = engine.compile(expr)
def __call__(self, econtext):
return int(not econtext.evaluateBoolean(self._c))
def __repr__(self):
return '<NotExpr %s>' % `self._s`
class DeferWrapper:
def __init__(self, expr, econtext):
self._expr = expr
self._econtext = econtext
def __str__(self):
return str(self())
def __call__(self):
return self._expr(self._econtext)
class DeferExpr:
def __init__(self, name, expr, compiler):
self._s = expr = expr.lstrip()
self._c = compiler.compile(expr)
def __call__(self, econtext):
return DeferWrapper(self._c, econtext)
def __repr__(self):
return '<DeferExpr %s>' % `self._s`
class SimpleModuleImporter:
"""Minimal module importer with no security."""
def __getitem__(self, module):
mod = __import__(module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
=== Added File Zope3/src/zope/tales/pythonexpr.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Generic Python Expression Handler"""
__version__ = '$Revision: 1.1 $'[11:-2]
class PythonExpr:
def __init__(self, name, expr, engine):
text = expr.replace('\n', ' ').strip()
self.text = text
# The next line can legally raise SyntaxError.
self._code = code = compile(text, '<string>', 'eval')
self._varnames = code.co_names
def _bind_used_names(self, econtext, builtins):
# Bind template variables
names = {}
vars = econtext.vars
marker = self
for vname in self._varnames:
val = vars.get(vname, marker)
if val is not marker:
names[vname] = val
elif vname not in builtins:
# Fall back to using expression types as variable values.
val = econtext._engine.getTypes().get(vname, marker)
if val is not marker:
val = ExprTypeProxy(vname, val, econtext)
names[vname] = val
names['__builtins__'] = builtins
return names
def __call__(self, econtext):
__traceback_info__ = self.text
vars = self._bind_used_names(econtext, __builtins__)
return eval(self._code, vars)
def __str__(self):
return 'Python expression "%s"' % self.text
def __repr__(self):
return '<PythonExpr %s>' % self.text
class ExprTypeProxy:
'''Class that proxies access to an expression type handler'''
def __init__(self, name, handler, econtext):
self._name = name
self._handler = handler
self._econtext = econtext
def __call__(self, text):
return self._handler(self._name, text,
self._econtext._engine)(self._econtext)
=== Added File Zope3/src/zope/tales/tales.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""TALES
An implementation of a generic TALES engine
"""
__metaclass__ = type # All classes are new style when run with Python 2.2+
__version__ = '$Revision: 1.1 $'[11:-2]
import re
import sys
from types import StringTypes
from zope.pagetemplate import iterator
from zope.pagetemplate import safemapping
from zope.tal.interfaces import ITALESCompiler, ITALESEngine, ITALESErrorInfo
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_parse_expr = re.compile(r"(%s):" % NAME_RE).match
_valid_name = re.compile('%s$' % NAME_RE).match
class TALESError(Exception):
"""Error during TALES evaluation"""
class Undefined(TALESError):
'''Exception raised on traversal of an undefined path'''
class CompilerError(Exception):
'''TALES Compiler Error'''
class RegistrationError(Exception):
'''Expression type or base name registration Error'''
_default = object()
_marker = object()
class Iterator(iterator.Iterator):
def __init__(self, name, seq, context):
iterator.Iterator.__init__(self, seq)
self.name = name
self._context = context
def __iter__(self):
return self
def next(self):
if iterator.Iterator.next(self):
self._context.setLocal(self.name, self.seq[self.index])
return 1
return 0
class ErrorInfo:
"""Information about an exception passed to an on-error handler."""
__implements__ = ITALESErrorInfo
def __init__(self, err, position=(None, None)):
if isinstance(err, Exception):
self.type = err.__class__
self.value = err
else:
self.type = err
self.value = None
self.lineno = position[0]
self.offset = position[1]
class ExpressionEngine:
'''Expression Engine
An instance of this class keeps a mutable collection of expression
type handlers. It can compile expression strings by delegating to
these handlers. It can provide an expression Context, which is
capable of holding state and evaluating compiled expressions.
'''
__implements__ = ITALESCompiler
def __init__(self):
self.types = {}
self.base_names = {}
self.iteratorFactory = Iterator
def registerType(self, name, handler):
if not _valid_name(name):
raise RegistrationError, (
'Invalid expression type name "%s".' % name)
types = self.types
if name in types:
raise RegistrationError, (
'Multiple registrations for Expression type "%s".' %
name)
types[name] = handler
def getTypes(self):
return self.types
def registerBaseName(self, name, object):
if not _valid_name(name):
raise RegistrationError, 'Invalid base name "%s".' % name
base_names = self.base_names
if name in base_names:
raise RegistrationError, (
'Multiple registrations for base name "%s".' % name)
base_names[name] = object
def getBaseNames(self):
return self.base_names
def compile(self, expression):
m = _parse_expr(expression)
if m:
type = m.group(1)
expr = expression[m.end():]
else:
type = "standard"
expr = expression
try:
handler = self.types[type]
except KeyError:
raise CompilerError, (
'Unrecognized expression type "%s".' % type)
return handler(type, expr, self)
def getContext(self, contexts=None, **kwcontexts):
if contexts is not None:
if kwcontexts:
kwcontexts.update(contexts)
else:
kwcontexts = contexts
return Context(self, kwcontexts)
def getCompilerError(self):
return CompilerError
class Context:
'''Expression Context
An instance of this class holds context information that it can
use to evaluate compiled expressions.
'''
__implements__ = ITALESEngine
_context_class = safemapping.SafeMapping
position = (None, None)
source_file = None
def __init__(self, engine, contexts):
self._engine = engine
self.contexts = contexts
contexts['nothing'] = None
contexts['default'] = _default
self.repeat_vars = rv = {}
# Wrap this, as it is visible to restricted code
contexts['repeat'] = rep = self._context_class(rv)
contexts['loop'] = rep # alias
self.global_vars = gv = contexts.copy()
self.local_vars = lv = {}
self.vars = self._context_class(gv, lv)
# Keep track of what needs to be popped as each scope ends.
self._scope_stack = []
def beginScope(self):
self._scope_stack.append([self.local_vars.copy()])
def endScope(self):
scope = self._scope_stack.pop()
self.local_vars = lv = scope[0]
v = self.vars
v._pop()
v._push(lv)
# Pop repeat variables, if any
i = len(scope) - 1
while i:
name, value = scope[i]
if value is None:
del self.repeat_vars[name]
else:
self.repeat_vars[name] = value
i = i - 1
def setLocal(self, name, value):
self.local_vars[name] = value
def setGlobal(self, name, value):
self.global_vars[name] = value
def setRepeat(self, name, expr):
expr = self.evaluate(expr)
if not expr:
return self._engine.iteratorFactory(name, (), self)
it = self._engine.iteratorFactory(name, expr, self)
old_value = self.repeat_vars.get(name)
self._scope_stack[-1].append((name, old_value))
self.repeat_vars[name] = it
return it
def evaluate(self, expression,
isinstance=isinstance):
if isinstance(expression, str):
expression = self._engine.compile(expression)
__traceback_supplement__ = (
TALESTracebackSupplement, self, expression)
return expression(self)
evaluateValue = evaluate
def evaluateBoolean(self, expr):
return not not self.evaluate(expr)
def evaluateText(self, expr):
text = self.evaluate(expr)
if text is _default or text is None:
return text
if not isinstance(text, StringTypes):
text = unicode(text)
return text
def evaluateStructure(self, expr):
return self.evaluate(expr)
evaluateStructure = evaluate
def evaluateMacro(self, expr):
# XXX Should return None or a macro definition
return self.evaluate(expr)
evaluateMacro = evaluate
def createErrorInfo(self, err, position):
return ErrorInfo(err, position)
def getDefault(self):
return _default
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position):
self.position = position
class TALESTracebackSupplement:
"""Implementation of zope.exceptions.ITracebackSupplement"""
def __init__(self, context, expression):
self.context = context
self.source_url = context.source_file
self.line = context.position[0]
self.column = context.position[1]
self.expression = repr(expression)
def getInfo(self, as_html=0):
import pprint
data = self.context.contexts.copy()
if 'modules' in data:
del data['modules'] # the list is really long and boring
s = pprint.pformat(data)
if not as_html:
return ' - Names:\n %s' % s.replace('\n', '\n ')
else:
from cgi import escape
return '<b>Names:</b><pre>%s</pre>' % (escape(s))
return None
class SimpleExpr:
'''Simple example of an expression type handler'''
def __init__(self, name, expr, engine):
self._name = name
self._expr = expr
def __call__(self, econtext):
return self._name, self._expr
def __repr__(self):
return '<SimpleExpr %s %s>' % (self._name, `self._expr`)