[Zope3-checkins] SVN: Zope3/trunk/ Templates now allow metal:define-macro and metal:use-macro in the same tag.

Shane Hathaway shane at zope.com
Mon Nov 22 11:38:57 EST 2004


Log message for revision 28489:
  Templates now allow metal:define-macro and metal:use-macro in the same tag.
  
  This allows a macro to extend another macro, which is particularly 
  useful for writing a skin that customizes another skin.
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/tal/taldefs.py
  U   Zope3/trunk/src/zope/tal/talgenerator.py
  U   Zope3/trunk/src/zope/tal/talinterpreter.py
  A   Zope3/trunk/src/zope/tal/tests/input/acme_template.pt
  A   Zope3/trunk/src/zope/tal/tests/input/document_list.pt
  A   Zope3/trunk/src/zope/tal/tests/input/pnome_template.pt
  U   Zope3/trunk/src/zope/tal/tests/input/test_metal7.html
  A   Zope3/trunk/src/zope/tal/tests/output/acme_template.html
  A   Zope3/trunk/src/zope/tal/tests/output/acme_template_source.html
  A   Zope3/trunk/src/zope/tal/tests/output/document_list.html
  A   Zope3/trunk/src/zope/tal/tests/output/document_list_source.html
  U   Zope3/trunk/src/zope/tal/tests/output/test_metal1.html
  U   Zope3/trunk/src/zope/tal/tests/test_talinterpreter.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/doc/CHANGES.txt	2004-11-22 16:38:56 UTC (rev 28489)
@@ -10,15 +10,20 @@
 
     New features
 
+      - Page templates now allow metal:define-macro and
+        metal:use-macro in the same tag.  This allows a macro to
+        extend another macro, which is particularly useful for writing
+        a skin that customizes another skin.
+
       - Added pagelet concept. Pagelets are responsible for a piece 
         of content in a view. They can be used to render additionaly 
         provided information into a pagetemplate.
 
-        + Added tal expression 'pagelet'
+        + Added tales expression 'pagelet'
 
-        + Added tal expression 'pagelets'
+        + Added tales expression 'pagelets'
 
-        + Added tal expression 'pagedata'
+        + Added tales expression 'pagedata'
         
       - Added pagelet chooser for store pagelet name in the annotation
         of a object.

Modified: Zope3/trunk/src/zope/tal/taldefs.py
===================================================================
--- Zope3/trunk/src/zope/tal/taldefs.py	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/taldefs.py	2004-11-22 16:38:56 UTC (rev 28489)
@@ -19,7 +19,7 @@
 from zope.tal.interfaces import ITALExpressionErrorInfo
 from zope.interface import implements
 
-TAL_VERSION = "1.5"
+TAL_VERSION = "1.5.1"
 
 XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
 XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations

Modified: Zope3/trunk/src/zope/tal/talgenerator.py
===================================================================
--- Zope3/trunk/src/zope/tal/talgenerator.py	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/talgenerator.py	2004-11-22 16:38:56 UTC (rev 28489)
@@ -387,6 +387,14 @@
         self.inMacroUse = 0
         self.emit("useMacro", expr, cexpr, self.popSlots(), program)
 
+    def emitExtendMacro(self, defineName, useExpr):
+        cexpr = self.compileExpression(useExpr)
+        program = self.popProgram()
+        self.inMacroUse = 0
+        self.emit("extendMacro", useExpr, cexpr, self.popSlots(), program,
+                  defineName)
+        self.emitDefineMacro(defineName)
+
     def emitDefineSlot(self, slotName):
         program = self.popProgram()
         slotName = slotName.strip()
@@ -538,10 +546,12 @@
             raise I18NError("i18n:data must be accompanied by i18n:translate",
                             position)
 
-        if len(metaldict) > 1 and (defineMacro or useMacro):
-            raise METALError("define-macro and use-macro cannot be used "
-                             "together or with define-slot or fill-slot",
-                             position)
+        if defineMacro or useMacro:
+            if fillSlot or defineSlot:
+                raise METALError(
+                    "define-slot and fill-slot cannot be used with "
+                    "define-macro or use-macro", position)
+
         if replace:
             if content:
                 raise TALError(
@@ -827,10 +837,13 @@
             self.emitDefineSlot(defineSlot)
         if fillSlot:
             self.emitFillSlot(fillSlot)
-        if useMacro:
-            self.emitUseMacro(useMacro)
-        if defineMacro:
-            self.emitDefineMacro(defineMacro)
+        if useMacro or defineMacro:
+            if useMacro and defineMacro:
+                self.emitExtendMacro(defineMacro, useMacro)
+            elif useMacro:
+                self.emitUseMacro(useMacro)
+            elif defineMacro:
+                self.emitDefineMacro(defineMacro)
         if useMacro or defineSlot:
             # generate a source annotation after define-slot or use-macro
             # because the source file might have changed

Modified: Zope3/trunk/src/zope/tal/talinterpreter.py
===================================================================
--- Zope3/trunk/src/zope/tal/talinterpreter.py	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/talinterpreter.py	2004-11-22 16:38:56 UTC (rev 28489)
@@ -181,7 +181,10 @@
         self.html = 0
         self.endsep = "/>"
         self.endlen = len(self.endsep)
+        # macroStack contains:
+        # [(macroName, slots, definingName, extending, entering, i18ncontext)]
         self.macroStack = []
+        self.inUseDirective = False
         self.position = None, None  # (lineno, offset)
         self.col = 0
         self.level = 0
@@ -220,11 +223,12 @@
         assert self.level == level
         assert self.scopeLevel == scopeLevel
 
-    def pushMacro(self, macroName, slots, entering=1):
+    def pushMacro(self, macroName, slots, definingName, extending, entering=1):
         if len(self.macroStack) >= self.stackLimit:
             raise METALError("macro nesting limit (%d) exceeded "
                              "by %s" % (self.stackLimit, `macroName`))
-        self.macroStack.append([macroName, slots, entering, self.i18nContext])
+        self.macroStack.append([macroName, slots, definingName, extending,
+                                entering, self.i18nContext])
 
     def popMacro(self):
         return self.macroStack.pop()
@@ -361,28 +365,29 @@
         try:
             for item in attrList:
                 if _len(item) == 2:
-                    name, s = item
+                    rendered = item[1:]
                 else:
                     # item[2] is the 'action' field:
                     if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
                         if not self.showtal:
                             continue
-                        ok, name, s = self.attrAction(item)
+                        rendered = self.attrAction(item)
                     else:
-                        ok, name, s = attrAction(self, item)
-                    if not ok:
+                        rendered = attrAction(self, item)
+                    if not rendered:
                         continue
-                slen = _len(s)
-                if (wrap and
-                    col >= align and
-                    col + 1 + slen > wrap):
-                    append("\n")
-                    append(" "*align)
-                    col = align + slen
-                else:
-                    append(" ")
-                    col = col + 1 + slen
-                append(s)
+                for s in rendered:
+                    slen = _len(s)
+                    if (wrap and
+                        col >= align and
+                        col + 1 + slen > wrap):
+                        append("\n")
+                        append(" "*align)
+                        col = align + slen
+                    else:
+                        append(" ")
+                        col = col + 1 + slen
+                    append(s)
             append(end)
             col = col + endlen
         finally:
@@ -393,33 +398,47 @@
     def attrAction(self, item):
         name, value, action = item[:3]
         if action == 'insert':
-            return 0, name, value
+            return ()
         macs = self.macroStack
         if action == 'metal' and self.metal and macs:
-            if len(macs) > 1 or not macs[-1][2]:
-                # Drop all METAL attributes at a use-depth above one.
-                return 0, name, value
+            # Drop all METAL attributes at a use-depth beyond the first
+            # use-macro and its extensions
+            if len(macs) > 1:
+                for macro in macs[1:]:
+                    if macro is None:
+                        return ()
+                    extending = macro[3]
+                    if not extending:
+                        return ()
+            if not macs[-1][4]:
+                return ()
             # Clear 'entering' flag
-            macs[-1][2] = 0
+            macs[-1][4] = 0
             # 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.
-                name = prefix + "use-macro"
-                value = macs[-1][0] # Macro name
+                useName = macs[0][0]
+                defName = macs[0][2]
+                res = []
+                if defName:
+                    res.append('%sdefine-macro=%s' % (prefix, quote(defName)))
+                if useName:
+                    res.append('%suse-macro=%s' % (prefix, quote(useName)))
+                return res
             elif suffix == "define-slot":
                 name = prefix + "fill-slot"
             elif suffix == "fill-slot":
                 pass
             else:
-                return 0, name, value
+                return ()
 
         if value is None:
             value = name
         else:
             value = "%s=%s" % (name, quote(value))
-        return 1, name, value
+        return [value]
 
     def attrAction_tal(self, item):
         name, value, action = item[:3]
@@ -451,8 +470,9 @@
                     value = translated
             if value is None:
                 value = name
-            value = "%s=%s" % (name, quote(value))
-        return ok, name, value
+            return ["%s=%s" % (name, quote(value))]
+        else:
+            return ()
     bytecode_handlers["<attrAction>"] = attrAction
 
     def no_tag(self, start, program):
@@ -751,8 +771,10 @@
 
     def do_defineMacro(self, (macroName, macro)):
         macs = self.macroStack
+        wasInUse = self.inUseDirective
+        self.inUseDirective = False
         if len(macs) == 1:
-            entering = macs[-1][2]
+            entering = macs[-1][4]
             if not entering:
                 macs.append(None)
                 self.interpret(macro)
@@ -760,9 +782,11 @@
                 macs.pop()
                 return
         self.interpret(macro)
+        self.inUseDirective = wasInUse
     bytecode_handlers["defineMacro"] = do_defineMacro
 
-    def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
+    def do_useMacro(self, (macroName, macroExpr, compiledSlots, block),
+                    definingName=None, extending=False):
         if not self.metal:
             self.interpret(block)
             return
@@ -778,14 +802,18 @@
             if mode != (self.html and "html" or "xml"):
                 raise METALError("macro %s has incompatible mode %s" %
                                  (`macroName`, `mode`), self.position)
-        self.pushMacro(macroName, compiledSlots)
+        self.pushMacro(macroName, compiledSlots, definingName, extending)
 
         # We want 'macroname' name to be always available as a variable
         outer = self.engine.getValue('macroname')
         self.engine.setLocal('macroname', macroName.split('/')[-1])
 
         prev_source = self.sourceFile
+        wasInUse = self.inUseDirective
+        self.inUseDirective = True
         self.interpret(macro)
+        self.inUseDirective = wasInUse
+
         if self.sourceFile != prev_source:
             self.engine.setSourceFile(prev_source)
             self.sourceFile = prev_source
@@ -794,6 +822,19 @@
         self.engine.setLocal('macroname', outer)
     bytecode_handlers["useMacro"] = do_useMacro
 
+    def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block,
+                              definingName)):
+        # 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
+        self.do_useMacro((macroName, macroExpr, compiledSlots, block),
+                         definingName, extending)
+    bytecode_handlers["extendMacro"] = do_extendMacro
+
     def do_fillSlot(self, (slotName, block)):
         # This is only executed if the enclosing 'use-macro' evaluates
         # to 'default'.
@@ -806,17 +847,41 @@
             return
         macs = self.macroStack
         if macs and macs[-1] is not None:
-            macroName, slots = self.popMacro()[:2]
-            slot = slots.get(slotName)
+            len_macs = len(macs)
+            # Measure the extension depth of this use-macro
+            depth = 1
+            while depth < len_macs:
+                if macs[-depth][3]:
+                    depth += 1
+                else:
+                    break
+            # Search for a slot filler from the most specific to the
+            # 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)
+                if slot is not None:
+                    break
+                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
+                # render the slot filler.
+                chopped = macs[i:]
+                del macs[i:]
                 prev_source = self.sourceFile
                 self.interpret(slot)
                 if self.sourceFile != prev_source:
                     self.engine.setSourceFile(prev_source)
                     self.sourceFile = prev_source
-                self.pushMacro(macroName, slots, entering=0)
+                # Restore the stack entries.
+                for mac in chopped:
+                    mac[4] = 0  # Not entering
+                macs.extend(chopped)
                 return
-            self.pushMacro(macroName, slots)
             # Falling out of the 'if' allows the macro to be interpreted.
         self.interpret(block)
     bytecode_handlers["defineSlot"] = do_defineSlot

Added: Zope3/trunk/src/zope/tal/tests/input/acme_template.pt
===================================================================
--- Zope3/trunk/src/zope/tal/tests/input/acme_template.pt	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/input/acme_template.pt	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,15 @@
+<!-- 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">
+<head>
+<title metal:fill-slot="title">ACME Look and Feel</title>
+</head>
+<body>
+<div metal:fill-slot="page-footer">
+Copyright 2004 Acme Inc.
+<div metal:define-slot="disclaimer">
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>

Added: Zope3/trunk/src/zope/tal/tests/input/document_list.pt
===================================================================
--- Zope3/trunk/src/zope/tal/tests/input/document_list.pt	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/input/document_list.pt	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,21 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html metal:use-macro="acme_macros_page">
+<head>
+<title metal:fill-slot="title">Acme Document List</title>
+<style metal:fill-slot="local-styles" type="text/css">
+  body { background-color: white; }
+</style>
+</head>
+<body>
+<div metal:fill-slot="content">
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div metal:fill-slot="disclaimer">
+This document list is classified.
+</div>
+</body>
+</html>

Added: Zope3/trunk/src/zope/tal/tests/input/pnome_template.pt
===================================================================
--- Zope3/trunk/src/zope/tal/tests/input/pnome_template.pt	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/input/pnome_template.pt	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,23 @@
+<!-- fakeplone is a fictional user interface created by a large,
+well-focused team of graphics designers -->
+<html metal:define-macro="page">
+<head>
+<title metal:define-slot="title">Title here</title>
+<metal:block define-slot="local-styles">
+</metal:block>
+</head>
+<body>
+<div>
+  <div metal:define-slot="annoying-quote">
+  "The early bird gets the worm, but the second mouse gets the cheese."
+  </div>
+  <a href="#">Preferences...</a>
+</div>
+<div metal:define-slot="content">
+  Content here
+</div>
+<div metal:define-slot="page-footer">
+  page footer
+</div>
+</body>
+</html>

Modified: Zope3/trunk/src/zope/tal/tests/input/test_metal7.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/input/test_metal7.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/input/test_metal7.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -1,4 +1,6 @@
 <html metal:define-macro="page" i18n:domain="zope">
     <x metal:define-slot="title" />
 </html>
-<html metal:use-macro="page" />
\ No newline at end of file
+<html metal:use-macro="page">
+    <x metal:fill-slot="title" />
+</html>

Added: Zope3/trunk/src/zope/tal/tests/output/acme_template.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/output/acme_template.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/output/acme_template.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,26 @@
+<!-- This is ACME's generic look and feel, which is based on
+PNOME's look and feel. -->
+<html>
+<head>
+<title>ACME Look and Feel</title>
+
+
+</head>
+<body>
+<div>
+  <div>
+  "The early bird gets the worm, but the second mouse gets the cheese."
+  </div>
+  <a href="#">Preferences...</a>
+</div>
+<div>
+  Content here
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div>
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>

Added: Zope3/trunk/src/zope/tal/tests/output/acme_template_source.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/output/acme_template_source.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/output/acme_template_source.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,27 @@
+<!-- This is ACME's generic look and feel, which is based on
+PNOME's look and feel. -->
+<html metal:define-macro="page"
+      metal:use-macro="pnome_macros_page">
+<head>
+<title metal:fill-slot="title">ACME Look and Feel</title>
+<metal:block>
+</metal:block>
+</head>
+<body>
+<div>
+  <div>
+  "The early bird gets the worm, but the second mouse gets the cheese."
+  </div>
+  <a href="#">Preferences...</a>
+</div>
+<div>
+  Content here
+</div>
+<div metal:fill-slot="page-footer">
+Copyright 2004 Acme Inc.
+<div metal:define-slot="disclaimer">
+Standard disclaimers apply.
+</div>
+</div>
+</body>
+</html>

Added: Zope3/trunk/src/zope/tal/tests/output/document_list.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/output/document_list.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/output/document_list.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,30 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html>
+<head>
+<title>Acme Document List</title>
+<style type="text/css">
+  body { background-color: white; }
+</style>
+</head>
+<body>
+<div>
+  <div>
+  "The early bird gets the worm, but the second mouse gets the cheese."
+  </div>
+  <a href="#">Preferences...</a>
+</div>
+<div>
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div>
+This document list is classified.
+</div>
+</div>
+</body>
+</html>

Added: Zope3/trunk/src/zope/tal/tests/output/document_list_source.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/output/document_list_source.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/output/document_list_source.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -0,0 +1,30 @@
+<!-- ACME's document_list uses the ACME look and feel -->
+<html metal:use-macro="acme_macros_page">
+<head>
+<title metal:fill-slot="title">Acme Document List</title>
+<style metal:fill-slot="local-styles" type="text/css">
+  body { background-color: white; }
+</style>
+</head>
+<body>
+<div>
+  <div>
+  "The early bird gets the worm, but the second mouse gets the cheese."
+  </div>
+  <a href="#">Preferences...</a>
+</div>
+<div metal:fill-slot="content">
+<h1>Documents</h1>
+<ul>
+<li>Rocket Science for Dummies</li>
+<li>Birds for the Gourmet Chef</li>
+</ul>
+</div>
+<div>
+Copyright 2004 Acme Inc.
+<div metal:fill-slot="disclaimer">
+This document list is classified.
+</div>
+</div>
+</body>
+</html>

Modified: Zope3/trunk/src/zope/tal/tests/output/test_metal1.html
===================================================================
--- Zope3/trunk/src/zope/tal/tests/output/test_metal1.html	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/output/test_metal1.html	2004-11-22 16:38:56 UTC (rev 28489)
@@ -22,7 +22,7 @@
 
 <span metal:use-macro="OUTER2">
   AAA
-  <xxx metal:fill-slot="OUTERSLOT">
+  <xxx>
   <span>INNER</span>
   </xxx>
   BBB
@@ -48,7 +48,7 @@
 
 <span metal:use-macro="OUTER3">
   AAA
-  <xxx metal:fill-slot="OUTERSLOT">
+  <xxx>
   <span>INNER
     <xxx>INNERSLOT</xxx>
   </span>
@@ -63,7 +63,7 @@
 </span>
 
 <span metal:use-macro="INNER3">INNER
-    <xxx metal:fill-slot="INNERSLOT">INNERSLOT</xxx>
+    <xxx>INNERSLOT</xxx>
   </span>
 
 <span metal:use-macro="INNER3">INNER

Modified: Zope3/trunk/src/zope/tal/tests/test_talinterpreter.py
===================================================================
--- Zope3/trunk/src/zope/tal/tests/test_talinterpreter.py	2004-11-22 10:15:51 UTC (rev 28488)
+++ Zope3/trunk/src/zope/tal/tests/test_talinterpreter.py	2004-11-22 16:38:56 UTC (rev 28489)
@@ -16,12 +16,13 @@
 
 $Id$
 """
+import os
 import sys
 import unittest
 
 from StringIO import StringIO
 
-from zope.tal.taldefs import METALError, I18NError
+from zope.tal.taldefs import METALError, I18NError, TAL_VERSION
 from zope.tal.htmltalparser import HTMLTALParser
 from zope.tal.talinterpreter import TALInterpreter
 from zope.tal.dummyengine import DummyEngine, DummyTranslationDomain
@@ -74,6 +75,76 @@
         self.assertEqual(output, '<p><div>foo</div></p>')
 
 
+class MacroExtendTestCase(TestCaseBase):
+
+    def setUp(self):
+        s = self._read(('input', 'pnome_template.pt'))
+        self.pnome_program, pnome_macros = self._compile(s)
+        s = self._read(('input', 'acme_template.pt'))
+        self.acme_program, acme_macros = self._compile(s)
+        s = self._read(('input', 'document_list.pt'))
+        self.doclist_program, doclist_macros = self._compile(s)
+        macros = {
+            'pnome_macros_page': pnome_macros['page'],
+            'acme_macros_page': acme_macros['page'],
+            }
+        self.engine = DummyEngine(macros)
+
+    def _read(self, path):
+        dir = os.path.dirname(__file__)
+        fn = os.path.join(dir, *path)
+        f = open(fn)
+        data = f.read()
+        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
+        # same as using a macro.
+        result = StringIO()
+        interpreter = TALInterpreter(
+            self.acme_program, {}, self.engine, stream=result)
+        interpreter()
+        actual = result.getvalue().strip()
+        expected = self._read(('output', 'acme_template.html')).strip()
+        self.assertEqual(actual, expected)
+
+    def test_preview_acme_template_source(self):
+        # Render METAL attributes in acme_template
+        result = StringIO()
+        interpreter = TALInterpreter(
+            self.acme_program, {}, self.engine, stream=result, tal=False)
+        interpreter()
+        actual = result.getvalue().strip()
+        expected = self._read(('output', 'acme_template_source.html')).strip()
+        self.assertEqual(actual, expected)
+        
+
 class I18NCornerTestCase(TestCaseBase):
 
     def setUp(self):
@@ -155,7 +226,7 @@
                     '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n')
 
     def test_translate_static_text_as_dynamic_from_bytecode(self):
-        program =  [('version', '1.5'),
+        program =  [('version', TAL_VERSION),
  ('mode', 'html'),
 ('setPosition', (1, 0)),
 ('beginScope', {'i18n:translate': ''}),
@@ -441,6 +512,7 @@
 def test_suite():
     suite = unittest.makeSuite(I18NErrorsTestCase)
     suite.addTest(unittest.makeSuite(MacroErrorsTestCase))
+    suite.addTest(unittest.makeSuite(MacroExtendTestCase))
     suite.addTest(unittest.makeSuite(OutputPresentationTestCase))
     suite.addTest(unittest.makeSuite(ScriptTestCase))
     suite.addTest(unittest.makeSuite(I18NCornerTestCase))



More information about the Zope3-Checkins mailing list