[Checkins] SVN: z3c.pt/trunk/ Added Zope METAL-support.

Malthe Borch mborch at gmail.com
Sat Aug 9 12:18:54 EDT 2008


Log message for revision 89579:
  Added Zope METAL-support.

Changed:
  U   z3c.pt/trunk/CHANGES.txt
  U   z3c.pt/trunk/README.txt
  U   z3c.pt/trunk/setup.py
  U   z3c.pt/trunk/src/z3c/pt/config.py
  U   z3c.pt/trunk/src/z3c/pt/generation.py
  A   z3c.pt/trunk/src/z3c/pt/macro.py
  U   z3c.pt/trunk/src/z3c/pt/template.py
  U   z3c.pt/trunk/src/z3c/pt/template.txt
  U   z3c.pt/trunk/src/z3c/pt/testing.py
  U   z3c.pt/trunk/src/z3c/pt/translation.py
  U   z3c.pt/trunk/src/z3c/pt/translation.txt
  U   z3c.pt/trunk/src/z3c/pt/utils.py

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/CHANGES.txt	2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,9 +1,12 @@
 Changelog
 ---------
 
-Version 0.9.x
+Version 1.0dev
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+- Added support for METAL.
+  [malthe]
+
 - Add a TemplateLoader class to have a convenient method to instantiate
   templates. This is similar to the template loaders from other template
   toolkits and makes integration with Pylons a lot simpler.

Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/README.txt	2008-08-09 16:18:54 UTC (rev 89579)
@@ -5,6 +5,7 @@
 following dialects of the attribute template language:
 
 * Zope TAL
+* Zope METAL
 * Zope i18n
 * Genshi
 
@@ -19,9 +20,7 @@
 * Templates are serialized and compiled into Python bytecode
 * Pluggable expression implementation
 
-Note: Zope's METAL macro language is not supported.
 
-
 Usage
 -----
 

Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/setup.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '0.9dev'
+version = '1.0dev'
 
 setup(name='z3c.pt',
       version=version,

Modified: z3c.pt/trunk/src/z3c/pt/config.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/config.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/config.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -12,8 +12,9 @@
 
 XML_NS = "http://www.w3.org/1999/xhtml"
 TAL_NS = "http://xml.zope.org/namespaces/tal"
+METAL_NS = "http://xml.zope.org/namespaces/metal" 
 I18N_NS = "http://xml.zope.org/namespaces/i18n"
 PY_NS = "http://genshi.edgewall.org"
-NS_MAP = dict(py=PY_NS, tal=PY_NS)
+NS_MAP = dict(py=PY_NS, tal=TAL_NS, metal=METAL_NS)
             
         

Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -9,21 +9,27 @@
 import z3c.pt.generation
 from z3c.pt.config import DISABLE_I18N
 
-wrapper = """\
-def render(%starget_language=None):
+template_wrapper = """\
+def render(%(args)s%(extra)starget_language=None):
 \tglobal generation
 
-\t(_out, _write) = generation.initialize_stream()
-\t(_attributes, repeat) = generation.initialize_tal()
-\t(_domain, _negotiate, _translate) = generation.initialize_i18n()
+\t_out, _write = generation.initialize_stream()
+\t_attributes, repeat = generation.initialize_tal()
+\t_domain, _negotiate, _translate = generation.initialize_i18n()
 \t_marker = generation.initialize_helpers()
 \t_path = generation.initialize_traversal()
 
 \t_target_language = _negotiate(_context, target_language)
-%s
+%(code)s
 \treturn _out.getvalue()
 """
 
+macro_wrapper = """\
+def render(%(kwargs)s%(extra)s):
+\tglobal generation
+%(code)s
+"""
+
 def _fake_negotiate(context, target_language):
     return target_language
 
@@ -62,32 +68,42 @@
     return expressions.PathTranslation.traverse
 
 class Generator(object):
-    def __init__(self, params):
-        self.params = tuple(params)
+    def __init__(self, params, wrapper):
+        self.params = list(params)
+        self.wrapper = wrapper
         self.stream = CodeIO(indentation=1, indentation_string="\t")
 
         # initialize variable scope
-        self.stream.scope.append(set(params + ['_out', '_write']))
+        self.stream.scope.append(set(('_out', '_write') + tuple(params)))
 
     def __call__(self):
-        # prepare template arguments
-        args = self.params
-        
-        # we need to ensure we have _context for the i18n handling in
-        # the arguments. the default template implementations pass
-        # this in explicitly.
-        if '_context' not in args:
-            args = args + ('_context=None', )
-        args = ', '.join(args)
+        params = self.params
+        extra = ''
+
+        # prepare args
+        args = ', '.join(params)
         if args:
             args += ', '
 
-        # pass selectors
+        # prepare kwargs
+        kwargs = ', '.join("%s=None" % param for param in params)
+        if kwargs:
+            kwargs += ', '
+            
+        # prepare selectors
         for selector in self.stream.selectors:
-            args += '%s=None, ' % selector
+            extra += '%s=None, ' % selector
 
+        # we need to ensure we have _context for the i18n handling in
+        # the arguments. the default template implementations pass
+        # this in explicitly.
+        if '_context' not in params:
+            extra += '_context=None, '
+
         code = self.stream.getvalue()
-        return wrapper % (args, code), {'generation': z3c.pt.generation}
+        return self.wrapper % dict(
+            args=args, kwargs=kwargs, extra=extra, code=code), \
+            {'generation': z3c.pt.generation}
 
 class BufferIO(list):
     write = list.append

Added: z3c.pt/trunk/src/z3c/pt/macro.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/macro.py	                        (rev 0)
+++ z3c.pt/trunk/src/z3c/pt/macro.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -0,0 +1,8 @@
+class Macros(object):
+    def __init__(self, render):
+        self.render = render
+
+    def __getitem__(self, name):
+        def render(**kwargs):
+            return self.render(macro=name, **kwargs)
+        return render

Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/template.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,5 +1,6 @@
 import os
 import sys
+import macro
 import codegen
 import traceback
 
@@ -10,6 +11,7 @@
 class BaseTemplate(object):
 
     registry = {}
+    cachedir = None
     default_expression = 'python'
 
     def __init__(self, body, default_expression=None):
@@ -24,38 +26,46 @@
     def translate(self):
         return NotImplementedError("Must be implemented by subclass.")
 
-    def source_write(self):
-        # Hook for writing out the source code to the file system
-        return
+    @property
+    def macros(self):
+        return macro.Macros(self.render)
 
-    def cook(self, params):
+    def cook(self, params, macro=None):
         generator = self.translate(
-            self.body, params=params, default_expression=self.default_expression)
+            self.body, macro=macro, params=params,
+            default_expression=self.default_expression)
         
         source, _globals = generator()
         
         suite = codegen.Suite(source)
 
+        if self.cachedir:
+            self.registry.store(params, suite.code)
+
         self.source = source
-        self.source_write()
         self.selectors = generator.stream.selectors
         self.annotations = generator.stream.annotations
         
         _globals.update(suite._globals)
-        _locals = {}
+        return self.execute(suite.code, _globals)
 
-        exec suite.code in _globals, _locals
-
-        return _locals['render']
-
-    def render(self, **kwargs):
-        # a ''.join of a dict uses only the keys
-        signature = self.signature + hash(''.join(kwargs))
-
+    def cook_check(self, macro, params):
+        signature = self.signature, macro, params
         template = self.registry.get(signature, None)
         if template is None:
-            self.registry[signature] = template = self.cook(kwargs.keys())
+            template = self.cook(params, macro=macro)
+            self.registry[signature] = template
+            
+        return template
+    
+    def execute(self, code, _globals):
+        _locals = {}
+        exec code in _globals, _locals
+        return _locals['render']
 
+    def render(self, macro=None, **kwargs):
+        template = self.cook_check(macro, tuple(kwargs))
+        
         # pass in selectors
         kwargs.update(self.selectors)
 
@@ -101,8 +111,8 @@
 
 class BaseTemplateFile(BaseTemplate):
 
-    def __init__(self, filename, auto_reload=False, cachedir=None):
-        BaseTemplate.__init__(self, None)
+    def __init__(self, filename, auto_reload=False, cachedir=None, **kwargs):
+        BaseTemplate.__init__(self, None, **kwargs)
         self.auto_reload = auto_reload
         self.cachedir = cachedir
 
@@ -133,6 +143,8 @@
         else:
             self.registry = {}
 
+        self.read()
+        
     def _get_filename(self):
         return getattr(self, '_filename', None)
 
@@ -142,16 +154,21 @@
 
     filename = property(_get_filename, _set_filename)
 
-    @property
-    def source_filename(self):
-        return "%s.source" % self.filename
+    def _get_source(self):
+        return self._source
 
-    def source_write(self):
-        if DEBUG_MODE and self.source_filename:
-            fs = open(self.source_filename, 'w')
-            fs.write(self.source)
+    def _set_source(self, source):
+        self._source = source
+
+        # write source to disk
+        filename = "%s.source" % self.filename
+        if DEBUG_MODE:
+            fs = open(filename, 'w')
+            fs.write(source)
             fs.close()
 
+    source = property(_get_source, _set_source)
+    
     def read(self):
         fd = open(self.filename, 'r')
         self.body = body = fd.read()
@@ -159,60 +176,21 @@
         self.signature = hash(body)
         self._v_last_read = self.mtime()
 
-    def cook(self, params):
-        if self.body is None:
-            self.read()
-
-        generator = self.translate(
-            self.body, params=params, default_expression=self.default_expression)
-        
-        source, _globals = generator()
-        
-        suite = codegen.Suite(source)
-
-        self.source = source
-        self.source_write()
-        self.annotations = generator.stream.annotations
-        
-        _globals.update(suite._globals)
-        if self.cachedir:
-            self.registry.store(params, suite.code)
-
-        return self.execute(suite.code, _globals)
-
     def execute(self, code, _globals=None):
         # TODO: This is evil. We need a better way to get all the globals
         # independent from the generation step
         if _globals is None:
             _globals = codegen.Lookup.globals()
             _globals['generation'] = z3c.pt.generation
-        _locals = {}
-        exec code in _globals, _locals
-        return _locals['render']
 
-    def render(self, **kwargs):
-        if self._cook_check():
+        return BaseTemplate.execute(self, code, _globals)
+
+    def cook_check(self, *args):
+        if self.auto_reload and self._v_last_read != self.mtime():
             self.read()
 
-        signature = hash(''.join(kwargs))
-        template = self.registry.get(signature, None)
-        if template is None:
-            self.registry[signature] = template = self.cook(kwargs.keys())
+        return BaseTemplate.cook_check(self, *args)
 
-        if PROD_MODE:
-            return template(**kwargs)
-
-        return self.safe_render(template, **kwargs)
-
-    def _cook_check(self):
-        if self._v_last_read and not self.auto_reload:
-            return False
-
-        if self.mtime() == self._v_last_read:
-            return False
-
-        return True
-
     def mtime(self):
         try:
             return os.path.getmtime(self.filename)

Modified: z3c.pt/trunk/src/z3c/pt/template.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-09 16:18:54 UTC (rev 89579)
@@ -121,6 +121,36 @@
   <BLANKLINE>
   </div>
 
+metal:define-macro, metal:use-macro
+
+  >>> template1 = PageTemplate("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div metal:define-macro="greeting">
+  ...     Hello, ${name}!
+  ...   </div>
+  ... </div>""")
+
+  >>> template2 = PageTemplate("""\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div tal:define="name 'world'">
+  ...     <div metal:use-macro="template1.macros['greeting']" />
+  ...   </div>  
+  ... </div>""")
+
+  >>> print template2(template1=template1)
+  <div>
+    <div>
+      <div>
+      Hello, world!
+    </div>
+  <BLANKLINE>
+    </div>  
+  </div>
+  
 Error handling
 --------------
 

Modified: z3c.pt/trunk/src/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -1,4 +1,33 @@
 import expressions
+import macro
 
 def pyexp(string):
     return expressions.PythonTranslation.expression(string)
+
+def cook(generator, **kwargs):
+    source, _globals = generator()
+    _locals = {}
+    exec source in _globals, _locals
+    return _locals['render']
+
+def render(body, translator, **kwargs):
+    generator = translator(body, params=sorted(kwargs.keys()))
+    return _render(generator, **kwargs)
+
+def _render(generator, **kwargs):
+    cooked = cook(generator, **kwargs)
+    kwargs.update(generator.stream.selectors)
+    return cooked(**kwargs)
+
+class MockTemplate(object):
+    def __init__(self, body, translator):
+        self.body = body
+        self.translator = translator
+
+    @property
+    def macros(self):
+        def render(macro=None, **kwargs):
+            generator = self.translator(
+                self.body, macro=macro, params=kwargs.keys())
+            return _render(generator, **kwargs)
+        return macro.Macros(render)

Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -7,6 +7,7 @@
 import clauses
 import interfaces
 import expressions
+import itertools
 import types
 import utils
 import config
@@ -18,9 +19,9 @@
     compilation at this node, use the ``start`` method, providing a
     code stream object.
     """
+    
+    metal_slot_prefix = '_fill'
 
-    _stream = None
-
     def start(self, stream):
         self._stream = stream
         self.visit()
@@ -38,6 +39,7 @@
 
     def body(self):
         skip = self._replace or self._content or \
+               self.metal_define or self.metal_use or \
                self.i18n_translate is not None
         
         if not skip:
@@ -49,8 +51,9 @@
                     
     def visit(self, skip_macro=True):
         assert self.stream is not None, "Must use ``start`` method."
-        
-        if skip_macro and self.py_def is not None:
+
+        macro = self.py_def or self.metal_define
+        if skip_macro and macro is not None:
             return
 
         for element in self:
@@ -64,9 +67,14 @@
 
     @property
     def stream(self):
-        root = self.getroottree().getroot()
-        return root._stream
-    
+        while self is not None:
+            try:
+                return self._stream
+            except AttributeError:
+                self = self.getparent()
+
+        raise ValueError("Can't locate stream object.")
+        
     @property
     def translator(self):
         while self.tal_default_expression is None:
@@ -209,28 +217,29 @@
             _.append(clauses.Define(
                 "_domain", types.value(repr(self.i18n_domain))))
 
-        # defines
+        # variable definitions
         if self._define is not None:
             for variables, expression in self._define:
                 _.append(clauses.Define(variables, expression))
 
-        # macro
+        # genshi macro
         for element in tuple(self):
             if not isinstance(element, Element):
                 continue
-            
-            py_def = element.py_def
-            if py_def is not None:
+
+            macro = element.py_def
+            if macro is not None:
                 # define macro
                 subclauses = []
-                subclauses.append(clauses.Method("_macro", py_def.args))
+                subclauses.append(clauses.Method(
+                    "_macro", macro.args))
                 subclauses.append(clauses.Visit(element))
                 _.append(clauses.Group(subclauses))
                 
                 # assign to variable
                 _.append(clauses.Define(
-                    py_def.name, types.parts((types.value("_macro"),))))
-                    
+                    macro.name, types.parts((types.value("_macro"),))))
+
         # condition
         if self._condition is not None:
             _.append(clauses.Condition(self._condition))
@@ -245,16 +254,24 @@
             _.append(clauses.Repeat(variables[0], expression))
 
         # tag tail (deferred)
-        if self.tail:
+        if self.tail and not self.metal_fillslot:
             _.append(clauses.Out(self.tail.encode('utf-8'), defer=True))
 
+        # dynamic content and content translation
+        replace = self._replace
+        content = self._content
+
+        if self.metal_defineslot:
+            # check if slot has been filled
+            variable = self.metal_slot_prefix+self.metal_defineslot
+            if variable in itertools.chain(*self.stream.scope):
+                content = types.value(variable)
+            
         # compute dynamic flag
-        dynamic = (self._replace or
-                   self._content or
-                   self.i18n_translate is not None)
-        
+        dynamic = replace or content or self.i18n_translate is not None
+
         # tag
-        if self._replace is None:
+        if replace is None:
             selfclosing = self.text is None and not dynamic and len(self) == 0
             tag = clauses.Tag(self.tag, self._get_attributes(),
                               expression=self.py_attrs, selfclosing=selfclosing)
@@ -262,7 +279,8 @@
             if self._omit:
                 _.append(clauses.Condition(_not(self._omit), [tag],
                                            finalize=False))
-            elif self._omit is not None:
+            elif self._omit is not None or \
+                 self.metal_use or self.metal_fillslot:
                 pass
             else:
                 _.append(tag)
@@ -271,23 +289,47 @@
         if self.text and not dynamic:
             _.append(clauses.Out(self.text.encode('utf-8')))
 
-        # dynamic content and content translation
-        replace = self._replace
-        content = self._content
-
         if replace and content:
             raise ValueError("Can't use replace clause together with "
                              "content clause.")
 
-        expression = replace or content
-        if expression:
+        if replace or content:
             if self.i18n_translate is not None:
                 if self.i18n_translate != "":
                     raise ValueError("Can't use message id with "
                                      "dynamic content translation.")
                 _.append(clauses.Translate())
 
-            _.append(clauses.Write(expression))
+            _.append(clauses.Write(replace or content))
+        elif self.metal_use:
+            # for each fill-slot element, create a new output stream
+            # and save value in a temporary variable
+            kwargs = []
+
+            for element in self.xpath(
+                './/*[@metal:fill-slot]', namespaces={'metal': config.METAL_NS}):
+                variable = self.metal_slot_prefix+element.metal_fillslot
+                kwargs.append((variable, variable))
+                
+                subclauses = []
+                subclauses.append(clauses.Define(
+                    ('_out', '_write'),
+                    types.value('generation.initialize_stream()')))
+                subclauses.append(clauses.Visit(element))
+                subclauses.append(clauses.Assign(
+                    types.value('_out.getvalue()'), variable))
+                _.append(clauses.Group(subclauses))
+                
+            _.append(clauses.Assign(self.metal_use, '_metal'))
+
+            # compute macro function arguments and create argument string
+            arguments = ", ".join(
+                tuple("%s=%s" % (arg, arg) for arg in \
+                      itertools.chain(*self.stream.scope))+
+                tuple("%s=%s" % kwarg for kwarg in kwargs))
+                
+            _.append(clauses.Write(types.value("_metal(%s)" % arguments)))
+            
         else:
             if self.i18n_translate is not None:
                 msgid = self.i18n_translate
@@ -493,6 +535,14 @@
         utils.tal_attr('omit-tag'), lambda p: p.expression)
     tal_default_expression = utils.attribute(
         utils.tal_attr('default-expression'))
+    metal_define = utils.attribute(
+        utils.metal_attr('define-macro'), lambda p: p.method)
+    metal_use = utils.attribute(
+        utils.metal_attr('use-macro'), lambda p: p.expression)
+    metal_fillslot = utils.attribute(
+        utils.metal_attr('fill-slot'))
+    metal_defineslot = utils.attribute(
+        utils.metal_attr('define-slot'))
     i18n_translate = utils.attribute(
         utils.i18n_attr('translate'))
     i18n_attributes = utils.attribute(
@@ -591,19 +641,36 @@
 def translate_xml(body, *args, **kwargs):
     tree = lxml.etree.parse(StringIO(body), parser)
     root = tree.getroot()
+
     return translate_etree(root, *args, **kwargs)
 
-def translate_etree(root, params=[], default_expression='python'):
+def translate_etree(root, macro=None ,params=[], default_expression='python'):
     if None not in root.nsmap:
         raise ValueError, "Must set default namespace."
 
+    # skip to macro
+    if macro is not None:
+        elements = root.xpath(
+            './/*[@metal:define-macro="%s"]' % macro,
+            namespaces={'metal': config.METAL_NS})
+
+        if not elements:
+            raise ValueError("Macro not found: %s." % macro)
+
+        root = elements[0]
+        del root.attrib[utils.metal_attr('define-macro')]
+        
     # set default expression name
     key = utils.tal_attr('default-expression')
     if key not in root.attrib:
         root.attrib[key] = default_expression
 
     # set up code generation stream
-    generator = generation.Generator(params)
+    if macro is not None:
+        wrapper = generation.macro_wrapper
+    else:
+        wrapper = generation.template_wrapper
+    generator = generation.Generator(params, wrapper)
     stream = generator.stream
 
     # output doctype if any

Modified: z3c.pt/trunk/src/z3c/pt/translation.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/translation.txt	2008-08-09 16:18:54 UTC (rev 89579)
@@ -3,16 +3,7 @@
 
 This document contains functional template tests.
 
-A generic render-method is used for convenience.
-
-  >>> def render(body, translator, **kwargs):
-  ...    generator = translator(body)
-  ...    source, _globals = generator()
-  ...    _locals = {}
-  ...    _globals.update(kwargs)
-  ...    exec source in _globals, _locals
-  ...    return _locals['render'](**generator.stream.selectors)
-
+  >>> from z3c.pt.testing import render
   >>> from z3c.pt.translation import translate_xml
 
 XHTML
@@ -104,11 +95,13 @@
   ...         tal:attributes="class 'def' + a; style 'hij'"
   ...         tal:content="a + 'ghi'" />
   ...   <span tal:replace="'Hello World!'">Hello Universe!</span>
+  ...   <span tal:replace="'Hello World!'"><b>Hello Universe!</b></span>
   ...   <span tal:content="None" />
   ... </div>""", translate_xml)
     <div>
       <span id="test" style="hij" class="defabc">abcghi</span>
       Hello World!
+      Hello World!
       <span></span>
     </div>
 
@@ -283,6 +276,75 @@
       <built-in function dir>
     </div>
 
+METAL
+-----
+
+metal:define-macro, metal:use-macro
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div class="greeting" metal:define-macro="greeting">
+  ...     Hello, ${name}!
+  ...   </div>
+  ...   <div tal:define="name 'world'">
+  ...     <div metal:use-macro="template.macros['greeting']" />
+  ...   </div>  
+  ... </div>"""
+
+  >>> from z3c.pt.testing import MockTemplate
+  >>> template = MockTemplate(body, translate_xml)
+  >>> print render(body, translate_xml, template=template)
+  <div>
+    <div>
+      <div class="greeting">
+        Hello, world!
+      </div>
+    </div>  
+  </div>
+
+metal:define-slot, metal:fill-slot
+
+  >>> body = """\
+  ... <div xmlns="http://www.w3.org/1999/xhtml"
+  ...      xmlns:tal="http://xml.zope.org/namespaces/tal"
+  ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
+  ...   <div metal:define-macro="greeting">
+  ...     Hello, <b class="name" metal:define-slot="name">stranger!</b>
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <span metal:fill-slot="name">earth!</span>
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <!-- display fallback greeting -->
+  ...   </div>
+  ...   <div metal:use-macro="template.macros['greeting']">
+  ...     <span metal:fill-slot="dummy">dummy!</span>
+  ...   </div>
+  ... </div>"""
+
+  >>> from z3c.pt.testing import MockTemplate
+  >>> template = MockTemplate(body, translate_xml)
+  >>> print render(body, translate_xml, template=template)
+  <div>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">earth!</b>
+    </div>
+  <BLANKLINE>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">stranger!</b>
+    </div>
+  <BLANKLINE>
+  <BLANKLINE>
+      <div>
+      Hello, <b class="name">stranger!</b>
+    </div>
+  <BLANKLINE>
+  </div>
+
 Genshi
 ------
 

Modified: z3c.pt/trunk/src/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-09 15:23:27 UTC (rev 89578)
+++ z3c.pt/trunk/src/z3c/pt/utils.py	2008-08-09 16:18:54 UTC (rev 89579)
@@ -106,6 +106,9 @@
 def tal_attr(name):
     return "{%s}%s" % (config.TAL_NS, name)
 
+def metal_attr(name):
+    return "{%s}%s" % (config.METAL_NS, name)
+
 def i18n_attr(name):
     return "{%s}%s" % (config.I18N_NS, name)
 



More information about the Checkins mailing list