Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text/template: {{block}} in text/template #3812

Closed
moraes opened this issue Jul 9, 2012 · 35 comments
Closed

text/template: {{block}} in text/template #3812

moraes opened this issue Jul 9, 2012 · 35 comments
Assignees
Milestone

Comments

@moraes
Copy link
Contributor

@moraes moraes commented Jul 9, 2012

If you think this is worth a try, I can provide a initial implementation.

(this is not a defect, but a proposal/feature request)

We can achieve what is known as "template inheritance" in other template
engines using a new `{{block}}` tag (see:
https://docs.djangoproject.com/en/dev/topics/templates/#template-inheritance). Hold on,
this proposal is not for real inheritance, this is really composition and I believe it
is better.

A `{{block}}` is a replaceable fragment inside a template. Here's an example:

{{{
    {{define "Base"}}
      <p>A header</p>

      {{block body}}
        <p>A body</p>
      {{end}}}

      <p>A footer</p>
    {{end}}}
}}}

The "body" block is executed normally when this template is called
directly. But when it is called by another template, the blocks
defined in the caller template receive higher priority and can
override the ones from the callee. Here is a template
that "extends" the previous template, overriding the "body" block:

{{{
    {{define "Page"}}
      {{block body}}
        <p>Another body</p>
      {{end}}

      {{template "Base" .}}
    {{end}}
}}}

When the "Page" template is executed, it calls the "Base" template
and its own "body" block overrides the original one.

This is even better than inheritance because you are not restricted
to a single parent template: you can compose multiple calls to other
templates, possibly overriding "blocks" from any of them.
@moraes
Copy link
Contributor Author

@moraes moraes commented Jul 9, 2012

Comment 1:

This needs some more thought. The mechanics is not clear about what is executed in the
caller template. I mean, what defines that a block definition is intended to override a
block from a called template, and not to be executed?
@robpike
Copy link
Contributor

@robpike robpike commented Jul 12, 2012

Comment 2:

I believe this could make it very difficult to secure the template. That is, it would be
hard to do in html/template.

Labels changed: added priority-someday, removed priority-triage.

Status changed to Thinking.

@moraes
Copy link
Contributor Author

@moraes moraes commented Jul 12, 2012

Comment 4:

Blocks could be inlined during pre-process: that is, you call a template and its parse
tree is inlined in the current template, with the blocks replaced. 
With this approach, blocks are just sugar to merge templates and aren't included in the
final parse tree (at the cost of more parsing time && larger parse trees). The
pre-processed template is secured normally, as if it was a normal template.
That said, the mechanism for blocks is still lacking.
@moraes
Copy link
Contributor Author

@moraes moraes commented Jul 25, 2012

Comment 5:

I rethought the {{block}} mechanics and have an proof-of-concept implementation. It is
explained in this code review:
http://golang.org/cl/6447044/
Should I send it to the mailing list for further discussion?
@rsc
Copy link
Contributor

@rsc rsc commented Sep 12, 2012

Comment 6:

Labels changed: added go1.1maybe.

@robpike
Copy link
Contributor

@robpike robpike commented Mar 7, 2013

Comment 7:

Labels changed: removed go1.1maybe.

@rsc
Copy link
Contributor

@rsc rsc commented Jul 30, 2013

Comment 8:

Labels changed: added go1.2maybe.

Owner changed to @robpike.

@rsc
Copy link
Contributor

@rsc rsc commented Jul 30, 2013

Comment 9:

Labels changed: added go1.2, removed go1.2maybe.

@rsc
Copy link
Contributor

@rsc rsc commented Jul 30, 2013

Comment 10:

Labels changed: added feature.

@robpike
Copy link
Contributor

@robpike robpike commented Aug 7, 2013

Comment 11:

I now believe this is easy to support in html/template and would like to add this
feature to Go 1.2 but have questions. Why did you add a "fill" action in your CL? I
don't see why it's necessary because the execution stack could hold the scoped blocks.
I haven't looked closely at your CL yet because I want to understand the design before
the implementation.
@moraes
Copy link
Contributor Author

@moraes moraes commented Aug 7, 2013

Comment 12:

The design changed a couple of times since I submitted this. At the time the idea was to
differentiate a block declaration from its usage.
For familiarity and consistency with existing systems that support inheritance (a.k.a.
Django and all the engines that followed the idea), probably a single action should be
used. I'd invite the community for some brainstorm about the specific semantics, maybe.
:-)
@adg
Copy link
Contributor

@adg adg commented Aug 12, 2013

Comment 13:

Can you post a description of what you're currently working with?
@robpike
Copy link
Contributor

@robpike robpike commented Aug 19, 2013

Comment 14:

When trying to write up a proposal to implement this, I realized I still do not
understand it. In your example we have
{{{
   {{define "Page"}}
     {{block body}}
       <p>Another body</p>
     {{end}}
    {{template "Base" .}}
   {{end}}
}}}
When we invoke {{template"Page"}}, how do we know not to display the text "Another body"
before we reach the instantiation of the "Base" template?
@moraes
Copy link
Contributor Author

@moraes moraes commented Aug 19, 2013

Comment 15:

When I implemented this (or one of the variants), the template set was locked after all
templates were parsed, and blocks were "expanded" when appropriate. By expanded I mean:
block nodes were replaced in the resulting tree, when they were overridden. So execution
needed no changes, because the block nodes were not part of the resulting tree.
This is not possible in current text/template, because it doesn't lock the tree after
parsing (like html/template does -- a requirement of the escaping mechanism).
(Not-Go-1.x parenthesis:
A discovery I made during this experiment is that text/template and html/template could
share a lot more code -- specially execution -- if both locked the tree after parsing.
They could, or should imo, be a single package with the appropriate API to request a
template instance for HTML context or not. Escaping is just an after-parsing tree
expansion, like blocks or new actions or semantics could be.)
I think the particular proposal posted in this issue, as it is, must be forgotten. And
if an inversion-of-control functionality to assemble templates must be added, the
specific semantics should be re-thought from scratch.
I'm sorry for the lack of responses, but I've had no time to come back and think about
this in deep. If you wish, please consider the issue abandoned.
@moraes
Copy link
Contributor Author

@moraes moraes commented Aug 19, 2013

Comment 16:

As an example of a different proposal, see zap, which uses a inheritance syntax and
mechanics similar to Django/Jinja2. but zap is a parser that produces
text/template-compatible templates expanding `{{block}}`tags:
https://code.google.com/p/sadbox/source/browse/zap/doc.go
It adds a `{{block}}` action and a second optional parameter to `{{define}}`, to define
the template that is being overridden.
@moraes
Copy link
Contributor Author

@moraes moraes commented Aug 19, 2013

Comment 17:

PS: When i said "lock the tree after parsing", I really meant "lock the tree before
first execution", which is what html/template does. :)
@robpike
Copy link
Contributor

@robpike robpike commented Aug 20, 2013

Comment 18:

It's clear more design is required than I had anticipated. Deferring to Go 1.3; this
won't be ready for 1.2.

Labels changed: added go1.3, removed go1.2.

@robpike
Copy link
Contributor

@robpike robpike commented Aug 20, 2013

Comment 19:

Labels changed: removed go1.3.

@rsc
Copy link
Contributor

@rsc rsc commented Dec 4, 2013

Comment 20:

Labels changed: added repo-main.

@rsc
Copy link
Contributor

@rsc rsc commented Mar 3, 2014

Comment 21:

Adding Release=None to all Priority=Someday bugs.

Labels changed: added release-none.

@nicksloan
Copy link

@nicksloan nicksloan commented Feb 22, 2015

Has there been more discussion on this elsewhere? Where would be a good place to kick this off again?

@rsc rsc added this to the Unplanned milestone Apr 10, 2015
@rsc rsc removed repo-main labels Apr 14, 2015
@robpike robpike modified the milestones: Go1.6Early, Unplanned Aug 26, 2015
@adg
Copy link
Contributor

@adg adg commented Aug 28, 2015

Rob and I just designed a potential solution to this issue. It introduces a new block keyword that allows the definition of inline templates, and the Overlay method that allows the redefinition of templates.

const (
    master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
    overlay = `{{define "list"}} {{join . ", "}}{{end}} `
)
var (
    funcs     = template.FuncMap{"join": strings.Join}
    guardians = []string{"Groot", "Gamora", "Nebula", "Star-Lord", "Rocket"}
)
masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
if err != nil {
    log.Fatal(err)
}
overlayTmpl, err := masterTmpl.Overlay(overlay)
if err != nil {
    log.Fatal(err)
}
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
    log.Fatal(err)
}
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
    log.Fatal(err)
}
// Output:
// Names:
// - Groot
// - Gamora
// - Nebula
// - Star-Lord
// - Rocket
// Names: Groot, Gamora, Nebula, Star-Lord, Rocket

See this issue for the details: https://go-review.googlesource.com/#/c/14005

Still TBC are the helpers (OverlayFiles, OverlayGlob, etc) and html/template support.

@gopherbot
Copy link

@gopherbot gopherbot commented Aug 28, 2015

CL https://golang.org/cl/14005 mentions this issue.

@nf
Copy link

@nf nf commented Aug 28, 2015

To clarify the mechanics of block: It is a transformation that happens entirely inside the parser. The block block is defined as a template with the supplied name, and its occurrence in the outer template is replaced with a template directive.

For example, this:

{{define "foo"}}
Hello {{block "bar" .}}bar{{end}}!
{{end}}

after parsing, is effectively this:

{{define "foo"}}
Hello {{template "bar" .}}!
{{end}}
{{define "bar"}}bar{{end}}
@twotwotwo
Copy link

@twotwotwo twotwotwo commented Aug 28, 2015

This takes care of what I've used inheritance for in Django (and, before that, Perl's HTML::Mason 1.x): we have a master with the HTML boilerplate, the code to load common scripts, etc., and the content of site-wide headers/footers; other ("overlay") templates provide a chunk of body content, may add extra resources they need to pull in, may vary title, meta tags, legal fine print in the middle of the footer, or other stuff.

I like that in the proposal overlays don't specify their master, unlike in Django and Mason. Sometimes one set of overlays (forms) need to be inserted into more than one master (brand/"skin"), and Django and Mason make that ugly to do.

One misfeature of Django template inheritance is that you it'll let you put content outside any block in an overlay template and it silently discards it. It looks like this CL does that, too. Probably not worth the mess, but it'd be cool if Overlay checked the parsed overlay template and errored out ("overlay content must be inside block definitions") on seeing anything but a define, comment, or whitespace. Or Overlay could just execute the overlay template with nil data and error out if there's non-whitespace output or an error, but that'd be behavior quirky enough you might have to document it. Anyway, the behavior already in the CL is what I already live with every day, so not a big deal.

@mpvl
Copy link
Member

@mpvl mpvl commented Aug 28, 2015

I'm working on a localization design (doc going to golang-dev soon) which includes a proposed extension to templates akin to Soy. The most notable feature is a msg keyword with functionality not dissimilar to this, but different enough to not allow this as a solution.

I can simulate a "msg" keyword by using {{msg}}/{{msgend}} pairs, instead of {{msg}}/{{end}}, but the latter form is clearly ideal.

Anyway, the point is that the solution is fairly similar: parse the template using template/parse, rewrite the template to something understandable by the core template package, Execute. So it may be an idea to instead of defining the new overlay construct in core, extend template/parse to allow user-definable actions and rewrite capabilities, similar to allowing users to specify Funcs.

The core library could still define an off-the-shelf rewriter like the one proposed. Although I'm a little bit weary about adding features to a core package for something that is just syntactic sugar. The rewriter for msg could reside in the text repo and would not need to be in core, etc.

@rsc
Copy link
Contributor

@rsc rsc commented Aug 28, 2015

I'm confused. I replied on the CL.

@twotwotwo
Copy link

@twotwotwo twotwotwo commented Aug 28, 2015

@rsc - Some template systems do arrange so that you don't need to write any non-template code to glue two templates together. In Django you put {% extends "master_template_name" %} at the start of the overlay template. HTML::Mason looks through parent dirs for specially-named templates (autohandlers).

The Overlay method seems more flexible. In one case I had a few overlay templates with my app's forms and a few master templates with different branding and legal boilerplate in the headers/footers. I wanted to be able to use use any form with any header/footer. Overlay makes that easy; extends doesn't, since it ties each overlay to one master. But clearly I can get by with something like extends (here that would be, what, {{overlays "master"}}?); I'm living with it today in Django-land.

The extra lines to invoke Overlay don't bother me much. If I have one master for all my pages, I write a convenience function to overlay all my forms on it. If I have different needs I write something to search for masters like Mason does. As long as Go exposes overlaying, I think users can sort out app-appropriate ways to make it easy/quick to invoke.

@twotwotwo
Copy link

@twotwotwo twotwotwo commented Aug 28, 2015

Not in response to anything in particular, but just since it hasn't come up in discussion much, noting that master templates often have a few blocks. (Just looked at one with seven, for example.) Imagine that one part of the site needs to load additional scripts in the header, another needs a different organization's logo/name/nav links atop the page and tweaks to the legal boilerplate in the footer, a third wants to generate its title/description in its own way, etc. You make blocks to help do each of those things. That's part of why users want blocks and not just, say, a header template and a footer template.

Maybe an example with two blocks is worth it, but maybe users don't need the handholding.

@robpike
Copy link
Contributor

@robpike robpike commented Aug 28, 2015

I replied to @rsc on the CL.

@jonathaningram
Copy link

@jonathaningram jonathaningram commented Aug 28, 2015

(Reposting from my tweet to Andrew). For me, a quintessential example for HTML would be setting a default <title/> in the parent/master and then overriding it in the child/overlay, and also setting up an empty content block in the parent/master and filling it out in the child/overlay.

const (
    layout  = `<title>{{block "metaTitle" .}}Welcome to golang.org{{end}}</title><body>{{block "content"}}{{end}}</body>`
    aboutPage = `{{define "metaTitle"}}Read all about Go on golang.org{{end}} {{define "content"}}About page content{{end}}`
)
layoutTmpl, err := template.New("layout").Parse(layout)
if err != nil {
    log.Fatal(err)
}
aboutPageTmpl, err := layoutTmpl.Overlay(aboutPage)
if err != nil {
    log.Fatal(err)
}
if err := layoutTmpl.Execute(os.Stdout, nil); err != nil {
    log.Fatal(err)
}
if err := aboutPageTmpl.Execute(os.Stdout, nil); err != nil {
    log.Fatal(err)
}
// Output:
// <title>Welcome to golang.org</title><body></body>
// <title>Read all about Go on golang.org</title><body>About page content</body>

I hope this is useful feedback.

Would the child/overlay/aboutPage be able to get the default block content from the master with something like {{parent}} or {{master}}? I'm assuming with this design, no, because this means the overlay now needs to know it's a child and could not be executed in the absence of master.

E.g.

const (
    layout  = `<title>{{block "metaTitle" .}}golang.org{{end}}</title>`
    aboutPage = `{{define "metaTitle"}}Read all about Go - {{parent}}{{end}}`
)
// Output:
// <title>golang.org</title>
// <title>Read all about Go - golang.org</title>
@nodirt
Copy link
Contributor

@nodirt nodirt commented Sep 7, 2015

I may be late to the party, but let me propose an alternative design:

  1. Allow {{template}} to accept a pipeline as an argument, e.g. {{template .content}}. Currently it accepts only a constant string.
  2. Add built-in map func that accepts a list of pairs and returns a map, e.g. map "foo" 1 "bar" 2 in a template is equivalent to map[interface{}]interface{}{"foo": 1, "bar": 2} in Go.
  • Alternatively, add support for map literal.

This enables the following:

master:

{{if .content}}
    {{template .content .data}}
{{else}}
    Default content
{{end}}

page:

{{define "content"}}
    REAL content: {{.}}
{{end}}

{{template "master" map "content" "content" "data" .}}

Here the "page" explicitly passes the name of the template to be used as a content block. The "master" checks if content block was passed, and if it is, uses it. Otherwise, master uses the default content. This design relies on the fact that template scope is global and content template is indeed accessible to master.

Here is a proof of concept, with defined map function and hardcoded value of the content key: https://play.golang.org/p/ytuEwBxDoV

@nodirt
Copy link
Contributor

@nodirt nodirt commented Sep 7, 2015

On the other hand, this might be too verbose, and with a dynamic template name, html/tempalte would not be able to escape correctly.

@robpike
Copy link
Contributor

@robpike robpike commented Sep 7, 2015

@nodirt The reason the template action does not accept a dynamic expression for the name of the template to invoke is that that makes it infeasible to provide the safety that the html/template package guarantees. In fact, the ability was deleted from text/template when html/template was added.

@adg
Copy link
Contributor

@adg adg commented Sep 18, 2015

I've just uploaded a revised version of the change that removes the Overlay API addition, and instead permits redefinition of templates.

https://go-review.googlesource.com/#/c/14005/2

Redefinition is always permitted in text/template, but in html/template a template may not be redefined after it has been executed. The examples in the change illustrate how one might use the new feature.

@adg adg closed this in 12dfc3b Sep 28, 2015
d2g added a commit to d2g/-inactive-goti that referenced this issue Sep 29, 2015
This is no longer required as [Go #3812](golang/go#3812) should have resolved this problem.
@golang golang locked and limited conversation to collaborators Sep 27, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.