[Zope3-checkins] SVN: Zope3/branches/Zope-3.1/ Update TAL/METAL
implementation:
Fred L. Drake, Jr.
fdrake at gmail.com
Fri Aug 19 19:01:37 EDT 2005
Log message for revision 38014:
Update TAL/METAL implementation:
- move to METAL 1.1, including the macro extension feature
- integrate various maintenance patches from the Zope 3 trunk intended
to reduce the difference between the Zope 2 and Zope 3
implementations of page templates
This update merges the following revisions from the trunk:
Revision 37825:
add the FasterStringIO to replace the basic StringIO, so this matches the
Zope 2 code and gets a tiny bit faster as well
Revision 37830:
correct a broken comment, and expand with useful information
Revision 37939:
- add a comment about a mysterious instance variable
- remove an unused assignment statement
- simplify an expression assignment
Revision 37944:
changed the macro stack entries to have meaningful attribute names so
references to individual fields are easier to decipher
Revision 38008:
repair outdated comment
Revision 38009:
"surface" portion of the move to METAL 1.1: macro extension requires the
extend-macro attribute instead of use-macro, and cannot be combined with
use-macro
Revision 38010:
update comment based on the move to extend-macro
Revision 38012:
fix the semantics of macro extension to match the METAL 1.1 specification
Revision 38013:
note the move to METAL 1.1
Changed:
U Zope3/branches/Zope-3.1/doc/CHANGES.txt
U Zope3/branches/Zope-3.1/src/zope/app/ftesting.zcml
A Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/__init__.py
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/configure.zcml
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/inner.pt
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/intermediate.pt
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/outer.pt
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/test_nested.py
_U Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests/test_nested.txt
U Zope3/branches/Zope-3.1/src/zope/tal/taldefs.py
U Zope3/branches/Zope-3.1/src/zope/tal/talgenerator.py
U Zope3/branches/Zope-3.1/src/zope/tal/talinterpreter.py
U Zope3/branches/Zope-3.1/src/zope/tal/tests/input/acme_template.pt
U Zope3/branches/Zope-3.1/src/zope/tal/tests/input/test_metal9.html
U Zope3/branches/Zope-3.1/src/zope/tal/tests/output/test_metal9.html
U Zope3/branches/Zope-3.1/src/zope/tal/tests/test_htmltalparser.py
U Zope3/branches/Zope-3.1/src/zope/tal/tests/test_talinterpreter.py
-=-
Modified: Zope3/branches/Zope-3.1/doc/CHANGES.txt
===================================================================
--- Zope3/branches/Zope-3.1/doc/CHANGES.txt 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/doc/CHANGES.txt 2005-08-19 23:01:37 UTC (rev 38014)
@@ -10,6 +10,12 @@
New features
+ - Implemented the METAL 1.1 macro language in page templates.
+ This adds the "macro extension" feature to the METAL 1.0
+ implementation included in previous versions of Zope. The new
+ specification is available in the ZPT wiki:
+ http://www.zope.org/Wikis/DevSite/Projects/ZPT/MetalSpecification11
+
- Change ``zope.app.intid.addIntIdSubscriber`` and
``.removeIntIdSubscriber`` to adapt it's ob argument to
``IKeyReference``. They register/unregister the object only if
Modified: Zope3/branches/Zope-3.1/src/zope/app/ftesting.zcml
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/app/ftesting.zcml 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/app/ftesting.zcml 2005-08-19 23:01:37 UTC (rev 38014)
@@ -5,5 +5,6 @@
>
<include package=".container.browser.ftests" />
+<include package=".pagetemplate.ftests" />
</configure>
Copied: Zope3/branches/Zope-3.1/src/zope/app/pagetemplate/ftests (from rev 38013, Zope3/trunk/src/zope/app/pagetemplate/ftests)
Modified: Zope3/branches/Zope-3.1/src/zope/tal/taldefs.py
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/taldefs.py 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/taldefs.py 2005-08-19 23:01:37 UTC (rev 38014)
@@ -36,6 +36,7 @@
# TODO: In Python 2.4 we can use frozenset() instead of dict.fromkeys()
KNOWN_METAL_ATTRIBUTES = dict.fromkeys([
"define-macro",
+ "extend-macro",
"use-macro",
"define-slot",
"fill-slot",
Modified: Zope3/branches/Zope-3.1/src/zope/tal/talgenerator.py
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/talgenerator.py 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/talgenerator.py 2005-08-19 23:01:37 UTC (rev 38014)
@@ -442,12 +442,12 @@
return None
def replaceAttrs(self, attrlist, repldict):
- # Each entry in attrlist starts like (name, value).
- # Result is (name, value, action, expr, xlat) if there is a
+ # Each entry in attrlist starts like (name, value). Result is
+ # (name, value, action, expr, xlat, msgid) if there is a
# tal:attributes entry for that attribute. Additional attrs
# defined only by tal:attributes are added here.
#
- # (name, value, action, expr, xlat)
+ # (name, value, action, expr, xlat, msgid)
if not repldict:
return attrlist
newlist = []
@@ -508,6 +508,7 @@
todo = {}
defineMacro = metaldict.get("define-macro")
+ extendMacro = metaldict.get("extend-macro")
useMacro = metaldict.get("use-macro")
defineSlot = metaldict.get("define-slot")
fillSlot = metaldict.get("fill-slot")
@@ -537,12 +538,25 @@
raise I18NError("i18n:data must be accompanied by i18n:translate",
position)
- if defineMacro or useMacro:
+ if extendMacro:
+ if useMacro:
+ raise METALError(
+ "extend-macro cannot be used with use-macro", position)
+ if not defineMacro:
+ raise METALError(
+ "extend-macro must be used with define-macro", position)
+
+ if defineMacro or extendMacro or useMacro:
if fillSlot or defineSlot:
raise METALError(
"define-slot and fill-slot cannot be used with "
- "define-macro or use-macro", position)
+ "define-macro, extend-macro, or use-macro", position)
+ if defineMacro and useMacro:
+ raise METALError(
+ "define-macro may not be used with use-macro", position)
+ useMacro = useMacro or extendMacro
+
if content and msgid:
raise I18NError(
"explicit message id and tal:content can't be used together",
Modified: Zope3/branches/Zope-3.1/src/zope/tal/talinterpreter.py
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/talinterpreter.py 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/talinterpreter.py 2005-08-19 23:01:37 UTC (rev 38014)
@@ -16,6 +16,7 @@
$Id$
"""
import cgi
+import operator
import sys
# Do not use cStringIO here! It's not unicode aware. :(
@@ -87,6 +88,28 @@
return TALGenerator.replaceAttrs(self, attrlist, repldict)
+
+class MacroStackItem(list):
+ # This is a `list` subclass for backward compability.
+ """Stack entry for the TALInterpreter.macroStack.
+
+ This offers convenience attributes for more readable access.
+
+ """
+ __slots__ = ()
+
+ # These would be nicer using @syntax, but that would require
+ # Python 2.4.x; this will do for now.
+
+ macroName = property(lambda self: self[0])
+ slots = property(lambda self: self[1])
+ definingName = property(lambda self: self[2])
+ extending = property(lambda self: self[3])
+ entering = property(lambda self: self[4],
+ lambda self, value: operator.setitem(self, 4, value))
+ i18nContext = property(lambda self: self[5])
+
+
class TALInterpreter(object):
"""TAL interpreter.
@@ -180,9 +203,11 @@
self.html = 0
self.endsep = "/>"
self.endlen = len(self.endsep)
- # macroStack contains:
- # [(macroName, slots, definingName, extending, entering, i18ncontext)]
+ # macroStack entries are MacroStackItem instances;
+ # the entries are mutated while on the stack
self.macroStack = []
+ # `inUseDirective` is set iff we're handling either a
+ # metal:use-macro or a metal:extend-macro
self.inUseDirective = False
self.position = None, None # (lineno, offset)
self.col = 0
@@ -194,6 +219,11 @@
self.i18nContext = TranslationContext()
self.sourceAnnotations = sourceAnnotations
+ def StringIO(self):
+ # Third-party products wishing to provide a full Unicode-aware
+ # StringIO can do so by monkey-patching this method.
+ return FasterStringIO()
+
def saveState(self):
return (self.position, self.col, self.stream, self._stream_stack,
self.scopeLevel, self.level, self.i18nContext)
@@ -222,12 +252,13 @@
assert self.level == level
assert self.scopeLevel == scopeLevel
- def pushMacro(self, macroName, slots, definingName, extending, entering=1):
+ def pushMacro(self, macroName, slots, definingName, extending):
if len(self.macroStack) >= self.stackLimit:
raise METALError("macro nesting limit (%d) exceeded "
"by %s" % (self.stackLimit, `macroName`))
- self.macroStack.append([macroName, slots, definingName, extending,
- entering, self.i18nContext])
+ self.macroStack.append(
+ MacroStackItem((macroName, slots, definingName, extending,
+ True, self.i18nContext)))
def popMacro(self):
return self.macroStack.pop()
@@ -405,20 +436,18 @@
# use-macro and its extensions
if len(macs) > 1:
for macro in macs[1:]:
- extending = macro[3]
- if not extending:
+ if not macro.extending:
return ()
- if not macs[-1][4]:
+ if not macs[-1].entering:
return ()
- # Clear 'entering' flag
- macs[-1][4] = 0
+ macs[-1].entering = False
# Convert or drop depth-one METAL attributes.
i = name.rfind(":") + 1
prefix, suffix = name[:i], name[i:]
if suffix == "define-macro":
# Convert define-macro as we enter depth one.
- useName = macs[0][0]
- defName = macs[0][2]
+ useName = macs[0].macroName
+ defName = macs[0].definingName
res = []
if defName:
res.append('%sdefine-macro=%s' % (prefix, quote(defName)))
@@ -475,7 +504,7 @@
def no_tag(self, start, program):
state = self.saveState()
- self.stream = stream = StringIO()
+ self.stream = stream = self.StringIO()
self._stream_write = stream.write
self.interpret(start)
self.restoreOutputState(state)
@@ -631,7 +660,7 @@
# evaluate the mini-program to get the value of the variable.
state = self.saveState()
try:
- tmpstream = StringIO()
+ tmpstream = self.StringIO()
self.pushStream(tmpstream)
try:
self.interpret(program)
@@ -691,7 +720,7 @@
# Use a temporary stream to capture the interpretation of the
# subnodes, which should /not/ go to the output stream.
currentTag = self._currentTag
- tmpstream = StringIO()
+ tmpstream = self.StringIO()
self.pushStream(tmpstream)
try:
self.interpret(stuff[1])
@@ -788,7 +817,7 @@
lang, program = stuff
# Use a temporary stream to capture the interpretation of the
# subnodes, which should /not/ go to the output stream.
- tmpstream = StringIO()
+ tmpstream = self.StringIO()
self.pushStream(tmpstream)
try:
self.interpret(program)
@@ -833,7 +862,6 @@
bytecode_handlers["condition"] = do_condition
def do_defineMacro(self, (macroName, macro)):
- macs = self.macroStack
wasInUse = self.inUseDirective
self.inUseDirective = False
self.interpret(macro)
@@ -882,10 +910,7 @@
# extendMacro results from a combination of define-macro and
# use-macro. definingName has the value of the
# metal:define-macro attribute.
- extending = False
- if self.metal and self.inUseDirective:
- # extend the calling directive.
- extending = True
+ extending = self.metal and self.inUseDirective
self.do_useMacro((macroName, macroExpr, compiledSlots, block),
definingName, extending)
bytecode_handlers["extendMacro"] = do_extendMacro
@@ -906,7 +931,7 @@
# Measure the extension depth of this use-macro
depth = 1
while depth < len_macs:
- if macs[-depth][3]:
+ if macs[-depth].extending:
depth += 1
else:
break
@@ -914,13 +939,12 @@
# most general macro. The most general is at the top of
# the stack.
slot = None
- i = len_macs - depth
- while i < len_macs:
- slots = macs[i][1]
- slot = slots.get(slotName)
+ i = len_macs - 1
+ while i >= (len_macs - depth):
+ slot = macs[i].slots.get(slotName)
if slot is not None:
break
- i += 1
+ i -= 1
if slot is not None:
# Found a slot filler. Temporarily chop the macro
# stack starting at the macro that filled the slot and
@@ -934,7 +958,7 @@
self.sourceFile = prev_source
# Restore the stack entries.
for mac in chopped:
- mac[4] = 0 # Not entering
+ mac.entering = False # Not entering
macs.extend(chopped)
return
# Falling out of the 'if' allows the macro to be interpreted.
@@ -946,7 +970,7 @@
def do_onError_tal(self, (block, handler)):
state = self.saveState()
- self.stream = stream = StringIO()
+ self.stream = stream = self.StringIO()
self._stream_write = stream.write
try:
self.interpret(block)
@@ -980,3 +1004,26 @@
bytecode_handlers_tal["onError"] = do_onError_tal
bytecode_handlers_tal["<attrAction>"] = attrAction_tal
bytecode_handlers_tal["optTag"] = do_optTag_tal
+
+
+class FasterStringIO(StringIO):
+ """Append-only version of StringIO.
+
+ This let's us have a much faster write() method.
+ """
+ def close(self):
+ if not self.closed:
+ self.write = _write_ValueError
+ StringIO.close(self)
+
+ def seek(self, pos, mode=0):
+ raise RuntimeError("FasterStringIO.seek() not allowed")
+
+ def write(self, s):
+ #assert self.pos == self.len
+ self.buflist.append(s)
+ self.len = self.pos = self.pos + len(s)
+
+
+def _write_ValueError(s):
+ raise ValueError, "I/O operation on closed file"
Modified: Zope3/branches/Zope-3.1/src/zope/tal/tests/input/acme_template.pt
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/tests/input/acme_template.pt 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/tests/input/acme_template.pt 2005-08-19 23:01:37 UTC (rev 38014)
@@ -1,6 +1,6 @@
<!-- This is ACME's generic look and feel, which is based on
PNOME's look and feel. -->
-<html metal:use-macro="pnome_macros_page" metal:define-macro="page">
+<html metal:extend-macro="pnome_macros_page" metal:define-macro="page">
<head>
<title metal:fill-slot="title">ACME Look and Feel</title>
</head>
Modified: Zope3/branches/Zope-3.1/src/zope/tal/tests/input/test_metal9.html
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/tests/input/test_metal9.html 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/tests/input/test_metal9.html 2005-08-19 23:01:37 UTC (rev 38014)
@@ -4,7 +4,7 @@
</span>
</div>
-<div metal:define-macro="macro2" metal:use-macro="macro1" i18n:domain="zope">
+<div metal:define-macro="macro2" metal:extend-macro="macro1" i18n:domain="zope">
<span metal:fill-slot="slot1">
Macro 2's slot 1 decoration
<span metal:define-slot="slot1">
Modified: Zope3/branches/Zope-3.1/src/zope/tal/tests/output/test_metal9.html
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/tests/output/test_metal9.html 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/tests/output/test_metal9.html 2005-08-19 23:01:37 UTC (rev 38014)
@@ -24,6 +24,9 @@
<div metal:use-macro="macro2" i18n:domain="zope">
<span metal:fill-slot="slot1">
+Macro 2's slot 1 decoration
+<span metal:fill-slot="slot1">
Custom slot1
</span>
+</span>
</div>
Modified: Zope3/branches/Zope-3.1/src/zope/tal/tests/test_htmltalparser.py
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/tests/test_htmltalparser.py 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/tests/test_htmltalparser.py 2005-08-19 23:01:37 UTC (rev 38014)
@@ -557,6 +557,18 @@
self._should_error("<p metal:foobar='x' />", exc)
self._should_error("<p metal:define-macro='x'>", exc)
+ def test_extend_macro_errors(self):
+ exc = taldefs.METALError
+ # extend-macro requires define-macro:
+ self._should_error("<p metal:extend-macro='x'>xxx</p>", exc)
+ # extend-macro prevents use-macro:
+ self._should_error("<p metal:extend-macro='x'"
+ " metal:use-macro='x'"
+ " metal:define-macro='y'>xxx</p>", exc)
+ # use-macro doesn't co-exist with define-macro:
+ self._should_error("<p metal:use-macro='x'"
+ " metal:define-macro='y'>xxx</p>", exc)
+
#
# I18N test cases
#
Modified: Zope3/branches/Zope-3.1/src/zope/tal/tests/test_talinterpreter.py
===================================================================
--- Zope3/branches/Zope-3.1/src/zope/tal/tests/test_talinterpreter.py 2005-08-19 22:29:21 UTC (rev 38013)
+++ Zope3/branches/Zope-3.1/src/zope/tal/tests/test_talinterpreter.py 2005-08-19 23:01:37 UTC (rev 38014)
@@ -99,30 +99,6 @@
f.close()
return data
- def test_acme_extends_pnome(self):
- # ACME inc. has a document_list template that uses ACME's
- # common look and feel. ACME's look and feel is based on the
- # work of PNOME, Inc., a company that creates Pretty Nice
- # Object Management Environments for Zope. This test verifies
- # that document_list works as expected.
- result = StringIO()
- interpreter = TALInterpreter(
- self.doclist_program, {}, self.engine, stream=result)
- interpreter()
- actual = result.getvalue().strip()
- expected = self._read(('output', 'document_list.html')).strip()
- self.assertEqual(actual, expected)
-
- def test_acme_extends_pnome_source(self):
- # Render METAL attributes in document_list
- result = StringIO()
- interpreter = TALInterpreter(
- self.doclist_program, {}, self.engine, stream=result, tal=False)
- interpreter()
- actual = result.getvalue().strip()
- expected = self._read(('output', 'document_list_source.html')).strip()
- self.assertEqual(actual, expected)
-
def test_preview_acme_template(self):
# An ACME designer is previewing the ACME design. For the
# purposes of this use case, extending a macro should act the
More information about the Zope3-Checkins
mailing list