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

# はじめに

前回投稿した「Go Template の基本的な動作を見てみよう」の続きです。

https://qiita.com/_otakakot_/items/3ca4eba2576bc5bb1f30

前回の記事で Go Template に与えた値がどのように展開(評価)されるのかを確認してみました。
今回の記事は、[Actions](https://pkg.go.dev/text/template#hdr-Actions)における制御構文について挙動を見ていこうと思います。

※ 本記事では[text/template](https://pkg.go.dev/text/template)を用いて説明しますが、HTMLを出力する場合はコード・インジェクションに対応している[html/template](https://pkg.go.dev/html/template)を用いる必要があります。

# おさらい（基本文法）

[公式ドキュメント](https://pkg.go.dev/text/template)に記載されているコードをもとに Go Template の基本文法を振り返ります。

```go
package main

import (
	"html/template"
	"os"
)

// 0. テンプレート内部で展開したい構造体(型)を定義
type Inventory struct {
	Material string
	Count    uint
}

func main() {
    // 1. テンプレート内部で展開したい値を指定
	sweaters := Inventory{"wool", 17}

    // 2. テンプレート文字列を定義
	tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")

	if err != nil {
		panic(err)
	}

    // 3. 展開および出力
	err = tmpl.Execute(os.Stdout, sweaters)
	if err != nil {
		panic(err)
	}
}
```

上記の流れを追うことで Go Template は利用することができます。

# Actionsとは？

公式ドキュメントの[overview](https://pkg.go.dev/text/template#pkg-overview)に以下の記載があります。

> "Actions"--data evaluations or control structures--

データ評価または制御構文のことを示します。
前回の記事で解説した[Go Template の基本的な動作を見てみよう](https://qiita.com/_otakakot_/items/3ca4eba2576bc5bb1f30)はデータ評価であるため`Actions`であったと言えます。
今回は`Actions`の中でも「制御構文」に着目した内容となります。

# 比較演算子

`Actions`の説明に入るまえに、Go Template での比較演算子ついて確認しておきます。
Go Template では以下の比較演算子が用意されていて利用することができます。

- `eq` (`==`): equal
- `ne` (`!=`): not equal
- `lt` (`<` ): less than
- `le` (`<=`): less than or equal
- `gt` (`>` ): greater than
- `ge` (`>=`): greater than or equal

比較演算子は[Functions](https://pkg.go.dev/text/template#hdr-Functions)に分類される構文であり `{{ eq .Arg1 .Arg2 }}` のように記述します。
`<比較演算子> <第一引数> <第二引数>`となります。`lt`や`gt`などの場合は第一引数は第二引数より小さい(大きい)と考えるとよさそうです。

実際のコードは以下のようになります。

```go
package main

import (
	"html/template"
	"os"
)

type Args struct {
	Arg1 int
	Arg2 int
}

func main() {
	args := Args{Arg1: 1, Arg2: 2}

	comparison := "{{ lt .Arg1 .Arg2 }}"

	tmpl, err := template.New("test").Parse(comparison)
	if err != nil {
		panic(err)
	}

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

上記のコードを実行すると以下のように出力されます。

```shell
true
```

# Actionsの種類

`Actions`には以下の種類があります。それぞれ使い方を確認していきます。

## comment

`/* */`で囲むことでコメントを記述することができます。

```go
tmpl, _ := template.New("test").Parse("{{/* comment */}}") // comment
_ = tmpl.Execute(os.Stdout, map[string]string{"Arg1": "1", "Arg2": "2"})

// 出力 ※なにも表示されない
// 
```

## pipeline

前回の記事で解説した`{{ .Arg }}`のような記述をpipelineと呼びます。
シンプルに与えた値が評価されます。

```go
tmpl, _ := template.New("test").Parse("{{ .Arg }}")
_ = tmpl.Execute(os.Stdout, map[string]string{"Arg": "Hello, World"})

// 出力
// Hello, World
```

## if pipeline T1 end

`if`はpipelineの評価が`true`の場合に内容(`if end`で囲まれた記述)を出力します。

```go
tmpl, _ := template.New("test").Parse("{{if .Bool}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, map[string]bool{"Bool": true})

// 出力
// T1
```

## if pipeline T1 else T0 end

`if else`はpipelineの評価が`true`の場合に`if`で囲まれた記述を出力し`false`の場合は`else`の内容を出力します。

```go
tmpl, _ := template.New("test").Parse("{{if .Bool}}T1{{else}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, map[string]bool{"Bool": false})

// 出力
// T0
```

## if pipeline T1 else if pipeline T0 end

`if else if`はpipelineの評価が`true`の場合に`if`で囲まれた記述を出力し`false`の場合は`else if`のpipelineを評価し内容を出力します。

```go
tmpl, _ := template.New("test").Parse("{{if .Bool1}}T1{{else if .Bool2}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, map[string]bool{"Bool1": false, "Bool2": true})

// 出力
// T0
```

`.Bool1`は`false`であるため無視され`Bool2`が`true`であるため`T0`が出力されます。

## range pipeline T1 end

`range`はpipelineが`array`,`slice`,`map`,`channel`の場合に内容を繰り返し出力します。

```go
tmpl, _ := template.New("test").Parse("{{range .}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})

// 出力
// T1T1T1
```

上記実装の場合は与えた内容と関係なく配列の長さだけ`T1`が出力されます。
空要素を与えた場合はなにも出力されません。

```go
tmpl, _ := template.New("test").Parse("{{range .}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})

// 出力 ※なにも表示されない
//
```

与えた内容を出力するには以下のように記述します。

```go
tmpl, _ := template.New("test").Parse("{{range $index, $element := .}}index: {{$index}} element: {{$element}}\n{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})

// 出力
// index: 0 element: a
// index: 1 element: b
// index: 2 element: c
```

`$index`および`$element`を定義し`{{$index}}`および`{{$element}}`で参照することで配列の内容を出力することができます。

## range pipeline T1 else T0 end

`range else`は`range`と同じくpipelineが`array`,`slice`,`map`,`channel`の場合に内容を繰り返し出力します。また、pipelineが空の場合は`else`の内容を出力します。

```go	
tmpl, _ := template.New("test").Parse("{{range .}}T1{{else}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})

// 出力
// T0
```

## break

`break`は`range`ループの際に早期でループを終了することができます。`if end`と組み合わせて使用することで条件によってループを終了することができます。

```go
tmpl, _ := template.New("test").Parse("{{range $i, $e := .}}{{if eq $i 1}}{{break}}{{end}}{{$e}}{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})

// 出力
// a
```

`$i`が`1`と等しい場合に`break`が実行されループが終了します。そのため配列`0`番目の`a`は出力さされ`1`以降の`b`と`c`は出力されません。

## continue

`continue`は`range`ループの際に実行をスキップすることができます。`if end`と組み合わせて使用することで条件によってループをスキップすることができます。

```go
tmpl, _ := template.New("test").Parse("{{range $i, $e := .}}{{if eq $i 1}}{{continue}}{{end}}{{$e}}{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})

// 出力
// ac
```

`$i`が`1`と等しい場合に`continue`が実行されます。そのため配列`1`番目の`b`の出力がスキップされ`a`と`c`が出力されます。

## with pipeline T1 end

`with`はpipelineの値が`nil`や空でない場合に内容を出力します。

```go
tmpl, _ := template.New("test").Parse("{{with .}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})

// 出力
// T1
```

公式ドキュメントには`pipeline is empty`と記載がありますが, `""`や`0`などのゼロ値は`empty`とみなされるようです。

## with pipeline T1 else T0 end

`with else`は`with`と同じくpipelineの値が`nil`や空でない場合に内容を出力します。また、pipelineが空の場合は`else`の内容を出力します。

```go
tmpl, _ := template.New("test").Parse("{{with .}}T1{{else}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})

// 出力
// T0
```

与えた値が空配列であるため`T0`が出力されます。

# おわりに

Go Template について記事を書いてみたことによりだいぶ仲良くなれた気がします。
そろそろ Go Template を駆使してコード自動生成なんかにも挑戦してみたいです。
（今だとChatGPTにコード生成させた方が早いですかね...）