Goのhtml/template text/templateの記法を覚える

このエントリーをはてなブックマークに追加

自分のブログではHugoを利用していますが、HugoではブログのレイアウトにGoのhtml/template, text/templateの記法が用いられています。 例えばTaleというHugo Themesではブログページの雛形は以下のように示されています。

{{ define "main" }}

<main>
	<div class="post">
		{{ partial "single/post-info.html" . }}
		{{ partial "single/title.html" . }}

		{{ partial "single/header.html" . }}

		{{ .Content }}

		{{ partial "single/footer.html" . }}
	</div>

	<div class="pagination">
		{{- if .PrevPage }}
		<a href="{{ .PrevPage.RelPermalink }}" class="left arrow">&#8592;</a>
		{{- end }}
		{{- if .NextPage }}
		<a href="{{ .NextPage.RelPermalink }}" class="right arrow">&#8594;</a>
		{{- end }}

		<a href="#" class="top">Top</a>
	</div>
</main>

{{ end }}

この{{ }}で囲まれている部分がhtml/template,text/templateの記法部分です。 今回この{{ }}記法の理解を深めるために、html/template、text/templateのGoDocを参照しながら、{{ }}記法について調べてみました。

html/templateのGoDocを読んでみる

まず、Goのhtml/templateパッケージのGoDocを参照してみました。

https://golang.org/pkg/html/template/

するとページの最初に以下のような記述がありました。

Package template (html/template) implements data-driven templates for generating HTML output safe against code injection. It provides the same interface as package text/template and should be used instead of text/template whenever the output is HTML.

The documentation here focuses on the security features of the package. For information about how to program the templates themselves, see the documentation for text/template.

パッケージテンプレート(html/template)は、コードインジェクションに対して安全なHTML出力を生成するためのデータ駆動型テンプレートを実装します。 パッケージtext/templateと同じインターフェイスを提供し、出力がHTMLの場合はtext/templateの代わりに使用する必要があります。

ここのドキュメントは、パッケージのセキュリティ機能に焦点を当てています。テンプレート自体のプログラミング方法については、text/templateのドキュメントを参照してください。

html/templateとtext/templateは同じインターフェースを提供していて、記法についても同様のものを利用することがわかります。 記法についてはtext/templateのGoDocに記述されているようです。

text/templateの記法を覚える

html/templateの記法はtext/templateの記法と同じようなので、text/templateのGoDocを参照しながら記法について学んでいきます。

https://golang.org/pkg/text/template/

サンプルの実行

まずはサンプルを実行してみます。

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)
	}
}
$ go run main.go
17 items are made of wool

NewとParseメソッドの定義は以下のようになっていました。

func New(name string) *Template

func (t *Template) Parse(text string) (*Template, error)

template.New(“test”)でTemplate構造体を初期化して、Template構造体のParseメソッドでテンプレートの文字を読み込みます。

Executeメソッドの定義は以下のようになっていました。

func (t *Template) Execute(wr io.Writer, data interface{}) error

第一引数にio.Writer, 第二引数にinterface{}を渡し、io.Writerに対して、テンプレートに値を埋め込んだ結果を出力しています。 今回の例ではos.Stdoutを渡したので標準出力に 17 items are made of woolの文字列が出力されました。

GoDocでは{{}}のことをActionsと定義していました。

Actions

ここからはGoDocを参考にしながら、Actionsについて記述していきます。 template.New("test").Parse("xxxxxxxx")xxxxxxxxの部分を変えながら実行した結果も載せています。 (以降、xxxxxxxxの部分を本ブログでは入力テンプレートと読ぶことにします。)

値の出力

{{ pipeline }}

通常の文字出力です。

入力テンプレートを{{ 1 }}として実行すると1が出力されました。 入力テンプレートを{{ "hoge" }}として実行するとhogeが出力されました。

また、Executeの第二引数で渡された値がboolean, string, character, int, floatであれば{{.}}でアクセスすることができます。

入力テンプレートを{{.}}とし、Executeの第二引数に1を渡すと、1が出力されました。

また、Executeの第二引数で渡された値が構造体であれば、{{.フィールド名}}でフィールドにアクセスできます。

コメント

{{/*  */}}

上記でコメントを表します。

入力テンプレートを{{/* hoge */}}として実行すると何も出力されませんでした。

ホワイトスペースのトリミング

{{-  -}}

{{- -}}で前後のホワイトスペースを削除します。ホワイトスペースは改行も含むそうです。 入力テンプレートをhoge {{- fuga -}} hogeとし実行するとhogefugahogeが出力されました。

if

{{if pipeline}} T1 {{end}}

pipelineにfalse, 0, nilポインタや長さ0の配列やスライス、マップ、文字列が入るとT1は実行されません。 {{ if "hoge" }} fuga {{ end }}で実行するとfugaが出力されました。

以下のように書くことでelse句も利用できます。

{{if pipeline}} T1 {{else}} T2 {{end}}

range

{{range pipeline}} T1 {{end}}

rangeはpipelineに配列、スライス、マップ、チャネルが入る時のみ利用できます。 配列やスライス要素数分だけ、T1が実行されます。 要素は.に代入されるようです。

入力テンプレートを{{ range . }} {{.}}hoge {{ end }}とし、Executeの第二引数に[]int{1, 2, 3, 4}を渡して実行すると、 1hoge 2hoge 3hoge 4hogeが出力されました。

defineとtemplate

defineは名前付きテンプレートを定義できます。 名前付きテンプレートはtemplateを利用することで実行できます。

{{define "T1"}} T1 {{end}}
{{template "T1"}}

入力テンプレートを{{define "T1"}}ONE{{end }}{{template "T1" }}{{template "T1" }}とし、実行するとONEONEが出力されました。

また以下のActionsを利用して、defineで定義した名前付きテンプレートに引数を渡すことができるようです。

{{define "T1"}} {{.}} T1 {{end}}
{{template "T1" pipeline}}

入力テンプレートを{{define "T1"}} {{.}} ONE {{end }}{{template "T1" "ZERO"}}とし、実行するとZERO ONEが出力されました。

block

{{block "name" pipeline}} T1 {{end}}

blockは{{ define "name" }} T1 {{end}}{{template "name" pipline }}を合わせたものです。

入力テンプレートを{{block "T1" "ZERO"}} {{.}} ONE {{end }}とし、実行するとZERO ONEが出力されました。

with

{{with pipeline}} T1 {{end}}

withはifと同様にpiplineがempty出なければT1を出力します。 また、.でpipelineの値を参照できます。

入力テンプレートを{{with "ZERO"}} {{.}} ONE {{end}}とし、実行するとZERO ONEが出力されました。

変数

{{$variable := pipeline}}
$variable := piplineとすることで$variableに値が代入されます。

入力テンプレートを{{$a := "hoge"}}{{$a}}とし、実行するとhogeが出力されました。

関数呼び出し

{{funcName}}

テンプレート内で利用できる関数が予め、定義されており、その関数を呼び出すことが可能です。 利用できる関数はGoDocのFunctionsを参照してみてください。

まとめ

GoDocにはtext/templateの記法が体系的に載っていてとてもわかりやすかったです。 また、これらの記法を覚えておくことでHugoのレイアウトテンプレートもかなり読みやすくなるなと感じました。

以上です。