LoginSignup
0
0

More than 1 year has passed since last update.

goで変数をtemplateの名前として利用する

Last updated at Posted at 2022-04-21

実現したいこと

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
}

Play Groundで開く

当然のことではありますが、テンプレート名として固定の文字列を使用すれば問題なく動作します。

変数を使用しない成功例
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
}

Play Groundで開く

このことに関しては、かなり前の質問とそれに対する回答として記載されています。
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()
	}
}

Play Groundで開く

ポイントとしてはテンプレートで展開するためのコードを関数内で定義することです。関数の作りはかなり雑ですが、うまいことWrapすればほぼ気にすることなく利用可能かと思います。
ご自身の利用用途に合わせてご利用ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0