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

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

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
Labels
Projects
Milestone

Comments

@sylr
Copy link

@sylr 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
@gopherbot gopherbot added the Proposal label Jul 25, 2019
@alersenkevich

This comment was marked as spam.

@sylr
Copy link
Author

@sylr sylr commented Jul 29, 2019

Motivation: Azure/aks-engine#1663

@rsc
Copy link
Contributor

@rsc 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 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 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 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 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 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 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 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 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 rsc added this to Incoming in Proposals Dec 4, 2019
@rsc
Copy link
Contributor

@rsc 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?

@rsc rsc moved this from Incoming to Active in Proposals Apr 8, 2020
@sylr
Copy link
Author

@sylr 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 rsc commented Apr 15, 2020

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

@rsc rsc moved this from Active to Likely Decline in Proposals Apr 15, 2020
@rsc
Copy link
Contributor

@rsc rsc commented Apr 22, 2020

No change in consensus, so declined.

@rsc rsc moved this from Likely Decline to Declined in Proposals Apr 22, 2020
@rsc rsc closed this Apr 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Proposals
Declined
Linked pull requests

Successfully merging a pull request may close this issue.

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