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.

4
  • You can execute templates using {{template}} and you can pass data to it, e.g. {{template "othertemplate" .}}. Commented Jan 10, 2023 at 8: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... Commented Jan 10, 2023 at 8:33
  • The template name to execute must be a constant (string literal). See the marked duplicate for alternatives. Commented Jan 10, 2023 at 8: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. Commented Jan 10, 2023 at 8:41

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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

1 Comment

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.