1. Mike Bayer
  2. mako

Commits

Mike Bayer  committed 4a19e4d

- New tag: <%block>. A variant on <%def> that
evaluates its contents in-place.
Can be named or anonymous,
the named version is intended for inheritance
layouts where any given section can be
surrounded by the <%block> tag in order for
it to become overrideable by inheriting
templates, without the need to specify a
top-level <%def> plus explicit call.
Modified scoping and argument rules as well as a
more strictly enforced usage scheme make it ideal
for this purpose without at all replacing most
other things that defs are still good for.
Lots of new docs. [ticket:164]

  • Participants
  • Parent commits 266abeb
  • Branches master

Comments (0)

Files changed (16)

File CHANGES

View file
  • Ignore whitespace
 0.4.1
+- New tag: <%block>.  A variant on <%def> that
+  evaluates its contents in-place.  
+  Can be named or anonymous,
+  the named version is intended for inheritance
+  layouts where any given section can be 
+  surrounded by the <%block> tag in order for
+  it to become overrideable by inheriting
+  templates, without the need to specify a
+  top-level <%def> plus explicit call.
+  Modified scoping and argument rules as well as a 
+  more strictly enforced usage scheme make it ideal 
+  for this purpose without at all replacing most 
+  other things that defs are still good for.  
+  Lots of new docs. [ticket:164]
+
 - a slight adjustment to the "highlight" logic
   for generating template bound stacktraces.
   Will stick to known template source lines

File doc/build/builder/builders.py

View file
  • Ignore whitespace
         self.layout = builder.config.html_context.get('mako_layout', 'html')
  
         self.lookup = TemplateLookup(directories=builder.config.templates_path,
-            format_exceptions=True, 
             imports=[
                 "from builder import util"
             ]

File doc/build/caching.rst

View file
  • Ignore whitespace
 ========
 
 Any template or component can be cached using the ``cache``
-argument to the ``<%page>`` or ``<%def>`` directives:
+argument to the ``<%page>``, ``<%def>`` or ``<%block>`` directives:
 
 .. sourcecode:: mako
 
         other text
     </%def>
 
+... and equivalently with the ``<%block>`` tag, anonymous or named:
+
+.. sourcecode:: mako
+
+    <%block cached="True" cache_timeout="30" cache_type="memory">
+        other text
+    </%block>
+
 Cache arguments
 ================
 

File doc/build/defs.rst

View file
  • Ignore whitespace
 .. _defs_toplevel:
 
-====
-Defs
-====
+===============
+Defs and Blocks
+===============
 
-``<%def>`` is the single tag used to demarcate any block of text
-and/or code. It exists within generated Python as a callable
-function:
+``<%def>`` and ``<%block>`` are two tags that both demarcate any block of text
+and/or code.   They both exist within generated Python as a callable function, 
+i.e., a Python ``def``.   They differ in their scope and calling semantics.
+Whereas ``<%def>`` provides a construct that is very much like a named Python
+``def``, the ``<%block>`` is more layout oriented.
+
+Using Defs
+==========
+
+The ``<%def>`` tag requires a ``name`` attribute, where the ``name`` references
+a Python function signature:
 
 .. sourcecode:: mako
 
         hello world
     </%def>
 
-They are normally called as expressions:
+To invoke the ``<%def>``, it is normally called as an expression:
 
 .. sourcecode:: mako
 
 does not exist.
 
 Calling defs from Other Files 
-==============================
+-----------------------------
 
 Top level ``<%defs>`` are **exported** by your template's
 module, and can be called from the outside; including from other
 :ref:`namespaces_toplevel`.
 
 Calling defs programmatically
-==============================
+-----------------------------
 
 You can call def's programmatically from any :class:`.Template` object
 using the :meth:`~.Template.get_def()` method, which returns a :class:`.DefTemplate`
  
 
 Defs within Defs
-================
+----------------
 
 The def model follows regular Python rules for closures.
 Declaring ``<%def>`` inside another ``<%def>`` declares it
 .. _defs_with_content:
 
 Calling a def with embedded content and/or other defs
-=====================================================
+-----------------------------------------------------
 
 A flip-side to def within def is a def call with content. This
 is where you call a def, and at the same time declare a block of
 provides via ``<%def>`` tags and plain Python callables which are
 invoked via ``<%namespacename:defname>`` or ``<%call>``.
 
+.. _blocks:
+
+Using Blocks
+============
+
+The ``<%block>`` tag is new as of Mako 0.4.1, and introduces some new twists on the
+``<%def>`` tag which make it more closely tailored towards layout.
+
+An example of a block:
+
+.. sourcecode:: mako
+
+    <html>
+        <body>
+            <%block>
+                this is a block.
+            </%block>
+        </body>
+    </html>
+
+In the above example, we define a simple block.  The block renders its content in the place
+that it's defined.  Since the block is called for us, it doesn't need a name and the above
+is referred to as an **anonymous block**.  So the output of the above template will be:
+
+.. sourcecode:: html
+
+    <html>
+        <body>
+                this is a block.
+        </body>
+    </html>
+
+So in fact the above block has absolutely no effect.   Its usefulness comes when we start
+using modifiers.  Such as, we can apply a filter to our block:
+
+.. sourcecode:: mako
+
+    <html>
+        <body>
+            <%block filter="h">
+                <html>this is some escaped html.</html>
+            </%block>
+        </body>
+    </html>
+
+or perhaps a caching directive:
+
+.. sourcecode:: mako
+
+    <html>
+        <body>
+            <%block cached="True" cache_timeout="60">
+                This content will be cached for 60 seconds.
+            </%block>
+        </body>
+    </html>
+
+Blocks also work in iterations, conditionals, just like defs:
+
+.. sourcecode:: mako
+
+    % if some_condition:
+        <%block>condition is met</%block>
+    % endif
+
+While the block renders at the point it is defined in the template,
+the underlying function is present in the generated Python code only
+once, so there's no issue with placing a block inside of a loop or
+similar. Anonymous blocks are defined as closures in the local
+rendering body, so have access to local variable scope:
+
+.. sourcecode:: mako
+
+    % for i in range(1, 4):
+        <%block>i is ${i}</%block>
+    % endfor
+
+
+Using Named Blocks
+------------------
+
+Possibly the more important area where blocks are useful is when we
+do actually give them names. Named blocks are tailored to behave
+somewhat closely to Jinja2's block tag, in that they define an area
+of a layout which can be overridden by an inheriting template. In
+sharp contrast to the ``<%def>`` tag, the name given to a block is
+global for the entire template regardless of how deeply it's nested:
+
+.. sourcecode:: mako
+
+    <html>
+    <%block name="header">
+        <head>
+            <title>
+                <%block name="title">Title</%block>
+            </title>
+        </head>
+    </%block>
+    <body>
+        ${next.body()}
+    </body>
+    </html>
+
+The above example has two named blocks "``header``" and "``title``", both of which can be referred to 
+by an inheriting template.  A detailed walkthrough of this usage can be found at :ref:`inheritance_toplevel`.
+
+Note above that named blocks don't have any argument declaration the way defs do.   They still implement themselves
+as Python functions, however, so they can be invoked additional times beyond their initial definition:
+
+.. sourcecode:: mako
+
+    <div name="page">
+        <%block name="pagecontrol">
+            <a href="">previous page</a> |
+            <a href="">next page</a>
+        </%block>
+
+        <table>
+            ## some content
+        </table>
+
+        ${pagecontrol()}
+    </div>
+
+The content referenced by ``pagecontrol`` above will be rendered both above and below the ``<table>`` tags.
+
+To keep things sane, named blocks have restrictions that defs do not:
+
+* The ``<%block>`` declaration cannot have any argument signature.
+* The name of a ``<%block>`` can only be defined once in a template - an error is raised if two blocks of the same
+  name occur anywhere in a single template, regardless of nesting.  A similar error is raised if a top level def 
+  shares the same name as that of a block.
+* A named ``<%block>`` cannot be defined within a ``<%def>``, or inside the body of a "call", i.e.
+  ``<%call>`` or ``<%namespacename:defname>`` tag.   Anonymous blocks can, however.
+
+Using page arguments in named blocks
+-------------------------------------
+
+A named block is very much like a top level def. It has a similar
+restriction to these types of defs in that arguments passed to the
+template via the ``<%page>`` tag aren't automatically available.
+Using arguments with the ``<%page>`` tag is described in the section
+:ref:`namespaces_body`, and refers to scenarios such as when the
+``body()`` method of a template is called from an inherited template passing
+arguments, or the template is invoked from an ``<%include>`` tag
+with arguments. To allow a named block to share the same arguments
+passed to the page, the ``args`` attribute can be used:
+
+.. sourcecode:: mako
+
+    <%page args="post"/>
+
+    <a name="${post.title}" />
+
+    <span class="post_prose">
+        <%block name="post_prose" args="post">
+            ${post.content}
+        </%block>
+    </span>
+
+Where above, if the template is called via a directive like
+``<%include file="post.mako" args="post=post" />``, the ``post``
+variable is available both in the main body as well as the
+``post_prose`` block.
+
+Similarly, the ``**pageargs`` variable is present, in named blocks only, 
+for those arguments not explicit in the ``<%page>`` tag:
+
+.. sourcecode:: mako
 
+    <%block name="post_prose">
+        ${pageargs['post'].content}
+    </%block>
 
+The ``args`` attribute is only allowed with named blocks. With
+anonymous blocks, the Python function is always rendered in the same
+scope as the call itself, so anything available directly outside the
+anonymous block is available inside as well.

File doc/build/filtering.rst

View file
  • Ignore whitespace
  
 will render ``myexpression`` using the ``trim`` filter only. 
 
-Filtering Defs
-=================
+Filtering Defs and Blocks
+==========================
 
-The ``%def`` tag has a filter argument which will apply the
+The ``%def`` and ``%block`` tags have an argument called ``filter`` which will apply the
 given list of filter functions to the output of the ``%def``:
 
 .. sourcecode:: mako
     ${foo()}
 
 The decorator can be used with top-level defs as well as nested
-defs. Note that when calling a top-level def from the
+defs, and blocks too. Note that when calling a top-level def from the
 ``Template`` api, i.e. ``template.get_def('somedef').render()``,
 the decorator has to write the output to the ``context``, i.e.
 as in the first example. The return value gets discarded.

File doc/build/inheritance.rst

View file
  • Ignore whitespace
 Inheritance
 ===========
 
+.. note::  Most of the inheritance examples here take advantage of a feature that's 
+  new in Mako as of version 0.4.1 called the "block".  This tag is very similar to 
+  the "def" tag but is more streamlined for usage with inheritance.  Note that
+  all of the examples here which use blocks can also use defs instead.   Constrasting
+  usages will be illustrated.
+
 Using template inheritance, two or more templates can organize
 themselves into an **inheritance chain**, where content and
 functions from all involved templates can be intermixed. The
 the **inherited** template, then makes decisions as to what
 resources from ``A`` shall be executed.
 
-In practice, it looks like this. Heres a hypothetical inheriting
+In practice, it looks like this. Here's a hypothetical inheriting
 template, ``index.html``:
 
 .. sourcecode:: mako
     ## index.html
     <%inherit file="base.html"/>
  
-    <%def name="header()">
+    <%block name="header">
         this is some header content
-    </%def>
+    </%block>
  
     this is the body content.
  
     <html>
         <body>
             <div class="header">
-                ${self.header()}
+                <%block name="header"/>
             </div>
  
             ${self.body()}
  
             <div class="footer">
-                ${self.footer()}
+                <%block name="footer">
+                    this is the footer
+                </%block>
             </div>
         </body>
     </html>
 
-    <%def name="footer()">
-        this is the footer
-    </%def>
-
 Here is a breakdown of the execution:
  
 * When ``index.html`` is rendered, control immediately passes to
   ``base.html``.
 * ``base.html`` then renders the top part of an HTML document,
-  then calls the method ``header()`` off of a built in namespace
+  then invokes the ``<%block name="header">`` block.  It invokes the
+  underlying ``header()`` function off of a built in namespace
   called ``self`` (this namespace was first introduced in the
-  Namespaces chapter in
-  :ref:`namespace_self`). Since
-  ``index.html`` is the topmost template and also defines a def
-  called ``header()``, its this ``header()`` def that gets
-  executed.
+  Namespaces chapter in :ref:`namespace_self`). Since
+  ``index.html`` is the topmost template and also defines a block
+  called ``header``, its this ``header`` block that ultimately gets
+  executed - instead of the one that's present in ``base.html``.
 * Control comes back to ``base.html``. Some more HTML is
   rendered.
 * ``base.html`` executes ``self.body()``. The ``body()``
   function on all template-based namespaces refers to the main
   body of the template, therefore the main body of
   ``index.html`` is rendered.
+* When ``<%block name="header">`` is encountered in ``index.html`` 
+  during the ``self.body()`` call, a conditional is checked - does the 
+  current inherited template, i.e. ``base.html``, also define this block ?  If yes,
+  the ``<%block>`` is **not** executed here - the inheritance 
+  mechanism knows that the parent template is responsible for rendering
+  this block (and in fact it already has).  In other words a block
+  only renders in its *basemost scope*.
 * Control comes back to ``base.html``. More HTML is rendered,
-  then the ``self.footer()`` expression is invoked.
-* The ``footer`` def is only defined in ``base.html``, so being
+  then the ``<%block name="footer">`` expression is invoked.
+* The ``footer`` block is only defined in ``base.html``, so being
   the topmost definition of ``footer``, its the one that
   executes. If ``index.html`` also specified ``footer``, then
   its version would **override** that of the base.
 semantics, a textual template is not very much like an
 object-oriented class construct in practice).
 
+Nesting Blocks
+==============
+
+The named blocks defined in an inherited template can also be nested within
+other blocks.   The name given to each block is globally accessible via any inheriting
+template.   We can add a new block ``title`` to our ``header`` block:
+
+.. sourcecode:: mako
+
+    ## base.html
+    <html>
+        <body>
+            <div class="header">
+                <%block name="header">
+                    <h2>
+                        <%block name="title"/>
+                    </h2>
+                </%block>
+            </div>
+ 
+            ${self.body()}
+ 
+            <div class="footer">
+                <%block name="footer">
+                    this is the footer
+                </%block>
+            </div>
+        </body>
+    </html>
+
+The inheriting template can name either or both of ``header`` and ``title``, separately
+or nested themselves:
+
+.. sourcecode:: mako
+
+    ## index.html
+    <%inherit file="base.html"/>
+ 
+    <%block name="header">
+        this is some header content
+        ${parent.header()}
+    </%block>
+
+    <%block name="title">
+        this is the title
+    </%block>
+ 
+    this is the body content.
+
+Note when we overrode ``header``, we added an extra call ``${parent.header()}`` in order to invoke
+the parent's ``header`` block in addition to our own.  That's described in more detail below,
+in :ref:`parent_namespace`.
+
+Rendering a named block multiple times
+======================================
+
+Recall from the section :ref:`blocks` that a named block is just like a ``<%def>``,
+with some different usage rules.   We can call one of our named sections distinctly, for example
+a section that is used more than once, such as the title of a page:
+
+.. sourcecode:: mako
+
+    <html>
+        <head>
+            <title>${self.title()}</title>
+        </head>
+        <body>
+        <%block name="header">
+            <h2><%block name="title"/></h2>
+        </%block>
+        ${self.body()}
+        </body>
+    </html>
+
+Where above an inheriting template can define ``<%block name="title">`` just once, and it will be
+used in the base template both in the ``<title>`` section as well as the ``<h2>``.
+
+But what about defs ?
+=====================
+
+The previous example used the ``<%block>`` tag to produce areas of content
+to be overridden.   Before Mako 0.4.1, there wasn't any such tag - instead
+there was only the ``<%def>`` tag.   As it turns out, named blocks and defs are
+largely interchangeable.  The def simply doesn't call itself automatically,
+and has more open-ended naming and scoping rules that are more flexible and similar
+to Python itself, but less suited towards layout.  The first example from 
+this chapter using defs would look like:
+
+.. sourcecode:: mako
+
+    ## index.html
+    <%inherit file="base.html"/>
+ 
+    <%def name="header()">
+        this is some header content
+    </%def>
+ 
+    this is the body content.
+ 
+And ``base.html``, the inherited template:
+
+.. sourcecode:: mako
+
+    ## base.html
+    <html>
+        <body>
+            <div class="header">
+                ${self.header()}
+            </div>
+ 
+            ${self.body()}
+ 
+            <div class="footer">
+                ${self.footer()}
+            </div>
+        </body>
+    </html>
+
+    <%def name="header()"/>
+    <%def name="footer()">
+        this is the footer
+    </%def>
+
+Above, we illustrate that defs differ from blocks in that their definition 
+and invocation are defined in two separate places, instead of at once. You can *almost* do exactly what a 
+block does if you put the two together:
+
+.. sourcecode:: mako
+
+    <div class="header">
+        <%def name="header()"></%def>${self.header()}
+    </div>
+
+The ``<%block>`` is obviously more streamlined than the ``<%def>`` for this kind
+of usage.  In addition,
+the above "inline" approach with ``<%def>`` does not work with nesting:
+
+.. sourcecode:: mako
+
+    <head>
+        <%def name="header()">
+            <title>
+            ## this won't work !
+            <%def name="title()">default title</%def>${self.title()}
+            </title>
+        </%def>${self.header()}
+    </head>
+
+Where above, the ``title()`` def, because it's a def within a def, is not part of the
+template's exported namespace and will not be part of ``self``.  If the inherited template
+did define its own ``title`` def at the top level, it would be called, but the "default title"
+above is not present at all on ``self`` no matter what.  For this to work as expected
+you'd instead need to say:
+
+.. sourcecode:: mako
+
+    <head>
+        <%def name="header()">
+            <title>
+            ${self.title()}
+            </title>
+        </%def>${self.header()}
+
+        <%def name="title()"/>
+    </head>
+
+That is, ``title`` is defined outside of any other defs so that it is in the ``self`` namespace.
+It works, but the definition needs to be potentially far away from the point of render.
+
+A named block is always placed in the ``self`` namespace, regardless of nesting,
+so this restriction is lifted:
+
+.. sourcecode:: mako
+
+    ## base.html
+    <head>
+        <%block name="header">
+            <title>
+            <%block name="title"/>
+            </title>
+        </%block>
+    </head>
+
+The above template defines ``title`` inside of ``header``, and an inheriting template can define
+one or both in **any** configuration, nested inside each other or not, in order for them to be used:
+
+.. sourcecode:: mako
+
+    ## index.html
+    <%inherit file="base.html"/>
+    <%block name="title">
+        the title
+    </%block>
+    <%block name="header">
+        the header
+    </%block>
+
+So while the ``<%block>`` tag lifts the restriction of nested blocks not being available externally, 
+in order to achieve this it *adds* the restriction that all block names in a single template need
+to be globally unique within the template, and additionally that a ``<%block>`` can't be defined 
+inside of a ``<%def>``. It's a more restricted tag suited towards a more specific use case than ``<%def>``. 
+
 Using the "next" namespace to produce content wrapping 
 =======================================================
 
     <html>
         <body>
             <div class="header">
-                ${self.header()}
+                <%block name="header"/>
             </div>
  
             ${next.body()}
  
             <div class="footer">
-                ${self.footer()}
+                <%block name="footer">
+                    this is the footer
+                </%block>
             </div>
         </body>
     </html>
 
-    <%def name="footer()">
-        this is the footer
-    </%def>
 
 Lets also add an intermediate template called ``layout.html``,
 which inherits from ``base.html``:
     ## layout.html
     <%inherit file="base.html"/>
     <ul>
-        ${self.toolbar()}
+        <%block name="toolbar">
+            <li>selection 1</li>
+            <li>selection 2</li>
+            <li>selection 3</li>
+        </%block> 
     </ul>
     <div class="mainlayout">
         ${next.body()}
     </div>
  
-    <%def name="toolbar()">
-        <li>selection 1</li>
-        <li>selection 2</li>
-        <li>selection 3</li>
-    </%def> 
 
 And finally change ``index.html`` to inherit from
 ``layout.html`` instead:
 ``index.html`` could be used; there would be no way to call
 ``layout.html``'s body content.
 
+.. _parent_namespace:
+
 Using the "parent" namespace to augment defs 
 =============================================
 
 Lets now look at the other inheritance-specific namespace, the
 opposite of ``next`` called ``parent``. ``parent`` is the
 namespace of the template **immediately preceding** the current
-template. What is most useful about this namespace is the
-methods within it which can be accessed within overridden
-versions of those methods. This is not as hard as it sounds and
+template. What's useful about this namespace is that
+defs or blocks can call upon their overridden versions.
+This is not as hard as it sounds and
 is very much like using the ``super`` keyword in Python. Lets
 modify ``index.html`` to augment the list of selections provided
 by the ``toolbar`` function in ``layout.html``:
     ## index.html
     <%inherit file="layout.html"/>
 
-    <%def name="header()">
+    <%block name="header">
         this is some header content
-    </%def>
+    </%block>
 
-    <%def name="toolbar()">
+    <%block name="toolbar">
         ## call the parent's toolbar first
         ${parent.toolbar()}
         <li>selection 4</li>
         <li>selection 5</li>
-    </%def>
+    </%block>
  
     this is the body content.
 

File doc/build/namespaces.rst

View file
  • Ignore whitespace
 Namespaces
 ==========
 
-Namespaces are used to organize groups of components into
-categories, and also to "import" components from other files.
+Namespaces are used to organize groups of defs into
+categories, and also to "import" defs from other files.
 
-If the file ``components.html`` defines these two components:
+If the file ``components.html`` defines these two defs:
 
 .. sourcecode:: mako
 
     </%def>
  
 You can make another file, for example ``index.html``, that
-pulls those two components into a namespace called ``comp``:
+pulls those two defs into a namespace called ``comp``:
 
 .. sourcecode:: mako
 

File doc/build/syntax.rst

View file
  • Ignore whitespace
 Expression Escaping
 ===================
 
-
 Mako includes a number of built-in escaping mechanisms,
 including HTML, URI and XML escaping, as well as a "trim"
 function. These escapes can be added to an expression
 arguments to other def calls (not as hard as it sounds). Get the
 full deal on what %def can do in :ref:`defs_toplevel`.
 
+<%block>
+---------
+
+``%block`` is a tag that's new as of Mako 0.4.1.   It's close to 
+a ``%def``, except executes itself immediately in its base-most scope,
+and can also be anonymous (i.e. with no name):
+
+.. sourcecode:: mako
+
+    <%block filter="h">
+        some <html> stuff.
+    </%block>
+
+Inspired by Jinja2 blocks, named blocks offer a syntactically pleasing way 
+to do inheritance:
+
+.. sourcecode:: mako
+
+    <html>
+        <body>
+        <%block name="header">
+            <h2><%block name="title"/></h2>
+        </%block>
+        ${self.body()}
+        </body>
+    </html>
+
+Blocks are introduced in :ref:`blocks` and further described in :ref:`inheritance_toplevel`.
+
 <%namespace>
 -------------
 
 
 The call tag is the "classic" form of a user-defined tag, and is
 roughly equiavlent to the ``<%namespacename:defname>`` syntax
-described above. This tag is also described in `defs_with_content`.
+described above. This tag is also described in :ref:`defs_with_content`.
 
 <%doc>
 ------

File doc/build/templates/genindex.mako

View file
  • Ignore whitespace
 <%inherit file="layout.mako"/>
 
-<%def name="show_title()">${_('Index')}</%def>
+<%block name="show_title" filter="util.striptags">
+    ${_('Index')}
+</%block>
 
    <h1 id="index">${_('Index')}</h1>
 

File doc/build/templates/layout.mako

View file
  • Ignore whitespace
 ## coding: utf-8
 <%inherit file="${context['mako_layout']}"/>
 
-<%def name="headers()">
+<%block name="headers">
     <link rel="stylesheet" href="${pathto('_static/pygments.css', 1)}" type="text/css" />
     <link rel="stylesheet" href="${pathto('_static/docs.css', 1)}" type="text/css" />
 
     % if prevtopic:
         <link rel="prev" title="${prevtopic['title']|util.striptags}" href="${prevtopic['link']|h}" />
     % endif
-    ${self.extrahead()}
-</%def>
-<%def name="extrahead()"></%def>
+
+    <%block name="extrahead">
+    </%block>
+</%block>
 
         <h1>${docstitle|h}</h1>
 
                 % if current_page_name != master_doc:
                 » ${self.show_title()} 
                 % endif
- 
+
                 ${prevnext()}
                 <h2>
-                    ${self.show_title()} 
+                    <%block name="show_title" filter="util.striptags">
+                    % if title:
+                        ${title}
+                    % endif
+                    </%block>
                 </h2>
             </div>
             % if display_toc and not current_page_name.startswith('index'):
             </div>
         </div>
 
-        <%def name="footer()">
+        <%block name="footer">
             <div class="bottomnav">
                 ${prevnext()}
                 <div class="doc_copyright">
                 % endif
                 </div>
             </div>
-        </%def>
-        ${self.footer()}
+        </%block>
 
 <%def name="prevnext()">
 <div class="prevnext">
 </div>
 </%def>
 
-<%def name="show_title()">
-% if title:
-    ${title}
-% endif
-</%def>
 

File doc/build/templates/search.mako

View file
  • Ignore whitespace
 <%!
     local_script_files = ['_static/searchtools.js']
 %>
-<%def name="show_title()">${_('Search')}</%def>
+
+<%block name="show_title" filter="util.striptags">
+    ${_('Search')}
+</%block>
 
 <div id="searchform">
 <h3>Enter Search Terms:</h3>
 
 <div id="search-results"></div>
 
-<%def name="footer()">
+<%block name="footer">
     ${parent.footer()}
     <script type="text/javascript" src="searchindex.js"></script>
-</%def>
+</%block>

File doc/build/templates/site_base.mako

View file
  • Ignore whitespace
 ${'</%text>'}
 
 <%text><%def name="style()"></%text>
-    ${self.headers()}
+    <%block name="headers"/>
+
     <%text>${parent.style()}</%text>
     <link href="/css/site_docs.css" rel="stylesheet" type="text/css"></link>
 <%text></%def></%text>
 
-<%text><%def name="title()"></%text>${capture(self.show_title)|util.striptags} &mdash; ${docstitle|h}<%text></%def></%text>
+<%text><%def name="title()"></%text><%block name="show_title"/> &mdash; ${docstitle|h}<%text></%def></%text>
 
 <%!
     local_script_files = []

File doc/build/templates/static_base.mako

View file
  • Ignore whitespace
     <head>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
         ${metatags and metatags or ''}
-        <title>${capture(self.show_title)|util.striptags} &mdash; ${docstitle|h}</title>
-        ${self.headers()}
+        <title><%block name="show_title"/> &mdash; ${docstitle|h}</title>
+        <%block name="headers"/>
     </head>
     <body>
         ${next.body()}

File mako/codegen.py

View file
  • Ignore whitespace
 import time
 import re
 from mako.pygen import PythonPrinter
-from mako import util, ast, parsetree, filters
+from mako import util, ast, parsetree, filters, exceptions
 
 MAGIC_NUMBER = 6
 
         self.node = node
         self.identifier_stack = [None]
  
-        self.in_def = isinstance(node, parsetree.DefTag)
+        self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag))
 
         if self.in_def:
-            name = "render_" + node.name
-            args = node.function_decl.get_argument_expressions()
+            name = "render_%s" % node.funcname
+            args = node.get_argument_expressions()
             filtered = len(node.filter_args.args) > 0 
             buffered = eval(node.attributes.get('buffered', 'False'))
             cached = eval(node.attributes.get('cached', 'False'))
             defs = None
             pagetag = None
+            if node.is_block and not node.is_anonymous:
+                args += ['**pageargs']
         else:
             defs = self.write_toplevel()
             pagetag = self.compiler.pagetag
             self.printer.writeline("context._push_buffer()")
  
         self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
-        if not self.in_def and '**pageargs' in args:
+        if (not self.in_def or self.node.is_block) and '**pageargs' in args:
             self.identifier_stack[-1].argument_declared.add('pageargs')
 
         if not self.in_def and (
                 self.in_def = True
                 class NSDefVisitor(object):
                     def visitDefTag(s, node):
+                        s.visitDefOrBase(node)
+
+                    def visitBlockTag(s, node):
+                        s.visitDefOrBase(node)
+
+                    def visitDefOrBase(s, node):
+                        if node.is_anonymous:
+                            raise exceptions.CompileException(
+                                "Can't put anonymous blocks inside <%namespace>", 
+                                **node.exception_kwargs
+                            )
                         self.write_inline_def(node, identifiers, nested=False)
-                        export.append(node.name)
+                        export.append(node.funcname)
                 vis = NSDefVisitor()
                 for n in node.nodes:
                     n.accept_visitor(vis)
         top-level, it is fully rendered as a local closure.
  
         """
- 
         # collection of all defs available to us in this scope
-        comp_idents = dict([(c.name, c) for c in identifiers.defs])
+        comp_idents = dict([(c.funcname, c) for c in identifiers.defs])
         to_write = set()
  
         # write "context.get()" for all variables we are going to 
  
         # write closure functions for closures that we define 
         # right here
-        to_write = to_write.union([c.name for c in identifiers.closuredefs.values()])
+        to_write = to_write.union([c.funcname for c in identifiers.closuredefs.values()])
 
         # remove identifiers that are declared in the argument 
         # signature of the callable
         for ident in to_write:
             if ident in comp_idents:
                 comp = comp_idents[ident]
-                if comp.is_root():
-                    self.write_def_decl(comp, identifiers)
+                if comp.is_block:
+                    if not comp.is_anonymous:
+                        self.write_def_decl(comp, identifiers)
+                    else:
+                        self.write_inline_def(comp, identifiers, nested=True)
                 else:
-                    self.write_inline_def(comp, identifiers, nested=True)
+                    if comp.is_root():
+                        self.write_def_decl(comp, identifiers)
+                    else:
+                        self.write_inline_def(comp, identifiers, nested=True)
+
             elif ident in self.compiler.namespaces:
                 self.printer.writeline(
                             "%s = _mako_get_namespace(context, %r)" % 
 
     def write_def_decl(self, node, identifiers):
         """write a locally-available callable referencing a top-level def"""
-        funcname = node.function_decl.funcname
-        namedecls = node.function_decl.get_argument_expressions()
-        nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
+        funcname = node.funcname
+        namedecls = node.get_argument_expressions()
+        nameargs = node.get_argument_expressions(include_defaults=False)
  
         if not self.in_def and (
                                 len(self.identifiers.locally_assigned) > 0 or
  
     def write_inline_def(self, node, identifiers, nested):
         """write a locally-available def callable inside an enclosing def."""
- 
-        namedecls = node.function_decl.get_argument_expressions()
+
+        namedecls = node.get_argument_expressions()
  
         decorator = node.decorator
         if decorator:
             self.printer.writeline("@runtime._decorate_inline(context, %s)" % decorator)
-        self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls)))
+        self.printer.writeline("def %s(%s):" % (node.funcname, ",".join(namedecls)))
         filtered = len(node.filter_args.args) > 0 
         buffered = eval(node.attributes.get('buffered', 'False'))
         cached = eval(node.attributes.get('cached', 'False'))
         self.write_def_finish(node, buffered, filtered, cached)
         self.printer.writeline(None)
         if cached:
-            self.write_cache_decorator(node, node.name, 
+            self.write_cache_decorator(node, node.funcname, 
                                         namedecls, False, identifiers, 
                                         inline=True, toplevel=False)
  
     def visitDefTag(self, node):
         pass
 
+    def visitBlockTag(self, node):
+        if node.is_anonymous:
+            self.printer.writeline("%s()" % node.funcname)
+        else:
+            nameargs = node.get_argument_expressions(include_defaults=False)
+            nameargs += ['**pageargs']
+            self.printer.writeline("if 'parent' not in context._data or "
+                                    "not hasattr(context._data['parent'], '%s'):" 
+                                    % node.funcname)
+            self.printer.writeline("context['self'].%s(%s)" % (node.funcname, ",".join(nameargs)))
+            self.printer.writeline("\n")
+
     def visitCallNamespaceTag(self, node):
         # TODO: we can put namespace-specific checks here, such
         # as ensure the given namespace will be imported,
         self.identifier_stack.append(body_identifiers)
         class DefVisitor(object):
             def visitDefTag(s, node):
+                s.visitDefOrBase(node)
+
+            def visitBlockTag(s, node):
+                s.visitDefOrBase(node)
+
+            def visitDefOrBase(s, node):
                 self.write_inline_def(node, callable_identifiers, nested=False)
-                export.append(node.name)
+                if not node.is_anonymous:
+                    export.append(node.funcname)
                 # remove defs that are within the <%call> from the "closuredefs" defined
                 # in the body, so they dont render twice
-                if node.name in body_identifiers.closuredefs:
-                    del body_identifiers.closuredefs[node.name]
+                if node.funcname in body_identifiers.closuredefs:
+                    del body_identifiers.closuredefs[node.funcname]
 
         vis = DefVisitor()
         for n in node.nodes:
         if self.node is node:
             for n in node.nodes:
                 n.accept_visitor(self)
- 
+
+    def _check_name_exists(self, collection, node):
+        existing = collection.get(node.funcname)
+        collection[node.funcname] = node
+        if existing is not None and \
+            existing is not node and \
+            (node.is_block or existing.is_block):
+            raise exceptions.CompileException(
+                    "%%def or %%block named '%s' already "
+                    "exists in this template." % 
+                    node.funcname, **node.exception_kwargs)
+
     def visitDefTag(self, node):
-        if node.is_root():
-            self.topleveldefs[node.name] = node
+        if node.is_root() and not node.is_anonymous:
+            self._check_name_exists(self.topleveldefs, node)
         elif node is not self.node:
-            self.closuredefs[node.name] = node
+            self._check_name_exists(self.closuredefs, node)
 
         for ident in node.undeclared_identifiers():
             if ident != 'context' and ident not in self.declared.union(self.locally_declared):
                 self.argument_declared.add(ident)
             for n in node.nodes:
                 n.accept_visitor(self)
- 
+
+    def visitBlockTag(self, node):
+        if node is not self.node and \
+            not node.is_anonymous:
+
+            if isinstance(self.node, parsetree.DefTag):
+                raise exceptions.CompileException(
+                        "Named block '%s' not allowed inside of def '%s'" 
+                        % (node.name, self.node.name), **node.exception_kwargs)
+            elif isinstance(self.node, (parsetree.CallTag, parsetree.CallNamespaceTag)):
+                raise exceptions.CompileException(
+                        "Named block '%s' not allowed inside of <%%call> tag" 
+                        % (node.name, ), **node.exception_kwargs)
+
+        if not node.is_anonymous:
+            self._check_name_exists(self.topleveldefs, node)
+            self.undeclared.add(node.funcname)
+        elif node is not self.node:
+            self._check_name_exists(self.closuredefs, node)
+        for ident in node.declared_identifiers():
+            self.argument_declared.add(ident)
+        for n in node.nodes:
+            n.accept_visitor(self)
+
     def visitIncludeTag(self, node):
         self.check_declared(node)
  

File mako/parsetree.py

View file
  • Ignore whitespace
                                 attributes.get('filter', ''), 
                                 **self.exception_kwargs)
 
+    is_anonymous = False
+    is_block = False
+
+    @property
+    def funcname(self):
+        return self.function_decl.funcname
+
+    def get_argument_expressions(self, **kw):
+        return self.function_decl.get_argument_expressions(**kw)
+
     def declared_identifiers(self):
         return self.function_decl.argnames
 
                             difference(filters.DEFAULT_ESCAPES.keys())
                         )
 
+class BlockTag(Tag):
+    __keyword__ = 'block'
+
+    def __init__(self, keyword, attributes, **kwargs):
+        super(BlockTag, self).__init__(
+                keyword, 
+                attributes, 
+                ('buffered', 'cached', 'cache_key', 'cache_timeout', 
+                    'cache_type', 'cache_dir', 'cache_url', 'args'), 
+                ('name','filter', 'decorator'), 
+                (), 
+                **kwargs)
+        name = attributes.get('name')
+        if name and not re.match(r'^[\w_]+$',name):
+            raise exceptions.CompileException(
+                                "%block may not specify an argument signature", 
+                                **self.exception_kwargs)
+        if not name and attributes.get('args', None):
+            raise exceptions.CompileException(
+                                "Only named %blocks may specify args",
+                                **self.exception_kwargs
+                                )
+        self.body_decl = ast.FunctionArgs(attributes.get('args', ''), 
+                                            **self.exception_kwargs)
+
+        self.name = name
+        self.decorator = attributes.get('decorator', '')
+        self.filter_args = ast.ArgumentList(
+                                attributes.get('filter', ''), 
+                                **self.exception_kwargs)
+
+
+    is_block = True
+
+    @property
+    def is_anonymous(self):
+        return self.name is None
+
+    @property
+    def funcname(self):
+        return self.name or "__M_anon_%d" % (self.lineno, )
+
+    def get_argument_expressions(self, **kw):
+        return self.body_decl.get_argument_expressions(**kw)
+
+    def declared_identifiers(self):
+        return self.body_decl.argnames
+
+    def undeclared_identifiers(self):
+        return []
+
 class CallTag(Tag):
     __keyword__ = 'call'
 

File test/test_block.py

View file
  • Ignore whitespace
+from mako.template import Template
+from mako.lookup import TemplateLookup
+from mako import exceptions
+from test import TemplateTest, assert_raises, assert_raises_message
+from util import flatten_result, result_lines
+
+
+
+class BlockTest(TemplateTest):
+    def test_anonymous_block_namespace_raises(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "Can't put anonymous blocks inside <%namespace>",
+            Template, """
+                <%namespace name="foo">
+                    <%block>
+                        block
+                    </%block>
+                </%namespace>
+            """
+        )
+
+    def test_anonymous_block_in_call(self):
+        template = Template("""
+        
+            <%self:foo x="5">
+                <%block>
+                    this is the block x
+                </%block>
+            </%self:foo>
+            
+            <%def name="foo(x)">
+                foo:
+                ${caller.body()}
+            </%def>
+        """)
+        self._do_test(
+            template,
+            ["foo:", "this is the block x"],
+            filters=result_lines
+        )
+
+    def test_named_block_in_call(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "Named block 'y' not allowed inside of <%call> tag",
+            Template,"""
+        
+            <%self:foo x="5">
+                <%block name="y">
+                    this is the block
+                </%block>
+            </%self:foo>
+            
+            <%def name="foo(x)">
+                foo:
+                ${caller.body()}
+                ${caller.y()}
+            </%def>
+        """)
+
+    def test_name_collision_blocks_toplevel(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "%def or %block named 'x' already exists in this template",
+            Template,
+            """
+                <%block name="x">
+                    block
+                </%block>
+                
+                foob 
+                
+                <%block name="x">
+                    block
+                </%block>
+            """
+        )
+
+    def test_name_collision_blocks_nested_block(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "%def or %block named 'x' already exists in this template",
+            Template,
+            """
+                <%block>
+                <%block name="x">
+                    block
+                </%block>
+                
+                foob 
+                
+                <%block name="x">
+                    block
+                </%block>
+                </%block>
+            """
+        )
+
+    def test_name_collision_blocks_nested_def(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "Named block 'x' not allowed inside of def 'foo'",
+            Template,
+            """
+                <%def name="foo()">
+                <%block name="x">
+                    block
+                </%block>
+                
+                foob 
+                
+                <%block name="x">
+                    block
+                </%block>
+                </%def>
+            """
+        )
+
+    def test_name_collision_block_def_toplevel(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "%def or %block named 'x' already exists in this template",
+            Template,
+            """
+                <%block name="x">
+                    block
+                </%block>
+                
+                foob 
+                
+                <%def name="x()">
+                    block
+                </%def>
+            """
+        )
+
+    def test_name_collision_def_block_toplevel(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "%def or %block named 'x' already exists in this template",
+            Template,
+            """
+                <%def name="x()">
+                    block
+                </%def>
+
+                foob 
+                
+                <%block name="x">
+                    block
+                </%block>
+                
+            """
+        )
+
+    def test_named_block_renders(self):
+        template = Template("""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            below
+        """)
+        self._do_test(template, ["above", "the header", "below"], 
+                filters=result_lines)
+
+    def test_inherited_block_no_render(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block name="header">
+                    index header
+                </%block>
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "index header", "below"], 
+                filters=result_lines)
+
+    def test_no_named_in_def(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "Named block 'y' not allowed inside of def 'q'",
+            Template,
+            """
+            <%def name="q()">
+                <%block name="y">
+                </%block>
+            </%def>
+        """)
+
+    def test_inherited_block_nested_both(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block name="title">
+                    index title
+                </%block>
+                
+                <%block name="header">
+                    index header
+                    ${parent.header()}
+                </%block>
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                base header
+                <%block name="title">
+                    the title
+                </%block>
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "index header", "base header", "index title", "below"], 
+                filters=result_lines)
+
+    def test_inherited_block_nested_inner_only(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block name="title">
+                    index title
+                </%block>
+                
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                base header
+                <%block name="title">
+                    the title
+                </%block>
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "base header", "index title", "below"], 
+                filters=result_lines)
+
+    def test_noninherited_block_no_render(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block name="some_thing">
+                    some thing
+                </%block>
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "the header", "some thing", "below"], 
+                filters=result_lines)
+
+    def test_no_conflict_nested_one(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block>
+                    <%block name="header">
+                        inner header
+                    </%block>
+                </%block>
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "inner header", "below"], 
+                filters=result_lines)
+
+    def test_nested_dupe_names_raise(self):
+        assert_raises_message(
+            exceptions.CompileException,
+            "%def or %block named 'header' already exists in this template.",
+            Template,
+            """
+                <%inherit file="base"/>
+                <%block name="header">
+                    <%block name="header">
+                        inner header
+                    </%block>
+                </%block>
+            """
+        )
+
+    def test_two_levels_one(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="middle"/>
+                <%block name="header">
+                    index header
+                </%block>
+                <%block>
+                    index anon
+                </%block>
+            """
+        )
+        l.put_string("middle", """
+            <%inherit file="base"/>
+            <%block>
+                middle anon
+            </%block>
+            ${next.body()}
+        """)
+        l.put_string("base","""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "index header", "middle anon", 
+                "index anon", "below"], 
+                filters=result_lines)
+
+    def test_filter(self):
+        template = Template("""
+            <%block filter="h">
+                <html>
+            </%block>
+        """)
+        self._do_test(template, [u'&lt;html&gt;'], 
+                    filters=result_lines)
+
+    def test_anon_in_named(self):
+        template = Template("""
+            <%block name="x">
+                outer above
+                <%block>
+                    inner
+                </%block>
+                outer below
+            </%block>
+        """)
+        self._test_block_in_block(template)
+
+    def test_named_in_anon(self):
+        template = Template("""
+            <%block>
+                outer above
+                <%block name="x">
+                    inner
+                </%block>
+                outer below
+            </%block>
+        """)
+        self._test_block_in_block(template)
+
+    def test_anon_in_anon(self):
+        template = Template("""
+            <%block>
+                outer above
+                <%block>
+                    inner
+                </%block>
+                outer below
+            </%block>
+        """)
+        self._test_block_in_block(template)
+
+    def test_named_in_named(self):
+        template = Template("""
+            <%block name="x">
+                outer above
+                <%block name="y">
+                    inner
+                </%block>
+                outer below
+            </%block>
+        """)
+        self._test_block_in_block(template)
+
+    def _test_block_in_block(self, template):
+        self._do_test(template, 
+            ["outer above", "inner", "outer below"],
+            filters=result_lines
+        )
+
+    def test_iteration(self):
+        t = Template("""
+            % for i in (1, 2, 3):
+                <%block>${i}</%block>
+            % endfor
+        """)
+        self._do_test(t, 
+            ["1", "2", "3"],
+            filters=result_lines
+        )
+
+    def test_conditional(self):
+        t = Template("""
+            % if True:
+                <%block>true</%block>
+            % endif
+
+            % if False:
+                <%block>false</%block>
+            % endif
+        """)
+        self._do_test(t, 
+            ["true"],
+            filters=result_lines
+        )
+
+    def test_block_overridden_by_def(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%def name="header()">
+                    inner header
+                </%def>
+            """
+        )
+        l.put_string("base","""
+            above
+            <%block name="header">
+                the header
+            </%block>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "inner header", "below"], 
+                filters=result_lines)
+
+    def test_def_overridden_by_block(self):
+        l = TemplateLookup()
+        l.put_string("index",
+            """
+                <%inherit file="base"/>
+                <%block name="header">
+                    inner header
+                </%block>
+            """
+        )
+        l.put_string("base","""
+            above
+            ${self.header()}
+            <%def name="header()">
+                the header
+            </%def>
+            
+            ${next.body()}
+            below
+        """)
+        self._do_test(l.get_template("index"), 
+                ["above", "inner header", "below"], 
+                filters=result_lines)
+
+    def test_block_args(self):
+        l = TemplateLookup()
+        l.put_string("caller", """
+        
+            <%include file="callee" args="val1='3', val2='4'"/>
+            
+        """)
+        l.put_string("callee", """
+            <%page args="val1, val2"/>
+            <%block name="foob" args="val1, val2">
+                foob, ${val1}, ${val2}
+            </%block>
+        """)
+        self._do_test(
+            l.get_template("caller"),
+            [u'foob, 3, 4'],
+            filters=result_lines
+        )
+
+    def test_block_variables_contextual(self):
+        t = Template("""            
+            <%block name="foob" >
+                foob, ${val1}, ${val2}
+            </%block>
+        """)
+        self._do_test(
+            t,
+            [u'foob, 3, 4'],
+            template_args={'val1':3, 'val2':4},
+            filters=result_lines
+        )
+
+    def test_block_args_contextual(self):
+        t = Template("""            
+            <%page args="val1"/>
+            <%block name="foob" args="val1">
+                foob, ${val1}, ${val2}
+            </%block>
+        """)
+        self._do_test(
+            t,
+            [u'foob, 3, 4'],
+            template_args={'val1':3, 'val2':4},
+            filters=result_lines
+        )
+
+    def test_block_pageargs_contextual(self):
+        t = Template("""            
+            <%block name="foob">
+                foob, ${pageargs['val1']}, ${pageargs['val2']}
+            </%block>
+        """)
+        self._do_test(
+            t,
+            [u'foob, 3, 4'],
+            template_args={'val1':3, 'val2':4},
+            filters=result_lines
+        )
+
+    def test_block_pageargs(self):
+        l = TemplateLookup()
+        l.put_string("caller", """
+        
+            <%include file="callee" args="val1='3', val2='4'"/>
+            
+        """)
+        l.put_string("callee", """
+            <%block name="foob">
+                foob, ${pageargs['val1']}, ${pageargs['val2']}
+            </%block>
+        """)
+        self._do_test(
+            l.get_template("caller"),
+            [u'foob, 3, 4'],
+            filters=result_lines
+        )