実現したいこと
goで使用可能なtext/template
のテンプレート名として変数名を使いたいと思います。
しかしそのまま利用しようとする以下のコードはPanicが発生してしまいます。
失敗例
package main
import (
"os"
"strings"
"text/template"
)
var TMPL = strings.Trim(`
{{- /* []string expected */}}
{{- define "sasakuna"}}
{{- range $i, $text := . }}
{{$i}}: {{$text}}
{{- end }}
{{- end }}
`, "\n")
func main() {
tmpl := template.Must(template.New("").Parse(TMPL))
tmpl = template.Must(tmpl.Parse(`{{ template .Name .Data}}`))
tmpl.Execute(os.Stdout, struct {
Name string
Data []string
}{
Name: "sasakuna",
Data: []string{"sasa", "kuna"},
})
// Output:
// panic: template: :1: unexpected ".Name" in template clause
}
当然のことではありますが、テンプレート名として固定の文字列を使用すれば問題なく動作します。
変数を使用しない成功例
package main
import (
"os"
"strings"
"text/template"
)
var TMPL = strings.Trim(`
{{- /* []string expected */}}
{{- define "sasakuna"}}
{{- range $i, $text := . }}
{{$i}}: {{$text}}
{{- end }}
{{- end }}
`, "\n")
func main() {
tmpl := template.Must(template.New("").Parse(TMPL))
tmpl = template.Must(tmpl.Parse(`{{ template "sasakuna" .}}`))
tmpl.Execute(os.Stdout, []string{"sasa", "kuna"})
// Output:
// 0: sasa
// 1: kuna
}
このことに関しては、かなり前の質問とそれに対する回答として記載されています。
https://stackoverflow.com/questions/28830543/how-to-use-a-field-of-struct-or-variable-value-as-template-name
中で示されている理由を読んでみると、参照関係を読み解きやすくするため?というふうに読めます。なんとなくわかりますが、不便さを感じる場面もあるかと思います。
対処方法
この問題に対処するための方法として、テンプレート内で関数が使用できることを利用します。
変数名で指定に成功
package main
import (
"bytes"
"fmt"
"os"
"strings"
"text/template"
)
var TMPL = strings.Trim(`
{{- /* []string expected */}}
{{- define "sasakuna"}}
{{- range $i, $text := . }}
{{$i}}: {{$text}}
{{- end }}
{{- end }}
`, "\n")
func main() {
inlineTemplate := generateInlineTemplates(
[]string{
TMPL,
})
tmpl := template.New("")
tmpl = template.Must(tmpl.Parse(`{{ call .InlineTemplate .Name .Data}}`))
tmpl.Execute(os.Stdout, struct {
Name string
Data []string
InlineTemplate func(string, any) string
}{
Name: "sasakuna",
Data: []string{"sasa", "kuna"},
InlineTemplate: inlineTemplate,
})
// Output:
// 0: sasa
// 1: kuna
}
func generateInlineTemplates(
definitions []string,
) func(string, any) string {
templates := template.New("")
for _, def := range definitions {
templates = template.Must(templates.Parse(def))
}
return func(name string, param any) string {
buf := bytes.NewBuffer([]byte{})
tmpl := template.Must(templates.Parse(fmt.Sprintf(`{{template "%s" .}}`, name)))
if err := tmpl.Execute(buf, param); err != nil {
panic(err)
}
return buf.String()
}
}
ポイントとしてはテンプレートで展開するためのコードを関数内で定義することです。関数の作りはかなり雑ですが、うまいことWrapすればほぼ気にすることなく利用可能かと思います。
ご自身の利用用途に合わせてご利用ください。