mako icon indicating copy to clipboard operation
mako copied to clipboard

1.1 (or 2.0) feature idea: import blocks

Open sqlalchemy-bot opened this issue 8 years ago • 8 comments

Migrated issue, originally created by jvanasco (@jvanasco)

I don't know if I'd be able to pull this off, but wanted to share publicly for feedback.

I propose a new feature to "import" a block.

It might be invoked like:

<%block import="namespace.function"></%block>
<%block file="file.mako" name="function"></%block>
<%block_import file="file.mako" name="function"></%block>

Invoking this would (re)declare the block as a local function, generating the same code as if you copy/pasted the block between source files.

why?

if you do a large project with template inheritance, you inevitably end up having a lot of blocks that you'd like to re-use across components.

the implementation details for blocks/defs make it hard to recreate this behavior.

  • you can call a block like a def, but it then works like a def
  • you can include a file to place a block, but it's not over-writeable .

an example would be setting up a "header" on a site template. if you wanted to split out the "header" into it's own page, the current way would be...

library.mako:

 <%block name="header">library header</%block>
 

page.mako:

 <%block name="header">page header</%block>
 

site-template.mako:

<%namespace name="t_library" file="library.mako"/>
<%block name="header">${t_library.header()}</%block>

that's a bit messy.

if the library.mako file looks like this:

<%block name="header">
	library header
	<%block name="sub_header">library subheader</%block>
</%block>

then site-template would need to define a header block with a sub_header block for an override to happen from "page" -- but then to get nesting right one would need to do....

library

<%def name="_header()">
    library header
</%def>
<%def name="_subheader()">
    library subheader
</%def>
<%block name="header">
    ${_header()}
    <%block name="sub_header">${_subheader()}</%block>
</%block>

site-template

<%block name="header">
    ${t_library._header()}
    <%block name="sub_header">${t_library._subheader()}</%block>
</%block>

this gets increasingly complex.

so my proposal would be to allow for a block that is defined in one namespace, to be "imported" into a new namespace (vs being called as a regular def)

sqlalchemy-bot avatar Mar 12 '16 00:03 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

when i first made the <%include> tag I considered the option to have a version that's a so-called "server side" include; instead of invoking the related template at runtime, it would pull it in at compile time. the concepts are straight from JSP which you can see here: http://stackoverflow.com/a/14763794/34549.

is this essentially a server side include?

sqlalchemy-bot avatar Mar 15 '16 20:03 sqlalchemy-bot

jvanasco (@jvanasco) wrote:

Yes. Their server-side-include variation of a "compile-time" inclusion is essentially what I'm talking about (the format <%@ include file="header.html" %>) I'm not talking about the "on request" server side includes.

The main goal is to be able define blocks in a namespace file, and import them as-is.

Consider a namespace file that defines this:

## namespace.mako
<%block name="header">
	HEADER
	<%block name="sub_header">SUBHEADER</%block>
</%block>	

The current Mako implementation doesn't let you recycle that into another template as inhertitable blocks.
The current implementation requires block names be defined in the parent template.

So my goal is that writing something like this:

## parent.mako
<%namespace name="t_headers" file="namespace.mako"/>
<%block name="header" source="t_headers.header"></%block>	

Would import the code at compile-time from the namespace, and be equivalent to writing:

## parent.mako
<%block name="header">
	HEADER
	<%block name="sub_header">SUBHEADER</%block>
</%block>	

The workaround right now, is to make the header components separate, and then declare them on the parent files:

## namespace.mako
<%block name="content__header">
	HEADER
</%block>	
<%block name="content__subheader">
	subheader
</%block>	
<%block name="header">
	<%doc>
		This is only a reference implementation. 
		It can be executed as a "def" to print the header components, but is usesless as a "block"
	</%doc>
	${content__header()}
	<%block name="sub_header">${content__subheader()}</%block>
</%block>

## parent.mako
<%namespace name="t_headers" file="namespace.mako"/>
<%block name="header">
	${t_headers.content__header()}
	<%block name="sub_header">${t_headers.content__subheader()}</%block>
</%block>	

sqlalchemy-bot avatar Mar 15 '16 22:03 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

all the mechanics of namespaces are fully at runtime. getting a namespace to mean something at compile time would be an entirely new set of features. I'd rather name it something else. but also I'm not really sold on server side includes anyway since Python itself has no similar concept, and neither do any modern template languages I'm familiar with. You don't see them used in modern JSP either.

sqlalchemy-bot avatar Mar 15 '16 22:03 sqlalchemy-bot

jvanasco (@jvanasco) wrote:

"namespace" is just used as an example.

The proposal that i've been digging through the internals to possibly implement isn't directly for global "compile time" SSIs. (i'm just going to use the phrase compile time include because that's the specific variant we're talking about. a lot of SSIs are run-time).

I am only focused on being able to import a fully defined inheritance block from another file as-is. This only relates to compile-time is because of Mako's implementation details on how it handles the <%block> template tags. In other templating systems, this could (potentially) be handled at runtime.

I roughly solved my problem with a preprocessor that parses a custom tag. It's not an ideal solution because the preprocessor isn't aware of the TemplateLookup, and I had to kludge that in.

I think it could be a valuable feature to future versions if TemplateLookup supported a preprocessor variant that can pass the TemplateLookup object. That could allow processors to be more aware of the Mako environment. If you think it's useful, I'd be willing to do a PR for something like this: • add kwarg preprocessor_accepts_templatelookup to TemplateLookup, Template, Lexer init; handle accordingly in Lexer • new method TemplateLookup.get_filepath(self, url)

This looks to be related to the compile time concept (though it may be entirely different); doing some testing I encountered this implementation detail:

Given:

#parent.mako
<%block name="header">
    [SITE.header]
    <%block name="header_sub">[SITE.header_sub]</%block>
</%block>

A child can overwrite header or header_sub.

However, a child can not declare a blocks with addressable blocks:

# child.mako
<%inherit file="parent.mako"/>
<%block name="header">
    overwrites parent.mako#header
    <%block name="header_sub">will not render</%block>
    <%block name="header_new_block">but this will render</%block>
</%block>

The header_sub block won't render in child.mako, or anything that inherits it.

We have a lot of C > inherits > B > inherits A. Somehow we never encountered this before.

sqlalchemy-bot avatar Mar 16 '16 20:03 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

However, a child can not declare a blocks with addressable blocks:

OK this is how this works. You can make that header_sub render if you just did this:

#!

    <%block name="header">
        overwrites parent.mako#header
        ${header_sub()}
        <%block name="header_sub">will not render</%block>
        <%block name="header_new_block">but this will render</%block>
    </%block>

which is like yuk, but there's a reason, which is because the blocks were meant to be cascaded like this:

#!

    <%block name="header">
        overwrites parent.mako#header
        ${parent.header()}
        <%block name="header_sub">will not render</%block>
        <%block name="header_new_block">but this will render</%block>
    </%block>

where above, you get:

#!

    overwrites parent.mako#header
        
    [SITE.header]
    will not render

        
        but this will render

I'm not sure how to have it both ways unless we added more flags and switches to <%block>.

sqlalchemy-bot avatar Mar 17 '16 00:03 sqlalchemy-bot

jvanasco (@jvanasco) wrote:

which is like yuk, but there's a reason, which is because the blocks were meant to be cascaded like this

Wow. That IS weird, but I understand why it's necessary now. In any event, it works - so no complaints here.

If I could figure out a way to describe this for the docs, I'd submit a PR. One day...

A (better) way to write that and preserve the whitespace would be...

<%block name="header">
    overwrites parent.mako#header
    <%block name="header_sub">will not render</%block>${header_sub()}
    <%block name="header_new_block">but this will render</%block>
</%block>

The call to ${header_sub()} could go before of after the block definition to render the same whitespace.

sqlalchemy-bot avatar Mar 17 '16 17:03 sqlalchemy-bot

Changes by jvanasco (@jvanasco):

  • edited description

sqlalchemy-bot avatar Mar 12 '16 00:03 sqlalchemy-bot

Changes by jvanasco (@jvanasco):

  • edited description

sqlalchemy-bot avatar Mar 12 '16 00:03 sqlalchemy-bot