Go Template の基本的な動作を見てみよう


はじめに

Goにはテンプレートエンジンの機能としてtext/templateおよびhtml/templateがあります。

https://pkg.go.dev/text/template

https://pkg.go.dev/html/template

HTMLを出力する場合はコード・インジェクションに対応しているhtml/templateを用いる必要があります。

{{ }}で囲まれた「アクション」(データ評価または制御構造)と.を用いたデータ参照を組み合わせてテンプレートを作成します。 Go Template は HELM のテンプレートにも採用されておりGopher以外の方でも触れる機会があるかと思います。

今回の記事では、基本的な動作となるテンプレートと入力を複数組み合わせてどのような出力となるのかを確認してみます。

Go Template の使い方

テンプレート作成・出力の基本的な流れは以下のようになります。

  1. TemplateNewにより用意

    tmpl, err := template.New("my-template").Parse("Hello, {{.}}!")
  2. Executeによりテンプレートを埋め込み文字列を作成する流れとなります。

    err := tmpl.Execute(os.Stdout, "World")
  3. 出力

    Hello, World!

公式サイトのドキュメントではInventoryという構造体を定義しテンプレート側で{{.Count}}および{{.Material}}にて値を参照しています。

package main

import (
	"os"
	"text/template"
)

func main() {
	type Inventory struct {
		Material string
		Count    uint
	}

	sweaters := Inventory{"wool", 17}
	
	tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
	if err != nil {
		panic(err)
	}

	err = tmpl.Execute(os.Stdout, sweaters)
	if err != nil {
		panic(err)
	}
}

実行結果

17 items are made of wool

{{.}} で何が表示される?

シンプルな挙動を確認するためにテンプレート文字{{.}}を用意しさまざまな値を設定して出力を確認します。 以下のコードを基本として変数valに様々な値を設定して出力を確認します。

package main

import (
	"os"
	"text/template"
)

func main() {
	val := 1 // ここの値(型)を変えてみる

	text := "{{.}}"

	tmp, err := template.New("template").Parse(text)
	if err != nil {
		panic(err)
	}

	if err := tmp.Execute(os.Stdout, val); err != nil {
		panic(err)
	}
}

数値型 int

// input
val := 1

// output
// 1

浮動小数点 float

// input
val := 1.1

// output
// 1.1

論理値型 bool

// input
val := true

// output
// true

文字列型 string

// input
val := "value"

// output
// value

rune

// input
val := 'a'

// output
// 97

UTF-8文字コードで文字列をエンコードした結果

byte

// input
val := []byte("value")

// output
// [118 97 108 117 101]

配列

// input
val := []string{"a", "b", "c"}

// output
// [a b c]

構造体

// input
val := struct {
    X string
    Y int
}{
    X: "value",
    Y: 1,
}

// output
// {value 1}

map

// input
val := map[string]string{
    "key": "value",
}

// output
// map[key:value]

特定の要素にアクセス

配列や構造体、mapによる入力はテンプレート側で特定の要素にアクセスするように記述することができます。

配列

以下のコードを基本として入力は変えずにTemplateとしてアクセスする要素を変更してみます。 配列にアクセスするには index というFunctionsを用いる必要があります。 .[0]のようなアクセスはできないようです。

package main

import (
	"os"
	"text/template"
)

func main() {
	val := []string{"a", "b", "c"}

	temp := "{{ index . 0 }}"

	tmp, err := template.New("template").Parse(temp)
	if err != nil {
		panic(err)
	}

	if err := tmp.Execute(os.Stdout, val); err != nil {
		panic(err)
	}
}
// temp
temp := "{{ index . 0 }}"

// output
// a
// temp
temp := "{{ index . 1 }}"

// output
// b
// temp
temp := "{{ index . 2 }}"

// output
// c
// temp
temp := "{{ index . 3 }}"

// output
// panic: template: template:1:3: executing "template" at <index . 3>: error calling index: reflect: slice index out of range

与えられた配列の外側にアクセスしようとするとエラーが発生します。

map

以下のコードを基本としてTemplateとしてアクセスする要素を変更してみます。

package main

import (
	"os"
	"text/template"
)

func main() {
	val := map[string]string{
		"a": "A",
		"b": "B",
		"c": "C",
	}

	temp := "{{ .a }}"

	tmp, err := template.New("template").Parse(temp)
	if err != nil {
		panic(err)
	}

	if err := tmp.Execute(os.Stdout, val); err != nil {
		panic(err)
	}
}
// temp
temp := "{{ .a }}"

// output
// A
// temp
temp := "{{ .b }}"

// output
// B
// temp
temp := "{{ .c }}"

// output
// C
// temp
temp := "{{ .d }}"

// output
// <no value>

配列のときと異なりエラーが発生するのではなく<no value>と出力されました。

構造体

以下のコードを基本としてTemplateとしてアクセスする要素を変更してみます。

package main

import (
	"os"
	"text/template"
)

func main() {
	val := struct {
		X string
		Y int
	}{
		X: "value",
		Y: 1,
	}

	temp := "{{ .X }}"

	tmp, err := template.New("template").Parse(temp)
	if err != nil {
		panic(err)
	}

	if err := tmp.Execute(os.Stdout, val); err != nil {
		panic(err)
	}
}
// temp
temp := "{{ .X }}"

// output
// value
// temp
temp := "{{ .Y }}"

// output
// 1
// temp
temp := "{{ .Z }}"

// output
// panic: template: template:1:3: executing "template" at <.Z>: can't evaluate field Z in type struct { X string; Y int }

配列のときと同様、存在しないキーにアクセスを試みるとエラーが発生します。

おわりに

基本型である数値は文字列はそのまま出力されることが確認できました。 また、配列や構造体、mapでは型情報も一緒に出力されることが確認できました。 配列や構造体、mapにおいてはそれぞれの要素に対してアクセスする方法も確認しました。 基本的な動作となるテンプレートと入力を複数組み合わせてみましたが、少しだけ Go Template に対する壁がなくなったと思います。 Go Template を使いこなすためには制御構文や演算子を使いこなす必要があるので手を動かしてみてそちらも記事を書こうと思います。