2

The idea is to define a variable for a go template which is also a template using variables (a nested template) like this:

package main

import (
    "os"
    "text/template"
)

type Todo struct {
    Name        string
    Description string
    Subtemplate string
}

func main() {
    td := Todo{
        Name: "Test name",
        Description: "Test description",
        Subtemplate: "Subtemplate {{.Name}}",
    }

    t, err := template.New("todos").Parse("{{.Subtemplate}} You have a task named \"{{ .Name}}\" with description: \"{{ .Description}}\"")
    if err != nil {
        panic(err)
    }
    err = t.Execute(os.Stdout, td)
    if err != nil {
        panic(err)
    }
}

The result of the code above is however:

Subtemplate {{.Name}} You have a task named "Test name" with description: "Test description"

means the variable .Name in the subtemplate is not resolved (probably by design not possible, would require some kind of a recursive call). Is there any/other way to achieve this effect?

It should work for the template functions defined using template.FuncMap too. Thanx.

Adam Bogdan Boczek
  • 1,720
  • 6
  • 21
  • 34
  • You can execute templates using `{{template}}` and you can pass data to it, e.g. `{{template "othertemplate" .}}`. – icza Jan 10 '23 at 08:18
  • @icza yes, I tried it also like this `t, err := template.New("todos").Parse("{{define \"subtemplate\"}}{{.Subtemplate}}{{end}} {{template \"subtemplate\" .}} You have a task named \"{{ .Name}}\" with description: \"{{ .Description}}\"")` with no effect... – Adam Bogdan Boczek Jan 10 '23 at 08:33
  • The template name to execute must be a constant (string literal). See the marked duplicate for alternatives. – icza Jan 10 '23 at 08:36
  • @icza I do not want to use a variable as a template name but some variables inside a template defined also as a variable. Could you please open my question again? Thx. – Adam Bogdan Boczek Jan 10 '23 at 08:41

2 Answers2

2

You can register a function which parses a string template and executes it.

The function could look like this:

func exec(body string, data any) (string, error) {
    t, err := template.New("").Parse(body)
    if err != nil {
        return "", err
    }
    buf := &strings.Builder{}
    err = t.Execute(buf, data)
    return buf.String(), err
}

You pass the template body text and the data for template execution to it. It executes it and returns the result.

Once registered, you can call it like this from a template:

{{exec .Subtemplate .}}

Full example:

td := Todo{
    Name:        "Test name",
    Description: "Test description",
    Subtemplate: "Subtemplate {{.Name}}",
}

t, err := template.New("todos").Funcs(template.FuncMap{
    "exec": func(body string, data any) (string, error) {
        t, err := template.New("").Parse(body)
        if err != nil {
            return "", err
        }
        buf := &strings.Builder{}
        err = t.Execute(buf, data)
        return buf.String(), err
    },
}).Parse("{{exec .Subtemplate .}} You have a task named \"{{ .Name}}\" with description: \"{{ .Description}}\"")
if err != nil {
    panic(err)
}
err = t.Execute(os.Stdout, td)
if err != nil {
    panic(err)
}

This will output (try it on the Go Playground):

Subtemplate Test name You have a task named "Test name" with description: "Test description"

Note that if the subtemplate does not change during runtime, you should pre-parse it and store the resulting template (*template.Template) to avoid having to parse it each time you execute the template.

icza
  • 389,944
  • 63
  • 907
  • 827
1

You can render twice if you don't mind for peformance ...

package main

import (
    "os"
    "text/template"
    "bytes"
)

type Todo struct {
    Name        string
    Description string
    Subtemplate string
}

func main() {
    td := Todo{
        Name: "Test name",
        Description: "Test description",
        Subtemplate: "Subtemplate {{.Name}}",
    }

    t, err := template.New("todos").Parse("{{.Subtemplate}} You have a task named \"{{ .Name}}\" with description: \"{{ .Description}}\"")
    if err != nil {
        panic(err)
    }
    buffer := &bytes.Buffer{}
    err = t.Execute(buffer, td)
    tc, err := template.New ("todo2").Parse(string (buffer.Bytes ()))
    if err != nil {
        panic (err)
    }
    err = tc.Execute(os.Stdout, td)
    if err != nil {
        panic(err)
    }
}
glk0
  • 17
  • 6
  • This works for the example in the question but may easily fail if the output of the first template contains delimeters in its output (which are not intended to be processed again). – icza Jan 10 '23 at 13:32