Go template post processing: is it possible? - go

In my template, I use a sub-template that generates a piece of output.
The template output must be shifted though (because the output is in YAML format).
Is there any possibility to post-process template output?
{{ template "subtemplate" | indent 10 }}
This indent 10 is fictional, just to explain what I need.
It is possible (as #icza suggested) to save the output
into a variable and then work with it,
but maybe there is a better, more elegant approach?
{{$var := execTempl "subtemplate"}}
{{$var}}

The closest you can get to {{ template "subtemplate" | indent 10 }} is to define a function that parses and executes the subtemplate and outputs the result as string.
var externalTemplates = map[string]*template.Template{
"subtemplate": template.Must(template.New("subtemplate").Parse(sub_template)),
}
// Executes external template, must be registered with FuncMap in the main template.
func xtemplate(name string) (string, error) {
var b bytes.Buffer
if err := externalTemplates[name].ExecuteTemplate(&b, name, nil); err != nil {
return "", err
}
return b.String(), nil
}
t := template.Must(template.New("t").Funcs(template.FuncMap{
"xtemplate": xtemplate, // register func
}).Parse(main_template))
In the main template you can then use the function like this:
{{ xtemplate "subtemplate" | indent 10 }}
https://play.golang.org/p/brolOLFT4xL

Related

Golang: Custom template "block" functions?

I'm wondering if it's possible to use a custom function as a template block with Golang templates. The code below shows an example.
{{ custom_func . }}
This is content that "custom_func" should do something with.
{{ end }}
Use case is a bit peculiar and non-standard. Basically I want the ability for the template author to pass in large block of text where newlines etc. is respected and for that entire block of text to be passed to the function. I could have done something like:
{{ custom_func "This is a lot of text\n with many lines etc." }}
But this is not very user friendly to the template author. The end goal is for them to write something like this:
Author is writing something normal...
{{ note }}
But would like to wrap this content as a "note".
Which when passed to the "note" function, will wrap the content with appropriate divs etc.
{{ end }}
Basically I'm trying an experiment to see if I can achieve "markdown/reStructuredText"-like content with pure go templates. It's mostly an experiment for now.
Eventually I'll probably need to write a proper PEG parser for this, but I want to see if this is possible first.
String arguments to functions may be wrapped both in double quotes " or in backticks `.
String literals wrapped in backticks in templates are called raw string constants, and they work like raw string literals in Go source: may include newlines (and cannot contain escape sequences).
So it's possible what you want if you use backticks for the argument.
For example, a.tmpl:
START
{{ note `a
b\t
c
d`}}
END
App to load and execute the template:
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).ParseFiles("a.tmpl"))
if err := t.ExecuteTemplate(os.Stdout, "a.tmpl", nil); err != nil {
panic(err)
}
This will output:
START
<note>
a
b\t
c
d
</note>
END
It's a bit tricky if you define the template in your Go source, as if you use backticks for the template text (because you want to write multiple lines), you can't embed backticks in a raw string literal. You have to break the literal, and concatenate the backticks.
Example doing this in a Go source file:
func main() {
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).Parse(src))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
const src = `START
{{ note ` + "`" + `a
b\t
c
d` + "`" + `}}
END
`
This will output the same, try it on the Go Playground.

How can I send the result of named-template to a function [duplicate]

This question already has answers here:
Capture or assign golang template output to variable
(1 answer)
get the value of a go template from inside another template [duplicate]
(1 answer)
Closed 4 years ago.
I am trying to indent the result of a named-template. I have tried all of below syntax. Parentheses around "template name ." do not work either.
{{template "my-org.labels" . | indent 8}}
{{indent 8 template "mbfs-postgres.labels" .}}
{{with template "mbfs-postgres.labels" .}}...
There is not built-in support for sending the results of a template to a function.
It is possible to write a template function to do this: The execTemplate function returns a function that executes a named template in t.
func execTemplate(t *template.Template) func(string, interface{}) (string, error) {
return func(name string, v interface{}) (string, error) {
var buf strings.Builder
err := t.ExecuteTemplate(&buf, name, v)
return buf.String(), err
}
}
Use it like this:
t := template.New("")
t = template.Must(t.Funcs(template.FuncMap{"exec": execTemplate(t), "inent": indent}).Parse(`
The template is: {{exec "labels" . | indent 8}}
{{define "labels"}}Hello from Labels!{{end}}`))
t.Execute(os.Stdout, nil)
There are variations on this basic idea that may or may not be more convenient to use. For example, a value can be passed as an argument to the template instead of using a template function.
type execTemplate struct {
t *template.Template
}
func (et execTemplate) Execute(name string, v interface{}) (string, error) {
var buf strings.Builder
err := et.t.ExecuteTemplate(&buf, name, v)
return buf.String(), err
}
t := template.Must(template.New("").Funcs(template.FuncMap{"indent":
indent}).Parse(`The template is: {{.Execute "labels" . | indent 8}}
{{define "labels"}}Hello from Labels!{{end}}`))
fmt.Println(t.Execute(os.Stdout, execTemplate{t}))

get the value of a go template from inside another template [duplicate]

This question already has an answer here:
Capture or assign golang template output to variable
(1 answer)
Closed 5 years ago.
I have two templates T1 and T2. I want to get the output of T1 and do a some extra processing on it inside T2. My question is:
how do I store the output of T1 in a variable inside T2? Is this even possible?
Here's some pseudo-template:
{{define "T1"}}
{{ printf "%s-%s" complex stuff }}
{{end}}
{{define "T2"}}
{{ $some_var := output_from_template "T1"}} <<<<<<<<<<<
{{ etc }}
{{end}}
There is no builtin support for storing the result of a template in a template variable, only for the inclusion of the result.
But you can register custom functions with any complex functionality you want. You may register a GetOutput function which would execute a template identified by its name, and it could return the result as a string, which you can store in a template variable.
Example doing this:
func main() {
t := template.New("")
t = template.Must(t.Funcs(template.FuncMap{
"GetOutput": func(name string) (string, error) {
buf := &bytes.Buffer{}
err := t.ExecuteTemplate(buf, name, nil)
return buf.String(), err
},
}).Parse(src))
if err := t.ExecuteTemplate(os.Stdout, "T2", nil); err != nil {
panic(err)
}
}
const src = `
{{define "T1"}}{{ printf "%s-%s" "complex" "stuff" }}{{end}}
{{define "T2"}}
{{ $t1Out := (GetOutput "T1")}}
{{ printf "%s-%s" "even-more" $t1Out }}
{{end}}`
Output will be (try it on the Go Playground):
even-more-complex-stuff
The "T1" template simply outputs "complex-stuff", and the "T2" template gets the output of "T1", and concatenates the static text "even-more-" and the result of "T1".
The registered GetOutput function gets the name of a template to execute, executes it by directing its output to a local buffer, and returns the content of the buffer (along with the optional error of its execution).
Edit: I've found an exact duplicate: Capture or assign golang template output to variable

Pass data between templates [duplicate]

This question already has answers here:
Calling a template with several pipeline parameters
(10 answers)
Closed 5 years ago.
I have simple case, where a templates (text/templates) includes another like this
`index.html`
{{ template "image_row" . }}
`image_row.html`
{{ define "image_row" }}
To stuff here
{{ end }}
Now I want to reuse the image row template. Let's say I would like to pass a simple number, so that the image_row template builds up rows according to this number
I'd like to have something like that (where 5 is the additional argument)
index.html
{{ template "image_row" . | 5 }}
How could I achieve that in this case?
I'm not sure whether there exists a builtin solution for passing multiple arguments to a template invocation but, in case there isn't one, you could define a function that merges its arguments and returns them as a single slice value, then you can register that function and use it in the template invocation.
Something like:
func args(vs ...interface{}) []interface{} { return vs }
t, err := template.New("t").Funcs(template.FuncMap{"args":args}).Parse...
Then, in your index.html, you would do this:
{{ template "image_row" args . 5 }}
And then inside your image_row template you can access the arguments with the builtin index function like this:
{{ define "image_row" }}
To stuff here {{index . 0}} {{index . 1}}
{{ end }}
https://play.golang.org/p/gkdtvvJ1bb
There is no builtin for this. You can add a function that creates a map and use that in the child template:
func argsfn(kvs ...interface{}) (map[string]interface{}, error) {
if len(kvs)%2 != 0 {
return nil, errors.New("args requires even number of arguments.")
}
m := make(map[string]interface{})
for i := 0; i < len(kvs); i += 2 {
s, ok := kvs[i].(string)
if !ok {
return nil, errors.New("even args to args must be strings.")
}
m[s] = kvs[i+1]
}
return m, nil
}
Add it the function to the template like this:
t := template.Must(template.New("").Funcs(template.FuncMap{"args": argsfn}).Parse(......
Use it like this:
{{template "image_row" args "row" . "a" 5}}{{end}}
{{define "image_row"}}
{{$.row}} {{$.a}}
{{end}}
Run it in the playground
The advantage of using a map is that the arguments are "named". The advantage of using a slice as described in another answer is that the code is much simpler.

Go template and function

In my go code I often use if like this
if user && user.Registered { }
equivalent code in go templates would be
{{ if and .User .User.Registered }} {{ end }}
Unfortunately code in the template fails, if .User is nil :/
Is it possible to achieve the same thing in go templates?
The template and function does not do short circuit evaluation like the Go && operator.
The arguments to the and function are evaluated before the function is called. The expression .User.Registered is always evaluated, even if .User is nil.
The fix is to use nested if:
{{if .User}}{{if .User.Registered}} {{end}}{{end}}
You can avoid the nested if or with by using a template function:
func isRegistered(u *user) bool {
return u != nil && u.Registered
}
const tmpl = `{{if isRegistered .User}}registered{{else}}not registered{{end}}`
t := template.Must(template.New("").Funcs(template.FuncMap{"isRegistered": isRegistered}).Parse(tmpl))
playground example
Another option is to use the {{with}} action instead of the and template function.
Quoting from package doc of text/template:
{{with pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated;
otherwise, dot is set to the value of the pipeline and T1 is
executed.
Using {{with}} often results in cleaner and shorter code, as inside the {{with}} the dot . is already set to the non-empty "wrapper", the .User in our case; moreover you don't have to worry about how and if the arguments of the and template function are evaluated.
Your template rewritten:
{{with .User -}}
{{if .Registered}}REGISTERED{{end}}
{{- end}}
Testing it without and with a user:
t := template.Must(template.New("").Parse(tmpl))
fmt.Println("No user:")
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
u := struct{ Registered bool }{true}
fmt.Printf("User: %+v\n", u)
if err := t.Execute(os.Stdout, map[string]interface{}{"User": u}); err != nil {
panic(err)
}
Output (try it on the Go Playground):
No user:
User: {Registered:true}
REGISTERED

Resources