はじめに
TIGの辻です。Go1.23連載の5本目です。
この記事では、マイナーアップデートから text/template
パッケージを取り上げて紹介します。
text/template のアップデート内容
- テンプレートが新しいアクション “else with” をサポートするようになりました(#57646)
テンプレートが新しいアクション “else with” をサポートするようになりました(#57646)
text/template
はテンプレートを用意し、データをテンプレートの値として当てはめて、テキスト出力できる便利なパッケージです。
FutureのいくつかのOSSでも、この text/template
パッケージを利用しています。たとえば future-architect/vuls
があります。
text/template
パッケージで利用できる以下のようなテンプレートを用意し、このテンプレートにデータを当てはめて、結果の文字列を取得できます。
const mdTemplate =
</span></span><br><span class="line"><span class="string">{{.CveID}}</span></span><br><span class="line"><span class="string">================</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">CVSS Scores</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{.Cvsses }}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">{{if .SSVC}}</span></span><br><span class="line"><span class="string">SSVC</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range $ssvc := .SSVC -}}</span></span><br><span class="line"><span class="string">* {{$ssvc.Type}}</span></span><br><span class="line"><span class="string"> Exploitation : {{$ssvc.Value.Exploitation}}</span></span><br><span class="line"><span class="string"> Automatable : {{$ssvc.Value.Automatable}}</span></span><br><span class="line"><span class="string"> TechnicalImpact : {{$ssvc.Value.TechnicalImpact}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Summary</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string"> {{.Summary }}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Mitigation</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{.Mitigation }}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Primary Src</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range $link := .Links -}}</span></span><br><span class="line"><span class="string">* {{$link}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">Patch</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range $url := .PatchURLs -}}</span></span><br><span class="line"><span class="string">* {{$url}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">CWE</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range .Cwes -}}</span></span><br><span class="line"><span class="string">* {{.En.CweID}} [{{.En.Name}}](https://cwe.mitre.org/data/definitions/{{.En.CweID}}.html)</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">{{range $name := .CpeURIs -}}</span></span><br><span class="line"><span class="string">* {{$name}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">Confidence</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range $confidence := .Confidences -}}</span></span><br><span class="line"><span class="string">* {{$confidence.Score}} / {{$confidence.DetectionMethod}}</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string">References</span></span><br><span class="line"><span class="string">-----------</span></span><br><span class="line"><span class="string">{{range .References -}}</span></span><br><span class="line"><span class="string">* [{{.Source}}]({{.Link}})</span></span><br><span class="line"><span class="string">{{end}}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">
さて、テンプレート内でデータを評価するために利用できるアクション(≒構文)がいくつかあります。if
や range
with
break
などといったものです。今回のアップデートでは with
に関するアクション else with
が追加になりました。
withアクションとは
そもそも with
のアクションを利用したことがない方も多いと思いますので、本記事で簡単に触れておきます。ちなみに私も今回の記事を書くまではテンプレートで with
というアクションがあることを知りませんでした。
ドキュメントには以下のように記載があります。pipeline が空であれば、出力は生成されません。空でない場合はドットに pipeline の結果がセットされて、T1 が実行されます。
{{with pipeline}} T1 {{end}} |
テンプレートで条件分岐したい場合 if
を利用すれば十分である場合が多そうですが with
が有用である例として、人物名に対応するフルーツ名をテンプレート内で取得するような、次のテンプレートを考えてみます。
package main |
テンプレート内で関数を評価をして、その結果を .
で参照できるため、テンプレート内のコードがシンプルになります。もちろん with
ではなく if
を利用しても以下のように書けますが、若干テンプレートのコードが冗長になります。
const sampleTemplate = ` |
Go 1.22までのwith
さて Go 1.22 までは with
を利用したアクションは以下の2種類でした。
{{with pipeline}} T1 {{end}}
{{with pipeline}} T1 {{else}} T0 {{end}}
今回のアップデートは、上記の2つのアクションに加えて else with
として分岐できるようにしたい、ということです。if
を利用したアクションでは以下の3種類ありますが with
の場合は2種類で else with
は存在しませんでした。
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
この機能追加があると何が嬉しい?
例で紹介したように、テンプレート内で複雑な条件分岐している場合に、コードがシンプルになります。
#57646 のProposalを上げている willfaught さんはGoの静的サイトジェネレータであるHugoのテーマ Paige を開発されている方です。Goのテンプレートを駆使しており、実際のテンプレートのコードとして以下があったようです。
{{ with $page.Resources.GetMatch $url }} |
現実問題として、こういうコードを書かざるを得ないが、困っている、というのは Issue としてインパクトがあります。pipeline に関数を利用しており、変数 $url
を関数に渡して、結果が存在すればその結果を $result
変数に値を格納しています。こういったケースでは if
ではなく with
が有用と感じますが Go 1.22 の構文だけですとネストが深くなってしまい、テンプレートのコードが複雑になります。
with else
が導入されると上記のテンプレートコードは以下のように書き直せます。シンプルです。#57646 に記載があるこのようなユースケースを考慮して、機能追加は問題ない、としてGo1.23でリリースとなりました。
{{ with $page.Resources.GetMatch $url }} |
ちなみに else with
が利用できない場合、上記のテンプレートのコードは if
を利用して次のように記述できるでしょう。
{{ if $page.Resources.GetMatch $url }}
{{ $result = $page.Resources.GetMatch $url }}
{{ else if $page.Resources.Get $url }}
{{ $result = $page.Resources.Get $url }}
{{ else if resources.GetMatch $url }}
{{ $result = resources.GetMatch $url }}
{{ else if resources.Get $url }}
{{ $result = resources.Get $url }}
{{ else if resources.GetRemote $url }}
{{ $result = resources.GetRemote $url }}
{{ end }}
おわりに
text/template
のアップデートについて触れてみました。
with
を利用しているテンプレートで複雑な条件分岐が必要なケースでは、今回のシンタックスシュガーの導入でコードがシンプルに記述できるようになるでしょう。今回のUpdateでは text/template
パッケージを利用したテンプレートが複雑である場合に特に嬉しいアップデートと感じました。
次は市川さんのos.CopyFS & path/filepathです。