From 4a19e4d9d9b2f952e9afd93311ef19dc395ac46a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 6 Apr 2011 18:00:20 -0400 Subject: - 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] --- CHANGES | 15 + doc/build/builder/builders.py | 1 - doc/build/caching.rst | 10 +- doc/build/defs.rst | 206 ++++++++++++- doc/build/filtering.rst | 8 +- doc/build/inheritance.rst | 283 ++++++++++++++--- doc/build/namespaces.rst | 8 +- doc/build/syntax.rst | 32 +- doc/build/templates/genindex.mako | 4 +- doc/build/templates/layout.mako | 27 +- doc/build/templates/search.mako | 9 +- doc/build/templates/site_base.mako | 5 +- doc/build/templates/static_base.mako | 4 +- mako/codegen.py | 126 ++++++-- mako/parsetree.py | 61 ++++ test/test_block.py | 569 +++++++++++++++++++++++++++++++++++ 16 files changed, 1261 insertions(+), 107 deletions(-) create mode 100644 test/test_block.py diff --git a/CHANGES b/CHANGES index 76a023b..b99ff1d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,19 @@ 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 diff --git a/doc/build/builder/builders.py b/doc/build/builder/builders.py index 89eb350..b1311b4 100644 --- a/doc/build/builder/builders.py +++ b/doc/build/builder/builders.py @@ -17,7 +17,6 @@ class MakoBridge(TemplateBridge): 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" ] diff --git a/doc/build/caching.rst b/doc/build/caching.rst index 6cf7197..19656c8 100644 --- a/doc/build/caching.rst +++ b/doc/build/caching.rst @@ -5,7 +5,7 @@ Caching ======== 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 @@ -32,6 +32,14 @@ The caching flag and all its options can be used with the other text +... and equivalently with the ``<%block>`` tag, anonymous or named: + +.. sourcecode:: mako + + <%block cached="True" cache_timeout="30" cache_type="memory"> + other text + + Cache arguments ================ diff --git a/doc/build/defs.rst b/doc/build/defs.rst index 42380ee..12243d5 100644 --- a/doc/build/defs.rst +++ b/doc/build/defs.rst @@ -1,12 +1,20 @@ .. _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 @@ -14,7 +22,7 @@ function: hello world -They are normally called as expressions: +To invoke the ``<%def>``, it is normally called as an expression: .. sourcecode:: mako @@ -66,7 +74,7 @@ which evaluate to ``UNDEFINED`` if you reference a name that 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 @@ -110,7 +118,7 @@ these docs. For more detail and examples, see :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` @@ -136,7 +144,7 @@ object. This is a :class:`.Template` subclass which the parent Defs within Defs -================ +---------------- The def model follows regular Python rules for closures. Declaring ``<%def>`` inside another ``<%def>`` declares it @@ -195,7 +203,7 @@ in the expression that tries to render 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 @@ -432,5 +440,181 @@ with a "custom tag" or tag library in some other system, Mako 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 + + + + <%block> + this is a block. + + + + +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 + + + + this is a block. + + + +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 + + + + <%block filter="h"> + this is some escaped html. + + + + +or perhaps a caching directive: + +.. sourcecode:: mako + + + + <%block cached="True" cache_timeout="60"> + This content will be cached for 60 seconds. + + + + +Blocks also work in iterations, conditionals, just like defs: + +.. sourcecode:: mako + + % if some_condition: + <%block>condition is met + % 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} + % 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 + + + <%block name="header"> + + + <%block name="title">Title</%block> + + + + + ${next.body()} + + + +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 + +
+ <%block name="pagecontrol"> + previous page | + next page + + + + ## some content +
+ + ${pagecontrol()} +
+ +The content referenced by ``pagecontrol`` above will be rendered both above and below the ```` 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"/> + + + + + <%block name="post_prose" args="post"> + ${post.content} + + + +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} + +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. diff --git a/doc/build/filtering.rst b/doc/build/filtering.rst index 9cf65ec..ad42dc4 100644 --- a/doc/build/filtering.rst +++ b/doc/build/filtering.rst @@ -166,10 +166,10 @@ Will render ``myexpression`` with no filtering of any kind, and 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 @@ -334,7 +334,7 @@ as just ``runtime``): ${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. diff --git a/doc/build/inheritance.rst b/doc/build/inheritance.rst index e8fe2c2..60775fa 100644 --- a/doc/build/inheritance.rst +++ b/doc/build/inheritance.rst @@ -4,6 +4,12 @@ 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 @@ -14,7 +20,7 @@ to send the executional control to template ``B`` at runtime 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 @@ -22,9 +28,9 @@ template, ``index.html``: ## index.html <%inherit file="base.html"/> - <%def name="header()"> + <%block name="header"> this is some header content - + this is the body content. @@ -36,42 +42,47 @@ And ``base.html``, the inherited template:
- ${self.header()} + <%block name="header"/>
${self.body()} - <%def name="footer()"> - this is the footer - - 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. @@ -104,6 +115,208 @@ seriously; while useful to setup some commonly recognized 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 + + +
+ <%block name="header"> +

+ <%block name="title"/> +

+ +
+ + ${self.body()} + + + + + +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 name="title"> + this is the title + + + 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 + + + + ${self.title()} + + + <%block name="header"> +

<%block name="title"/>

+ + ${self.body()} + + + +Where above an inheriting template can define ``<%block name="title">`` just once, and it will be +used in the base template both in the ```` 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()} + + ${self.header()} + + +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 + + + <%def name="header()"> + + ${self.title()} + + ${self.header()} + + <%def name="title()"/> + + +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 + + <%block name="header"> + + <%block name="title"/> + + + + +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 name="header"> + the header + + +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 ======================================================= @@ -127,20 +340,19 @@ Lets change the line in ``base.html`` which calls upon
- ${self.header()} + <%block name="header"/>
${next.body()} - <%def name="footer()"> - this is the footer - Lets also add an intermediate template called ``layout.html``, which inherits from ``base.html``: @@ -150,17 +362,16 @@ which inherits from ``base.html``: ## layout.html <%inherit file="base.html"/>
${next.body()}
- <%def name="toolbar()"> -
  • selection 1
  • -
  • selection 2
  • -
  • selection 3
  • - And finally change ``index.html`` to inherit from ``layout.html`` instead: @@ -215,15 +426,17 @@ Without the ``next`` namespace, only the main body of ``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``: @@ -233,16 +446,16 @@ 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 name="toolbar()"> + <%block name="toolbar"> ## call the parent's toolbar first ${parent.toolbar()}
  • selection 4
  • selection 5
  • - + this is the body content. diff --git a/doc/build/namespaces.rst b/doc/build/namespaces.rst index 394750a..f3f66e6 100644 --- a/doc/build/namespaces.rst +++ b/doc/build/namespaces.rst @@ -4,10 +4,10 @@ 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 @@ -21,7 +21,7 @@ If the file ``components.html`` defines these two components: 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 diff --git a/doc/build/syntax.rst b/doc/build/syntax.rst index 7251e9b..7305d58 100644 --- a/doc/build/syntax.rst +++ b/doc/build/syntax.rst @@ -47,7 +47,6 @@ result. 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 @@ -300,6 +299,35 @@ with content, which enable packages of defs to be sent as 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 stuff. + + +Inspired by Jinja2 blocks, named blocks offer a syntactically pleasing way +to do inheritance: + +.. sourcecode:: mako + + + + <%block name="header"> +

    <%block name="title"/>

    + + ${self.body()} + + + +Blocks are introduced in :ref:`blocks` and further described in :ref:`inheritance_toplevel`. + <%namespace> ------------- @@ -360,7 +388,7 @@ To create custom tags which accept a body, see 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> ------ diff --git a/doc/build/templates/genindex.mako b/doc/build/templates/genindex.mako index 1543314..4933da4 100644 --- a/doc/build/templates/genindex.mako +++ b/doc/build/templates/genindex.mako @@ -1,6 +1,8 @@ <%inherit file="layout.mako"/> -<%def name="show_title()">${_('Index')} +<%block name="show_title" filter="util.striptags"> + ${_('Index')} +

    ${_('Index')}

    diff --git a/doc/build/templates/layout.mako b/doc/build/templates/layout.mako index 0355cd1..59c3f07 100644 --- a/doc/build/templates/layout.mako +++ b/doc/build/templates/layout.mako @@ -1,7 +1,7 @@ ## coding: utf-8 <%inherit file="${context['mako_layout']}"/> -<%def name="headers()"> +<%block name="headers"> @@ -35,9 +35,10 @@ % if prevtopic: % endif - ${self.extrahead()} - -<%def name="extrahead()"> + + <%block name="extrahead"> + +

    ${docstitle|h}

    @@ -74,10 +75,14 @@ % if current_page_name != master_doc: ยป ${self.show_title()} % endif - + ${prevnext()}

    - ${self.show_title()} + <%block name="show_title" filter="util.striptags"> + % if title: + ${title} + % endif +

    % if display_toc and not current_page_name.startswith('index'): @@ -92,7 +97,7 @@ - <%def name="footer()"> + <%block name="footer">
    ${prevnext()}
    - - ${self.footer()} + <%def name="prevnext()">
    @@ -122,9 +126,4 @@
    -<%def name="show_title()"> -% if title: - ${title} -% endif - diff --git a/doc/build/templates/search.mako b/doc/build/templates/search.mako index dfad4d0..eb21a24 100644 --- a/doc/build/templates/search.mako +++ b/doc/build/templates/search.mako @@ -3,7 +3,10 @@ <%! local_script_files = ['_static/searchtools.js'] %> -<%def name="show_title()">${_('Search')} + +<%block name="show_title" filter="util.striptags"> + ${_('Search')} +

    Enter Search Terms:

    @@ -16,7 +19,7 @@
    -<%def name="footer()"> +<%block name="footer"> ${parent.footer()} - + diff --git a/doc/build/templates/site_base.mako b/doc/build/templates/site_base.mako index 8ab31d9..6090480 100644 --- a/doc/build/templates/site_base.mako +++ b/doc/build/templates/site_base.mako @@ -18,12 +18,13 @@ ${next.body()} ${''} <%text><%def name="style()"> - ${self.headers()} + <%block name="headers"/> + <%text>${parent.style()} <%text> -<%text><%def name="title()">${capture(self.show_title)|util.striptags} — ${docstitle|h}<%text> +<%text><%def name="title()"><%block name="show_title"/> — ${docstitle|h}<%text> <%! local_script_files = [] diff --git a/doc/build/templates/static_base.mako b/doc/build/templates/static_base.mako index eaca5ce..23ebf0a 100644 --- a/doc/build/templates/static_base.mako +++ b/doc/build/templates/static_base.mako @@ -5,8 +5,8 @@ ${metatags and metatags or ''} - ${capture(self.show_title)|util.striptags} — ${docstitle|h} - ${self.headers()} + <%block name="show_title"/> — ${docstitle|h} + <%block name="headers"/> ${next.body()} diff --git a/mako/codegen.py b/mako/codegen.py index 2af1eac..5369180 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -9,7 +9,7 @@ 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 @@ -84,16 +84,18 @@ class _GenerateRenderMethod(object): 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 @@ -238,7 +240,7 @@ class _GenerateRenderMethod(object): 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 ( @@ -308,8 +310,19 @@ class _GenerateRenderMethod(object): 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) @@ -376,9 +389,8 @@ class _GenerateRenderMethod(object): 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 @@ -387,7 +399,7 @@ class _GenerateRenderMethod(object): # 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 @@ -420,10 +432,17 @@ class _GenerateRenderMethod(object): 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)" % @@ -472,9 +491,9 @@ class _GenerateRenderMethod(object): 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 @@ -488,13 +507,13 @@ class _GenerateRenderMethod(object): 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')) @@ -519,7 +538,7 @@ class _GenerateRenderMethod(object): 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) @@ -750,6 +769,18 @@ class _GenerateRenderMethod(object): 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, @@ -770,12 +801,19 @@ class _GenerateRenderMethod(object): 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: @@ -934,12 +972,23 @@ class _Identifiers(object): 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): @@ -951,7 +1000,30 @@ class _Identifiers(object): 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) diff --git a/mako/parsetree.py b/mako/parsetree.py index 53a980d..31b9b4f 100644 --- a/mako/parsetree.py +++ b/mako/parsetree.py @@ -410,6 +410,16 @@ class DefTag(Tag): 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 @@ -423,6 +433,57 @@ class DefTag(Tag): 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' diff --git a/test/test_block.py b/test/test_block.py new file mode 100644 index 0000000..e70b79c --- /dev/null +++ b/test/test_block.py @@ -0,0 +1,569 @@ +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 + + + """ + ) + + def test_anonymous_block_in_call(self): + template = Template(""" + + <%self:foo x="5"> + <%block> + this is the block x + + + + <%def name="foo(x)"> + foo: + ${caller.body()} + + """) + 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 + + + + <%def name="foo(x)"> + foo: + ${caller.body()} + ${caller.y()} + + """) + + 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 + + + foob + + <%block name="x"> + 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 + + + foob + + <%block name="x"> + 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 + + + foob + + <%block name="x"> + block + + + """ + ) + + 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 + + + foob + + <%def name="x()"> + block + + """ + ) + + 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 + + + foob + + <%block name="x"> + block + + + """ + ) + + def test_named_block_renders(self): + template = Template(""" + above + <%block name="header"> + the header + + 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 + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + the header + + + ${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"> + + + """) + + def test_inherited_block_nested_both(self): + l = TemplateLookup() + l.put_string("index", + """ + <%inherit file="base"/> + <%block name="title"> + index title + + + <%block name="header"> + index header + ${parent.header()} + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + base header + <%block name="title"> + the title + + + + ${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 + + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + base header + <%block name="title"> + the title + + + + ${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 + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + the header + + + ${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 + + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + the header + + + ${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 + + + """ + ) + + def test_two_levels_one(self): + l = TemplateLookup() + l.put_string("index", + """ + <%inherit file="middle"/> + <%block name="header"> + index header + + <%block> + index anon + + """ + ) + l.put_string("middle", """ + <%inherit file="base"/> + <%block> + middle anon + + ${next.body()} + """) + l.put_string("base",""" + above + <%block name="header"> + the header + + + ${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"> + + + """) + self._do_test(template, [u'<html>'], + filters=result_lines) + + def test_anon_in_named(self): + template = Template(""" + <%block name="x"> + outer above + <%block> + inner + + outer below + + """) + self._test_block_in_block(template) + + def test_named_in_anon(self): + template = Template(""" + <%block> + outer above + <%block name="x"> + inner + + outer below + + """) + self._test_block_in_block(template) + + def test_anon_in_anon(self): + template = Template(""" + <%block> + outer above + <%block> + inner + + outer below + + """) + self._test_block_in_block(template) + + def test_named_in_named(self): + template = Template(""" + <%block name="x"> + outer above + <%block name="y"> + inner + + outer below + + """) + 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} + % endfor + """) + self._do_test(t, + ["1", "2", "3"], + filters=result_lines + ) + + def test_conditional(self): + t = Template(""" + % if True: + <%block>true + % endif + + % if False: + <%block>false + % 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 + + """ + ) + l.put_string("base",""" + above + <%block name="header"> + the header + + + ${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 + + """ + ) + l.put_string("base",""" + above + ${self.header()} + <%def name="header()"> + the header + + + ${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} + + """) + 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} + + """) + 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} + + """) + 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']} + + """) + 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']} + + """) + self._do_test( + l.get_template("caller"), + [u'foob, 3, 4'], + filters=result_lines + ) \ No newline at end of file -- cgit v1.2.1