はじめに
前回投稿した「Go Template の基本的な動作を見てみよう」の続きです。
前回の記事で Go Template に与えた値がどのように展開(評価)されるのかを確認してみました。
今回の記事は、Actionsにおける制御構文について挙動を見ていこうと思います。
※ 本記事ではtext/templateを用いて説明しますが、HTMLを出力する場合はコード・インジェクションに対応しているhtml/templateを用いる必要があります。
おさらい(基本文法)
公式ドキュメントに記載されているコードをもとに Go Template の基本文法を振り返ります。
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に以下の記載があります。
"Actions"--data evaluations or control structures--
データ評価または制御構文のことを示します。
前回の記事で解説したGo Template の基本的な動作を見てみようはデータ評価であるため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に分類される構文であり {{ eq .Arg1 .Arg2 }}
のように記述します。
<比較演算子> <第一引数> <第二引数>
となります。lt
やgt
などの場合は第一引数は第二引数より小さい(大きい)と考えるとよさそうです。
実際のコードは以下のようになります。
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)
}
}
上記のコードを実行すると以下のように出力されます。
true
Actionsの種類
Actions
には以下の種類があります。それぞれ使い方を確認していきます。
comment
/* */
で囲むことでコメントを記述することができます。
tmpl, _ := template.New("test").Parse("{{/* comment */}}") // comment
_ = tmpl.Execute(os.Stdout, map[string]string{"Arg1": "1", "Arg2": "2"})
// 出力 ※なにも表示されない
//
pipeline
前回の記事で解説した{{ .Arg }}
のような記述をpipelineと呼びます。
シンプルに与えた値が評価されます。
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
で囲まれた記述)を出力します。
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
の内容を出力します。
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を評価し内容を出力します。
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
の場合に内容を繰り返し出力します。
tmpl, _ := template.New("test").Parse("{{range .}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, []string{"a", "b", "c"})
// 出力
// T1T1T1
上記実装の場合は与えた内容と関係なく配列の長さだけT1
が出力されます。
空要素を与えた場合はなにも出力されません。
tmpl, _ := template.New("test").Parse("{{range .}}T1{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})
// 出力 ※なにも表示されない
//
与えた内容を出力するには以下のように記述します。
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
の内容を出力します。
tmpl, _ := template.New("test").Parse("{{range .}}T1{{else}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})
// 出力
// T0
break
break
はrange
ループの際に早期でループを終了することができます。if end
と組み合わせて使用することで条件によってループを終了することができます。
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
と組み合わせて使用することで条件によってループをスキップすることができます。
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
や空でない場合に内容を出力します。
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
の内容を出力します。
tmpl, _ := template.New("test").Parse("{{with .}}T1{{else}}T0{{end}}")
_ = tmpl.Execute(os.Stdout, []string{})
// 出力
// T0
与えた値が空配列であるためT0
が出力されます。
おわりに
Go Template について記事を書いてみたことによりだいぶ仲良くなれた気がします。
そろそろ Go Template を駆使してコード自動生成なんかにも挑戦してみたいです。
(今だとChatGPTにコード生成させた方が早いですかね...)