Commit 1816437f authored by Evan Simpson's avatar Evan Simpson

Optimized scoping, and added nocall:

parent 8661868c
......@@ -89,7 +89,7 @@ Page Template-specific implementation of TALES, with handlers
for Python expressions, string literals, and paths.
"""
__version__='$Revision: 1.17 $'[11:-2]
__version__='$Revision: 1.18 $'[11:-2]
import re, sys
from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
......@@ -108,7 +108,7 @@ def getEngine():
def installHandlers(engine):
reg = engine.registerType
pe = PathExpr
for pt in ('standard', 'path', 'exists'):
for pt in ('standard', 'path', 'exists', 'nocall'):
reg(pt, pe)
reg('string', StringExpr)
reg('python', PythonExpr)
......@@ -171,14 +171,16 @@ class PathExpr:
dp.reverse()
return base, path, dp
def _eval(self, (base, path, dp), econtext, securityManager,
list=list, isinstance=isinstance, StringType=type('')):
def _eval(self, econtext, securityManager,
list=list, isinstance=isinstance, StringType=type(''),
render=render):
vars = econtext.vars
exists = 0
for base, path, dp in self._paths:
path = list(path) # Copy!
contexts = econtext.contexts
var = contexts['var']
# Expand dynamic path parts from right to left.
for i, varname in dp:
val = var[varname]
val = vars[varname]
if isinstance(val, StringType):
path[i] = val
else:
......@@ -188,33 +190,27 @@ class PathExpr:
try:
__traceback_info__ = base
if base == 'CONTEXTS':
ob = contexts
ob = econtext.contexts
else:
has, ob = var.has_get(base)
if not has:
ob = contexts[base]
ob = vars[base]
if path:
return restrictedTraverse(ob, path, securityManager)
else:
return ob
ob = restrictedTraverse(ob, path, securityManager)
exists = 1
break
except (AttributeError, KeyError, TypeError, IndexError,
'Unauthorized'), e:
return Undefined(self._s, sys.exc_info())
def __call__(self, econtext):
securityManager = getSecurityManager()
for pathinfo in self._paths:
ob = self._eval(pathinfo, econtext, securityManager)
exists = not isinstance(ob, Undefined)
ob = Undefined(self._s, sys.exc_info())
if exists:
# We're done
break
if self._name == 'exists':
# All we wanted to know is whether one of the paths exist.
return exists
if self._name == 'nocall' or isinstance(ob, StringType):
return ob
# Return the rendered object
return render(ob, econtext.contexts)
return render(ob, vars)
def __call__(self, econtext):
return self._eval(econtext, getSecurityManager())
def __str__(self):
return '%s expression "%s"' % (self._name, self._s)
......
......@@ -86,7 +86,7 @@
"""Generic Python Expression Handler
"""
__version__='$Revision: 1.2 $'[11:-2]
__version__='$Revision: 1.3 $'[11:-2]
from string import strip, split, join, replace, lstrip
......@@ -117,10 +117,10 @@ class PythonExpr:
def _bind_used_names(self, econtext):
# Bind template variables
names = {}
var = econtext.contexts['var']
vars = econtext.vars
getType = econtext._engine.getTypes().get
for vname in self._f_varnames:
has, val = var.has_get(vname)
has, val = vars.has_get(vname)
if not has:
has = val = getType(vname)
if has:
......
......@@ -87,7 +87,7 @@
An implementation of a generic TALES engine
"""
__version__='$Revision: 1.15 $'[11:-2]
__version__='$Revision: 1.16 $'[11:-2]
import re, sys, ZTUtils
from MultiMapping import MultiMapping
......@@ -230,49 +230,49 @@ class Context:
contexts['nothing'] = None
contexts['default'] = Default
# Keep track of what contexts get pushed as each scope begins.
self._ctxts_pushed = []
# These contexts will need to be pushed.
self._current_ctxts = {'local': 1, 'repeat': 1}
lv = self._context_class()
init_local = contexts.get('local', None)
if init_local:
lv._push(init_local)
contexts['local'] = lv
contexts['repeat'] = rep = self._context_class()
self.repeat_vars = rv = {}
# Wrap this, as it is visible to restricted code
contexts['repeat'] = rep = self._context_class(rv)
contexts['loop'] = rep # alias
contexts['global'] = gv = contexts.copy()
contexts['var'] = self._context_class(gv, lv)
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):
oldctxts = self._current_ctxts
self._ctxts_pushed.append(oldctxts)
self._current_ctxts = ctxts = {}
contexts = self.contexts
for ctxname in oldctxts.keys():
# Push fresh namespace on each local stack.
ctxts[ctxname] = ctx = {}
contexts[ctxname]._push(ctx)
self._scope_stack.append([self.local_vars.copy()])
def endScope(self):
self._current_ctxts = ctxts = self._ctxts_pushed.pop()
# Pop the ones that were pushed at the beginning of the scope.
contexts = self.contexts
for ctxname in ctxts.keys():
# Pop, then make sure there's no circular garbage
contexts[ctxname]._pop().clear()
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._current_ctxts['local'][name] = value
self.local_vars[name] = value
def setGlobal(self, name, value):
self.contexts['global'][name] = value
self.global_vars[name] = value
def setRepeat(self, name, expr):
expr = self.evaluate(expr)
it = self._engine.Iterator(name, expr, self)
self._current_ctxts['repeat'][name] = it
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,
......
......@@ -89,7 +89,7 @@ Handler for Python expressions, using the pre-Python 2.1 restriction
machinery from PythonScripts.
"""
__version__='$Revision: 1.2 $'[11:-2]
__version__='$Revision: 1.3 $'[11:-2]
from AccessControl import getSecurityManager
from Products.PythonScripts.Guarded import _marker, \
......@@ -128,7 +128,7 @@ def call_with_ns(f, ns, arg=1):
td.this = None
td._push(ns['request'])
td._push(InstanceDict(ns['here'], td))
td._push(ns['var'])
td._push(ns)
try:
if arg==2:
return f(None, td)
......
......@@ -88,7 +88,7 @@
Handler for Python expressions that uses the RestrictedPython package.
"""
__version__='$Revision: 1.2 $'[11:-2]
__version__='$Revision: 1.3 $'[11:-2]
from AccessControl import full_read_guard, full_write_guard, \
safe_builtins, getSecurityManager
......@@ -133,7 +133,7 @@ def call_with_ns(f, ns, arg=1):
td.this = None
td._push(ns['request'])
td._push(InstanceDict(ns['here'], td, full_read_guard))
td._push(ns['var'])
td._push(ns)
try:
if arg==2:
return f(None, td)
......
......@@ -11,7 +11,7 @@
</span>
<dl>
<span tal:repeat="arg options/batch">
<dt><span tal:replace="local/arg">??</span>.</dt>
<dt><span tal:replace="arg">??</span>.</dt>
<dd>Argument <span tal:define="num arg/num"
tal:replace="string: $num"
>99</span> was <span tal:replace="arg"
......
......@@ -89,25 +89,25 @@ class TALESTests(unittest.TestCase):
def testVariables(self):
'''Test variables'''
ctxt = self.getContext()
c = ctxt.contexts
c = ctxt.vars
ctxt.beginScope()
ctxt.setLocal('v1', 1)
ctxt.setLocal('v2', 2)
assert c['var']['v1'] == 1, 'Variable "v1"'
assert c['v1'] == 1, 'Variable "v1"'
ctxt.beginScope()
ctxt.setLocal('v1', 3)
ctxt.setGlobal('g', 1)
assert c['var']['v1'] == 3, 'Inner scope'
assert c['var']['v2'] == 2, 'Outer scope'
assert c['var']['g'] == 1, 'Global'
assert c['v1'] == 3, 'Inner scope'
assert c['v2'] == 2, 'Outer scope'
assert c['g'] == 1, 'Global'
ctxt.endScope()
assert c['var']['v1'] == 1, "Uncovered local"
assert c['var']['g'] == 1, "Global from inner scope"
assert c['v1'] == 1, "Uncovered local"
assert c['g'] == 1, "Global from inner scope"
ctxt.endScope()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment