Masterminds / sprig

Useful template functions for Go templates.
http://masterminds.github.io/sprig/
MIT License
4.25k stars 436 forks source link

Feature Request: add "yield" function #387

Open breml opened 11 months ago

breml commented 11 months ago

With Go templates it is not possible to dynamically (based on a variable) include other templates (partials). For example the following is not a valid Go template:

{{ define "first" }} First {{ end }}
{{ $template := "first" }}
{{ template $template . }}

and it will produce the following error:

error parsing template: template: base:4: unexpected "$template" in template clause

Since I had this problem already multiple times, I created a function yield, which does the same as template, but allows the template name (first argument) to by dynamic (it can be a variable).

With the new function yield, the following becomes possible:

{{ define "first" }} First {{ end }}
{{ define "second" }} Second {{ end }}
{{ $template := list "first" "second" }}
{{ $randTemplate := index $template (randInt 0 (len $template) ) }}
{{ yield $randTemplate . }} 

I think, this would be a meaning full addition to https://github.com/Masterminds/sprig and I am happy to provide a PR for this. What do you think?

forquare commented 7 months ago

Not with template, but we have a pattern like this for includes:

{{ include (printf "%s" $template) . }}

It's certainly not as nice as having a dedicated function, but it works well enough for us.

breml commented 7 months ago

@forquare Thanks for your contribution. I am not entirely sure I can follow your example. May I ask you to elaborate. In particular what is $template (filename, name of the thing to include, the content to be included (plain text), the content to be included, but rendered as a Go template, something else) and what does the include function exactly do. Thanks.

breml commented 7 months ago

On a side note: Hugo does provide include functionality for Go templates, see https://gohugo.io/templates/introduction/#includes

forquare commented 7 months ago

@breml Firstly, let me apologise because I'm not sure how Helm does this, but I think they do some kind of preprocessing - I've tried this with my limited Go knowledge and can't replicate my suggestion simply using Go text/template.

Secondly, apologies for not expanding. Despite the fact that what I said may not be valid! Let me still expand:

include is a function in Helm which "will import the contents of a template into the present pipeline where it can be passed along to other functions in the pipeline" (from the Helm documentation here)

In my example, $template is a variable containing the name of a named template, exactly the same as your first example. I had thought that your original example could have been rewritten like this:

{{ define "first" }} First {{ end }}
{{ $template := "first" }}
{{ template (printf "%s" $template) . }}

However I think you would need to have some sort of preprocessing in place to handle this, as my limited knowledge cant' replicate this simply in Go.

breml commented 7 months ago

@forquare Thanks for the additional context. As you have written, include is a function provided by helm (source code: https://github.com/helm/helm/blob/e81f6140ddb22bc99a08f7409522a8dbe5338ee3/pkg/engine/engine.go#L129), which works similar to the function yield that I am proposing in this issue. In the documentation you linked above there is a statement about template, which explains why your snippet does not work:

Because template is an action, and not a function, there is no way to pass the output of a template call to other functions; the data is simply inserted inline.

The key is "template is an action, and not a function". Additionally to the limitation, that it does not allow the output to be passed to an other function, it is also more strict about the "arguments" it accepts. The first argument (name of the template) is required to be a string constant and it is not possible to pass the name of the template as a variable. The error, the template action produces, if the name is not a string constant (but a variable) is provided in the initial description of this issue.

Both these limitations are removed by either the yield function proposed here or the include function available in helm.