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

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

Related

Go template post processing: is it possible?

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

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}))

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

Go can't evaluate field when using range to build from template

I have Files slice of File structure in my Go program to keep name and size of files. I created template, see below:
type File struct {
FileName string
FileSize int64
}
var Files []File
const tmpl = `
{{range .Files}}
file {{.}}
{{end}}
`
t := template.Must(template.New("html").Parse(tmplhtml))
err = t.Execute(os.Stdout, Files)
if err != nil { panic(err) }
Of course I got panic saying:
can't evaluate field Files in type []main.File
Not sure how to correctly display file names and sizes using range in template.
The initial value of your pipeline (the dot) is the value you pass to Template.Execute() which in your case is Files which is of type []File.
So during your template execution the dot . is []File. This slice has no field or method named Files which is what .Files would refer to in your template.
What you should do is simply use . which refers to your slice:
const tmpl = `
{{range .}}
file {{.}}
{{end}}
`
And that's all. Testing it:
var Files []File = []File{
File{"data.txt", 123},
File{"prog.txt", 5678},
}
t := template.Must(template.New("html").Parse(tmpl))
err := t.Execute(os.Stdout, Files)
Output (try it on the Go Playground):
file {data.txt 123}
file {prog.txt 5678}

Resources