Skip to content

proposal: text/template: allow template and block outputs to be chained #33273

New issue

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

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

Already on GitHub? # to your account

Closed
sylr opened this issue Jul 25, 2019 · 15 comments
Closed

proposal: text/template: allow template and block outputs to be chained #33273

sylr opened this issue Jul 25, 2019 · 15 comments

Comments

@sylr
Copy link

sylr commented Jul 25, 2019

It would be nice to be able to post process the output of the template and block directives, e.g.:

{{ block "letter" . | GzipString | Base64EncodeString }}
Dear {{.Name}},
{{if .Attended}}It was a pleasure to see you at the wedding.
{{else}}It is a shame you couldn't make it to the wedding.{{end}}
{{with .Gift}}Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
{{ end }}
{{ define "letter" }}
Dear {{.Name}},
{{if .Attended}}It was a pleasure to see you at the wedding.
{{else}}It is a shame you couldn't make it to the wedding.{{end}}
{{with .Gift}}Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
{{ end }}
{{ template "letter" . | GzipString | Base64EncodeString }}

Regards.

@gopherbot gopherbot added this to the Proposal milestone Jul 25, 2019
@alersenkevich

This comment has been minimized.

@sylr
Copy link
Author

sylr commented Jul 29, 2019

Motivation: Azure/aks-engine#1663

@rsc
Copy link
Contributor

rsc commented Aug 27, 2019

/cc @robpike and @adg. I believe the Go 1.6 template blocks were meant to address this need, but maybe it is worth taking a second look to see how well it has done that.

@adg
Copy link
Contributor

adg commented Sep 2, 2019

In the example provided in this issue, I don't see why the caller wouldn't do the gzipping and base64 encoding in plain Go. Is there some more context that I'm missing?

In the aks-engine PR, I'm not seeing the connection. I also wonder why the author is using text/template to generate JSON; it seems easier and safer to me to use encoding/json.

@sylr can you provide more context as to how these features would benefit you?

@ianlancetaylor ianlancetaylor changed the title proposal: Go 2: text/template allow template and block outputs to be chained proposal: Go 2: text/template: allow template and block outputs to be chained Sep 3, 2019
@sylr
Copy link
Author

sylr commented Sep 13, 2019

@adg aks-engine use text/template to generate a yaml string which is used as value in a json object. Only parts of the YAML are gzipped, e.g.:

https://github.com/Azure/aks-engine/blob/master/parts/k8s/cloud-init/nodecustomdata.yml#L6-L9

Some times the template content is not coming from functions but are defined in the templates:

https://github.com/Azure/aks-engine/blob/master/parts/k8s/cloud-init/nodecustomdata.yml#L245-L261

Allowing to chain template and block output would allow to do gzipping directly in the template.

@adg
Copy link
Contributor

adg commented Sep 16, 2019

I can see how the feature would be useful, but the proposed syntax doesn't make sense. The syntax for a {{template}} block is:

{{template "name" pipeline}}

In the given example

{{ template "letter" . | GzipString | Base64EncodeString }}

the part that says

 . | GzipString | Base64EncodeString

would be considered the pipeline part, when really the dot and the Gzip/Base64 parts are separate. There'd need to be some other way to treat the template invocation as the value, for another pipeline. It's not clear how one might do that.

@sylr
Copy link
Author

sylr commented Sep 16, 2019

Yes it would need to change how parameters are handled.

It currently evaluates like this :

{{ template "name" (. | ...) }}

It would evaluate like this:

{{ (template "name" .) | (...) }}

@sylr
Copy link
Author

sylr commented Sep 16, 2019

As far as I see things it is the current syntax that does not make sense.

The 3rd parameter of the template statement is implicitly handled as a variadic argument which is confusing.

@adg
Copy link
Contributor

adg commented Sep 17, 2019

The third argument to the template statement is not a variadic; it's a pipeline, which is just the text/template way of saying "expression". It's consistent with the rest of the grammar.

Any change we make here would need to be compatible with the existing uses of text/template, so changing the meaning of the grammar there is a non-starter.

@sylr
Copy link
Author

sylr commented Oct 7, 2019

Then what about a new type of block which takes an Argument rather than a Pipeline and which could be chained, e.g.:

{{ eval "letter" . |  ... }}

@adg
Copy link
Contributor

adg commented Oct 7, 2019

To achieve that, you can define a function like 'eval' yourself. For example:

		"eval": func(name string, arg interface{}) (string, error) {
			var buf bytes.Buffer
			err := t.ExecuteTemplate(&buf, name, arg)
			return buf.String(), err
		},

The trick is to make that function enclose the template variable itself, for example:

package main

import (
	"bytes"
	"encoding/base64"
	"log"
	"os"
	"text/template"
)

const tmplText = `
{{ define "letter" }}
Dear {{.Name}},
{{if .Attended}}It was a pleasure to see you at the wedding.
{{else}}It is a shame you couldn't make it to the wedding.{{end}}
{{with .Gift}}Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
{{ end }}
{{(eval "letter" .) | base64 }}
`

func main() {
	t := template.New("")
	t.Funcs(template.FuncMap{
		"eval": func(name string, arg interface{}) (string, error) {
			var buf bytes.Buffer
			err := t.ExecuteTemplate(&buf, name, arg)
			return buf.String(), err
		},
		"base64": func(s string) string {
			return base64.StdEncoding.EncodeToString([]byte(s))
		},
	})
	_, err := t.Parse(tmplText)
	if err != nil {
		log.Fatal(err)
	}
	err = t.Execute(os.Stdout, map[string]interface{}{
		"Name":     "Joe",
		"Attended": true,
		"Gift":     "shoes",
	})
	if err != nil {
		log.Fatal(err)
	}
}

@rsc rsc changed the title proposal: Go 2: text/template: allow template and block outputs to be chained proposal: text/template: allow template and block outputs to be chained Dec 4, 2019
@rsc
Copy link
Contributor

rsc commented Apr 8, 2020

To summarize the discussion so far:

  • the proposed syntax already has a different meaning; that syntax cannot be redefined.
  • the specific need of redirecting a template's output can be done with a registered func
    like eval instead; that turns template execution into a value that can be part of a pipeline.

That is, the proposed feature can't be accepted with the suggested syntax, and the functionality can already be implemented with a short bit of Go code in the caller.

Those two together make it sound like we should decline this proposal. Am I reading anything wrong?

@sylr
Copy link
Author

sylr commented Apr 8, 2020

I am a bit sad this would not be not part of text/template but yes I think we can close this.

@rsc
Copy link
Contributor

rsc commented Apr 15, 2020

Based on the discussion, then, this seems like a likely decline.

@rsc
Copy link
Contributor

rsc commented Apr 22, 2020

No change in consensus, so declined.

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Projects
None yet
Development

No branches or pull requests

5 participants