Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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の複雑さを増す要因となっていました。

具体的には、以下のような課題がありました。

  1. Set 型の存在による複雑性: テンプレートをグループ化し、互いに参照させるために Set 型を明示的に作成・管理する必要がありました。これにより、テンプレートの初期化やパースのコードが冗長になりがちでした。
  2. テンプレート関連付けの明示的な管理: テンプレートが別のテンプレートを呼び出す場合(例: {{template "name"}})、その「name」がどの Set に属するかを意識する必要がありました。
  3. APIの広範さ: Set 型と Template 型の両方に類似のパースメソッドや実行メソッドが存在し、どちらを使うべきか、あるいはどのように連携させるべきかという点で混乱を招く可能性がありました。

このコミットは、これらの課題を解決し、テンプレートの関連付けをより自動的かつ透過的に行うことで、APIの学習コストと使用時の複雑さを軽減することを目指しています。

前提知識の解説

Go言語の text/template パッケージ

text/template パッケージは、Go言語でテキストベースの出力を生成するためのテンプレートエンジンを提供します。HTML、XML、プレーンテキストなど、様々な形式のドキュメントを動的に生成するのに使用されます。

主要な概念は以下の通りです。

  • テンプレート (Template): プレースホルダーや制御構造(条件分岐、ループなど)を含むテキスト。
  • アクション (Actions): {{}} で囲まれた部分で、データの表示、制御構造の定義、関数の呼び出しなどを行います。
  • パイプライン (Pipelines): 複数のコマンドを | で連結し、前のコマンドの出力を次のコマンドの入力として渡す機能。
  • 関数 (Functions): テンプレート内で呼び出せるGoの関数。カスタム関数を登録することも可能です。
  • データ (Data): テンプレートに渡されるGoの任意のデータ構造(構造体、マップ、スライスなど)。テンプレートは、このデータを使ってプレースホルダーを埋めたり、条件分岐を評価したりします。

テンプレートのパースと実行

  1. パース (Parsing): テンプレート文字列を読み込み、Goの内部データ構造(parse.Tree)に変換するプロセスです。この段階で、テンプレートの構文チェックが行われます。
  2. 実行 (Execution): パースされたテンプレートと入力データを結合し、最終的なテキスト出力を生成するプロセスです。

テンプレートの関連付けと {{template "name"}} アクション

text/template では、一つのテンプレートから別の名前付きテンプレートを呼び出すことができます。これは {{template "name"}} アクションを使って行われます。例えば、共通のヘッダーやフッターを別のテンプレートとして定義し、メインのテンプレートからそれらを呼び出すといった用途に利用されます。

この機能を実現するためには、呼び出される側のテンプレートが、呼び出す側のテンプレートと同じ「コンテキスト」または「セット」に属している必要がありました。このコミット以前は、この「セット」の概念が Set 型によって明示的に管理されていました。

以前の Set 型の役割 (このコミットで削除される概念)

このコミット以前の text/template パッケージには、Set という型が存在しました。Set は、複数の Template オブジェクトをまとめて管理するためのコンテナでした。

  • テンプレートのグループ化: 関連する複数のテンプレート(例: ウェブサイトの全ページテンプレート)を一つの Set オブジェクトにまとめることができました。
  • 名前による参照: Set 内のテンプレートは名前で識別され、{{template "name"}} アクションを使って Set 内の別のテンプレートを呼び出すことができました。
  • 共通の関数マップ: Set に関数マップを登録することで、その Set に属する全てのテンプレートで共通の関数を利用できました。
  • パースメソッド: Set.ParseSet.ParseFilesSet.ParseGlob といったメソッドがあり、複数のテンプレート定義を含む文字列やファイルから Set を構築できました。

しかし、この Set 型の存在が、APIの複雑さの一因となっていました。開発者は TemplateSet の両方の概念を理解し、適切に使い分ける必要がありました。

技術的詳細

このコミットの技術的な核心は、Set 型の廃止と、それに伴うテンプレートの関連付けメカニズムの根本的な変更です。

  1. 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に更新される予定です。
  2. Template 型への機能統合と common 構造体の導入:

    • 以前 Set が担っていた複数のテンプレートの管理機能が、Template 型自体に統合されました。
    • src/pkg/text/template/template.go が新規作成され、新しい Template 構造体が定義されています。この Template 構造体は、*parse.Tree (パースされたテンプレートの構文木) と、新たに導入された *common フィールドを持ちます。
    • common 構造体は、関連するテンプレート間で共有される情報(名前付きテンプレートのマップ tmpl、パース時および実行時の関数マップ parseFuncs, execFuncs)を保持します。これにより、Set が提供していた機能が、Template オブジェクトの内部で透過的に管理されるようになります。
  3. 自動的なテンプレート関連付け:

    • Template.New(name string) メソッドが、既存の Template オブジェクトに関連付けられた新しい Template オブジェクトを作成する役割を担うようになりました。これにより、New で作成されたテンプレートは、親テンプレートと同じ common 構造体を共有し、互いに参照可能になります。
    • Template.Parse(text string) メソッドは、与えられたテキストをパースし、その中に {{define "name"}}...{{end}} 形式で定義されたネストされたテンプレートがあれば、それらを自動的に現在の Template オブジェクトに関連付けます。これにより、明示的に Set に追加する手間がなくなります。
    • ParseFiles(filenames ...string) および ParseGlob(pattern string) 関数/メソッドは、複数のファイルからテンプレートをパースする際に、自動的にそれらのテンプレートを関連付けられた状態にします。返される Template オブジェクトは、パースされた最初のファイルのテンプレートになります。
  4. ExecuteTemplate メソッドの追加:

    • Template 型に ExecuteTemplate(wr io.Writer, name string, data interface{}) error メソッドが追加されました。これは、現在の Template オブジェクトに関連付けられた、指定された名前のテンプレートを実行するためのものです。以前 Set.Execute が担っていた機能が、Template 型に直接提供されるようになりました。
  5. 関数検索ロジックの簡素化:

    • src/pkg/text/template/funcs.go 内の findFunction 関数が変更され、引数から *Set が削除されました。これにより、関数は Template オブジェクトの関数マップとグローバル関数マップのみから検索されるようになります。
  6. リフレクション関連の最適化:

    • src/pkg/text/template/exec.go から methodByName ヘルパー関数が削除されました。これは、Go 1で reflect パッケージに MethodByName が導入されたため、カスタム実装が不要になったためです。

これらの変更により、テンプレートの管理と使用が大幅に簡素化され、開発者は Set の概念を意識することなく、より直感的に複数のテンプレートを扱うことができるようになります。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/text/template/doc.go:

    • Set 型に関する説明が削除され、新しい「Associated templates」(関連付けられたテンプレート)の概念が導入されています。
    • テンプレートが並行して安全に実行できること、関数がテンプレートとグローバルマップから検索されることなどが更新されています。
    • {{define}} アクションによるネストされたテンプレート定義と、それらがどのように関連付けられるかの説明が追加されています。
  2. src/pkg/text/template/exec.go:

    • Template 型に ExecuteTemplate メソッドが追加され、指定された名前の関連テンプレートを実行できるようになりました。
    • walkTemplate 関数内で、テンプレートの検索が s.tmpl.tmpl[t.Name] に変更され、Set への参照がなくなりました。
    • findFunction の呼び出しから set 引数が削除されました。
    • methodByName ヘルパー関数が削除され、reflect.Value.MethodByName が直接使用されるようになりました。
  3. 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 型への依存がなくなりました。
  4. src/pkg/text/template/helper.go:

    • Set 型に関連する多くのヘルパー関数(SetMust, ParseSetFiles, ParseSetGlob, ParseTemplateFiles, ParseTemplateGlob など)が削除されました。
    • ParseFiles および ParseGlob 関数/メソッドが *Template を返すように変更され、内部で parseFiles および parseGlob ヘルパー関数を呼び出すようになりました。これらのヘルパー関数は、Template オブジェクトを引数に取り、複数のファイルをパースしてテンプレートを関連付けるロジックを含みます。
  5. src/pkg/text/template/multi_test.go (新規追加):

    • 複数のテンプレートのパースと実行に関する新しいテストケースが追加されました。これは、新しいAPIの動作を検証するために不可欠です。
    • TestParseFiles, TestParseGlob, TestParseFilesWithData, TestParseGlobWithData, TestClone などのテストが含まれています。
  6. src/pkg/text/template/parse.go (削除):

    • このファイルは完全に削除されました。以前は Template 構造体の定義と基本的なパースロジックを含んでいましたが、これらの機能は src/pkg/text/template/template.go に統合されました。
  7. src/pkg/text/template/set.go (削除):

    • このファイルは完全に削除されました。Set 型の廃止を明確に示しています。
  8. 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 構造体の導入

以前は TemplateSet という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では、テンプレートの関連付けはより自動的かつ暗黙的に行われます。

  1. Template.New(name string) メソッド:

    • t.New(name) のように呼び出すことで、既存の Template t と同じ common 構造体を共有する新しい Template オブジェクトが作成されます。
    • これにより、t と新しく作成されたテンプレートは自動的に関連付けられ、互いに {{template "name"}} で呼び出すことが可能になります。
  2. 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) が行っていた機能に相当します。

ParseFilesParseGlob の変更

helper.goParseFilesParseGlob 関数も変更されました。これらはもはや *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言語の text/template パッケージのドキュメント (変更前後の比較)
  • Go言語のリフレクションに関する一般的な知識
  • Go言語の filepath.Glob 関数に関する一般的な知識