[インデックス 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)
のように呼び出すことで、既存のTemplate
t
と同じ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
関数に関する一般的な知識