[インデックス 10497] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template パッケージのAPIを大幅に簡素化し、再設計するものです。特に、複数のテンプレートを管理するための Set 型が廃止され、テンプレート間の関連付けがより自動的かつ直感的な方法で行われるようになります。これにより、テンプレートの構築APIの複雑さが劇的に軽減されます。
コミット
- コミットハッシュ:
f56db6f534759b211666f2218da1d44d7abbdd54 - Author: Rob Pike r@golang.org
- Date: Wed Nov 23 20:17:22 2011 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f56db6f534759b211666f2218da1d44d7abbdd54
元コミット内容
text/template: new, simpler API
The Set type is gone. Instead, templates are automatically associated by
being parsed together; nested definitions implicitly create associations.
Only associated templates can invoke one another.
This approach dramatically reduces the breadth of the construction API.
For now, html/template is deleted from src/pkg/Makefile, so this can
be checked in. Nothing in the tree depends on it. It will be updated next.
R=dsymonds, adg, rsc, r, gri, mikesamuel, nigeltao
CC=golang-dev
https://golang.org/cl/5415060
変更の背景
このコミットの主な目的は、Go言語の text/template パッケージのAPIをよりシンプルで使いやすいものにすることです。以前のバージョンでは、複数のテンプレートをまとめて管理するために Set 型が導入されていましたが、これがAPIの複雑さを増す要因となっていました。
具体的には、以下のような課題がありました。
Set型の存在による複雑性: テンプレートをグループ化し、互いに参照させるためにSet型を明示的に作成・管理する必要がありました。これにより、テンプレートの初期化やパースのコードが冗長になりがちでした。- テンプレート関連付けの明示的な管理: テンプレートが別のテンプレートを呼び出す場合(例:
{{template "name"}})、その「name」がどのSetに属するかを意識する必要がありました。 - APIの広範さ:
Set型とTemplate型の両方に類似のパースメソッドや実行メソッドが存在し、どちらを使うべきか、あるいはどのように連携させるべきかという点で混乱を招く可能性がありました。
このコミットは、これらの課題を解決し、テンプレートの関連付けをより自動的かつ透過的に行うことで、APIの学習コストと使用時の複雑さを軽減することを目指しています。
前提知識の解説
Go言語の text/template パッケージ
text/template パッケージは、Go言語でテキストベースの出力を生成するためのテンプレートエンジンを提供します。HTML、XML、プレーンテキストなど、様々な形式のドキュメントを動的に生成するのに使用されます。
主要な概念は以下の通りです。
- テンプレート (Template): プレースホルダーや制御構造(条件分岐、ループなど)を含むテキスト。
- アクション (Actions):
{{と}}で囲まれた部分で、データの表示、制御構造の定義、関数の呼び出しなどを行います。 - パイプライン (Pipelines): 複数のコマンドを
|で連結し、前のコマンドの出力を次のコマンドの入力として渡す機能。 - 関数 (Functions): テンプレート内で呼び出せるGoの関数。カスタム関数を登録することも可能です。
- データ (Data): テンプレートに渡されるGoの任意のデータ構造(構造体、マップ、スライスなど)。テンプレートは、このデータを使ってプレースホルダーを埋めたり、条件分岐を評価したりします。
テンプレートのパースと実行
- パース (Parsing): テンプレート文字列を読み込み、Goの内部データ構造(
parse.Tree)に変換するプロセスです。この段階で、テンプレートの構文チェックが行われます。 - 実行 (Execution): パースされたテンプレートと入力データを結合し、最終的なテキスト出力を生成するプロセスです。
テンプレートの関連付けと {{template "name"}} アクション
text/template では、一つのテンプレートから別の名前付きテンプレートを呼び出すことができます。これは {{template "name"}} アクションを使って行われます。例えば、共通のヘッダーやフッターを別のテンプレートとして定義し、メインのテンプレートからそれらを呼び出すといった用途に利用されます。
この機能を実現するためには、呼び出される側のテンプレートが、呼び出す側のテンプレートと同じ「コンテキスト」または「セット」に属している必要がありました。このコミット以前は、この「セット」の概念が Set 型によって明示的に管理されていました。
以前の Set 型の役割 (このコミットで削除される概念)
このコミット以前の text/template パッケージには、Set という型が存在しました。Set は、複数の Template オブジェクトをまとめて管理するためのコンテナでした。
- テンプレートのグループ化: 関連する複数のテンプレート(例: ウェブサイトの全ページテンプレート)を一つの
Setオブジェクトにまとめることができました。 - 名前による参照:
Set内のテンプレートは名前で識別され、{{template "name"}}アクションを使ってSet内の別のテンプレートを呼び出すことができました。 - 共通の関数マップ:
Setに関数マップを登録することで、そのSetに属する全てのテンプレートで共通の関数を利用できました。 - パースメソッド:
Set.ParseやSet.ParseFiles、Set.ParseGlobといったメソッドがあり、複数のテンプレート定義を含む文字列やファイルからSetを構築できました。
しかし、この Set 型の存在が、APIの複雑さの一因となっていました。開発者は Template と Set の両方の概念を理解し、適切に使い分ける必要がありました。
技術的詳細
このコミットの技術的な核心は、Set 型の廃止と、それに伴うテンプレートの関連付けメカニズムの根本的な変更です。
-
Set型の完全な削除:src/pkg/text/template/set.goおよびsrc/pkg/text/template/set_test.goファイルが完全に削除されました。これは、Set型がライブラリから完全に廃止されたことを意味します。src/pkg/Makefileからhtml/templateの参照が一時的に削除されていますが、これはtext/templateの変更を先行してチェックインするための措置であり、html/templateも追って新しいAPIに更新される予定です。
-
Template型への機能統合とcommon構造体の導入:- 以前
Setが担っていた複数のテンプレートの管理機能が、Template型自体に統合されました。 src/pkg/text/template/template.goが新規作成され、新しいTemplate構造体が定義されています。このTemplate構造体は、*parse.Tree(パースされたテンプレートの構文木) と、新たに導入された*commonフィールドを持ちます。common構造体は、関連するテンプレート間で共有される情報(名前付きテンプレートのマップtmpl、パース時および実行時の関数マップparseFuncs,execFuncs)を保持します。これにより、Setが提供していた機能が、Templateオブジェクトの内部で透過的に管理されるようになります。
- 以前
-
自動的なテンプレート関連付け:
Template.New(name string)メソッドが、既存のTemplateオブジェクトに関連付けられた新しいTemplateオブジェクトを作成する役割を担うようになりました。これにより、Newで作成されたテンプレートは、親テンプレートと同じcommon構造体を共有し、互いに参照可能になります。Template.Parse(text string)メソッドは、与えられたテキストをパースし、その中に{{define "name"}}...{{end}}形式で定義されたネストされたテンプレートがあれば、それらを自動的に現在のTemplateオブジェクトに関連付けます。これにより、明示的にSetに追加する手間がなくなります。ParseFiles(filenames ...string)およびParseGlob(pattern string)関数/メソッドは、複数のファイルからテンプレートをパースする際に、自動的にそれらのテンプレートを関連付けられた状態にします。返されるTemplateオブジェクトは、パースされた最初のファイルのテンプレートになります。
-
ExecuteTemplateメソッドの追加:Template型にExecuteTemplate(wr io.Writer, name string, data interface{}) errorメソッドが追加されました。これは、現在のTemplateオブジェクトに関連付けられた、指定された名前のテンプレートを実行するためのものです。以前Set.Executeが担っていた機能が、Template型に直接提供されるようになりました。
-
関数検索ロジックの簡素化:
src/pkg/text/template/funcs.go内のfindFunction関数が変更され、引数から*Setが削除されました。これにより、関数はTemplateオブジェクトの関数マップとグローバル関数マップのみから検索されるようになります。
-
リフレクション関連の最適化:
src/pkg/text/template/exec.goからmethodByNameヘルパー関数が削除されました。これは、Go 1でreflectパッケージにMethodByNameが導入されたため、カスタム実装が不要になったためです。
これらの変更により、テンプレートの管理と使用が大幅に簡素化され、開発者は Set の概念を意識することなく、より直感的に複数のテンプレートを扱うことができるようになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/text/template/doc.go:Set型に関する説明が削除され、新しい「Associated templates」(関連付けられたテンプレート)の概念が導入されています。- テンプレートが並行して安全に実行できること、関数がテンプレートとグローバルマップから検索されることなどが更新されています。
{{define}}アクションによるネストされたテンプレート定義と、それらがどのように関連付けられるかの説明が追加されています。
-
src/pkg/text/template/exec.go:Template型にExecuteTemplateメソッドが追加され、指定された名前の関連テンプレートを実行できるようになりました。walkTemplate関数内で、テンプレートの検索がs.tmpl.tmpl[t.Name]に変更され、Setへの参照がなくなりました。findFunctionの呼び出しからset引数が削除されました。methodByNameヘルパー関数が削除され、reflect.Value.MethodByNameが直接使用されるようになりました。
-
src/pkg/text/template/funcs.go:findFunction関数のシグネチャがfunc findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool)からfunc findFunction(name string, tmpl *Template) (reflect.Value, bool)に変更され、Set型への依存がなくなりました。
-
src/pkg/text/template/helper.go:Set型に関連する多くのヘルパー関数(SetMust,ParseSetFiles,ParseSetGlob,ParseTemplateFiles,ParseTemplateGlobなど)が削除されました。ParseFilesおよびParseGlob関数/メソッドが*Templateを返すように変更され、内部でparseFilesおよびparseGlobヘルパー関数を呼び出すようになりました。これらのヘルパー関数は、Templateオブジェクトを引数に取り、複数のファイルをパースしてテンプレートを関連付けるロジックを含みます。
-
src/pkg/text/template/multi_test.go(新規追加):- 複数のテンプレートのパースと実行に関する新しいテストケースが追加されました。これは、新しいAPIの動作を検証するために不可欠です。
TestParseFiles,TestParseGlob,TestParseFilesWithData,TestParseGlobWithData,TestCloneなどのテストが含まれています。
-
src/pkg/text/template/parse.go(削除):- このファイルは完全に削除されました。以前は
Template構造体の定義と基本的なパースロジックを含んでいましたが、これらの機能はsrc/pkg/text/template/template.goに統合されました。
- このファイルは完全に削除されました。以前は
-
src/pkg/text/template/set.go(削除):- このファイルは完全に削除されました。
Set型の廃止を明確に示しています。
- このファイルは完全に削除されました。
-
src/pkg/text/template/template.go(新規追加):- 新しい
Template構造体が定義されました。これにはname,*parse.Tree,*common,leftDelim,rightDelimフィールドが含まれます。 common構造体は、関連するテンプレート間で共有されるマップ (tmpl,parseFuncs,execFuncs) を保持します。New(name string): 新しいテンプレートを作成します。New(name string)(メソッド): 既存のテンプレートに関連付けられた新しいテンプレートを作成します。init():common構造体を初期化します。Clone(): テンプレートとその関連テンプレートの複製を作成します。Delims(),Funcs(): デリミタと関数マップを設定します。Template(name string): 指定された名前の関連テンプレートを返します。Parse(text string): テンプレート文字列をパースし、ネストされた定義を自動的に関連付けます。associate(new *Template): 新しいテンプレートをグループに追加します。名前の重複チェックも行います。isEmpty(n parse.Node): テンプレートツリーが空であるかをチェックするヘルパー関数。
- 新しい
コアとなるコードの解説
このコミットの核となる変更は、text/template パッケージの内部構造と外部APIの両方に及びます。
Template 構造体の再定義と common 構造体の導入
以前は Template と Set という2つの主要な型がありましたが、この変更により Set は廃止され、Template 型がテンプレート管理の中心となります。
新しい Template 構造体 (src/pkg/text/template/template.go に定義) は以下のようになります。
type Template struct {
name string
*parse.Tree
*common // 関連するテンプレート間で共有される情報
leftDelim string
rightDelim string
}
type common struct {
tmpl map[string]*Template // 名前付きテンプレートのマップ
parseFuncs FuncMap
execFuncs map[string]reflect.Value
}
*commonフィールドが導入されたことで、複数のTemplateオブジェクトが同じcommonインスタンスを共有できるようになりました。これにより、Setが担っていた「テンプレートのグループ化」と「共通の関数マップ」の機能が、Templateオブジェクト自体に統合され、より透過的に扱われます。tmplマップは、このcommonインスタンスに関連付けられた全ての名前付きテンプレートを保持します。
テンプレートの関連付けメカニズム
新しいAPIでは、テンプレートの関連付けはより自動的かつ暗黙的に行われます。
-
Template.New(name string)メソッド:t.New(name)のように呼び出すことで、既存のTemplatetと同じcommon構造体を共有する新しいTemplateオブジェクトが作成されます。- これにより、
tと新しく作成されたテンプレートは自動的に関連付けられ、互いに{{template "name"}}で呼び出すことが可能になります。
-
Template.Parse(text string)メソッド:- このメソッドは、与えられたテンプレート文字列をパースします。
- 文字列内に
{{define "name"}}...{{end}}の形式で別のテンプレートが定義されている場合、Parseメソッドはそれらの定義を自動的に抽出し、現在のTemplateオブジェクトのcommon.tmplマップに関連付けます。 - これにより、開発者は
Setに明示的にテンプレートを追加する代わりに、単一のParse呼び出しで複数の関連テンプレートを定義・登録できるようになります。
テンプレートの実行
Template.Execute(wr io.Writer, data interface{}): これは以前から存在するメソッドで、現在のTemplateオブジェクト自体を実行します。Template.ExecuteTemplate(wr io.Writer, name string, data interface{}): 新しく追加されたメソッドです。現在のTemplateオブジェクトに関連付けられた、指定されたnameのテンプレートを実行します。これは、以前Set.Execute(wr, name, data)が行っていた機能に相当します。
ParseFiles と ParseGlob の変更
helper.go の ParseFiles と ParseGlob 関数も変更されました。これらはもはや *Set を返さず、*Template を返します。
func ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(nil, filenames...)
}
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(t, filenames...)
}
- これらの関数は、指定されたファイル群をパースし、それらを自動的に関連付けられたテンプレートとして返します。返される
Templateオブジェクトは、パースされた最初のファイルのテンプレートになります。 - これにより、ファイルから複数のテンプレートを読み込む際も、
Setを意識することなく、単一のTemplateオブジェクトを通じて全ての関連テンプレートにアクセスできるようになります。
まとめ
このコミットは、text/template パッケージのAPIを大幅に簡素化し、より直感的な使用を可能にしました。Set 型の廃止と Template 型への機能統合、そして自動的なテンプレート関連付けメカニズムの導入により、テンプレートの管理と実行がよりシームレスに行えるようになっています。これにより、Go言語でテンプレートを使用する際の開発体験が向上しました。
関連リンク
- Go CL 5415060: https://golang.org/cl/5415060
参考にした情報源リンク
- コミットメッセージ
- Go言語の
text/templateパッケージのドキュメント (変更前後の比較) - Go言語のリフレクションに関する一般的な知識
- Go言語の
filepath.Glob関数に関する一般的な知識