[インデックス 12295] ファイルの概要
このコミットは、Go言語の標準ライブラリである text/template
パッケージに、複数のテンプレートを扱う方法を示す新しい例を追加するものです。具体的には、src/pkg/text/template/examplefiles_test.go
という新しいテストファイルが追加され、ParseGlob
、ExecuteTemplate
、テンプレートのクローン作成といった機能を使って、異なるテンプレートを組み合わせたり、共有したりするシナリオがデモンストレーションされています。これは、ユーザーが text/template
パッケージをより効果的に活用できるよう、具体的な使用例を提供することを目的としています。
コミット
commit dd001b59318cfdc5507c505ae66243459683054e
Author: Rob Pike <r@golang.org>
Date: Thu Mar 1 14:55:18 2012 +1100
text/template: add examples that use multiple templates
Fixes #2742.
R=golang-dev, peterthrun, adg
CC=golang-dev
https://golang.org/cl/5699083
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dd001b59318cfdc5507c505ae66243459683054e
元コミット内容
text/template: add examples that use multiple templates
Fixes #2742.
変更の背景
Go言語の text/template
パッケージは、テキストベースの出力を生成するための強力なツールですが、複数のテンプレートを組み合わせて使用する際のパターンやベストプラクティスは、特に初心者にとっては直感的ではない場合があります。コミットメッセージにある "Fixes #2742" は、おそらくこのパッケージの複数のテンプレート利用に関する既存の課題や、より明確なドキュメント/例の必要性を示唆しています。
このコミットの目的は、以下のようなシナリオにおける text/template
の利用方法を具体的に示すことで、開発者がより簡単に、かつ正確にテンプレートを扱えるようにすることです。
- 複数のテンプレートファイルの読み込み:
ParseGlob
のような関数を使って、ディレクトリ内の複数のテンプレートファイルを一度に読み込む方法。 - テンプレートのネストと呼び出し:
{{template "name"}}
アクションを使って、あるテンプレートから別のテンプレートを呼び出す方法。 - テンプレートの共有と分離: 共通のヘルパーテンプレートを定義し、それを異なる「ドライバー」テンプレートで再利用する方法、または同じドライバーテンプレートを異なるヘルパーセットで実行する方法。
これらの例を提供することで、開発者は text/template
の柔軟性と機能をより深く理解し、自身のアプリケーションで複雑なテンプレート構造を構築する際の指針を得ることができます。
前提知識の解説
このコミットの変更内容を理解するためには、Go言語の text/template
パッケージに関する以下の基本的な知識が必要です。
text/template
パッケージ: Go言語の標準ライブラリの一つで、プレーンテキストの出力を生成するためのテンプレートエンジンを提供します。HTMLの生成にはhtml/template
パッケージが推奨されますが、基本的な構文と機能はtext/template
と共通しています。- テンプレートの構文:
{{.FieldName}}
: データ構造のフィールドやマップのキーの値を出力します。{{range .Slice}}...{{end}}
: スライスや配列、マップをイテレートします。{{if .Condition}}...{{else}}...{{end}}
: 条件分岐を行います。{{define "name"}}...{{end}}
: 名前付きテンプレートを定義します。これにより、テンプレートを再利用可能な部品として定義できます。{{template "name"}}
: 定義された名前付きテンプレートを呼び出します。
template.ParseGlob(pattern string)
: 指定されたglobパターンに一致するすべてのファイルを読み込み、それらを単一の*template.Template
オブジェクトに関連付けます。最初に解析されたテンプレートが、そのテンプレートセットの「メイン」テンプレートとなります。template.Execute(wr io.Writer, data interface{})
: テンプレートセットのメインテンプレートを実行し、結果を指定されたio.Writer
に書き込みます。template.ExecuteTemplate(wr io.Writer, name string, data interface{})
: テンプレートセット内の指定された名前のテンプレートを実行し、結果を指定されたio.Writer
に書き込みます。これは、メインテンプレート以外の特定のテンプレートを実行したい場合に便利です。template.Clone()
: 既存の*template.Template
オブジェクトのコピーを作成します。これにより、元のテンプレートセットに影響を与えることなく、新しいテンプレート定義を追加したり、既存の定義を上書きしたりすることができます。これは、異なるコンテキストで同じ基本テンプレートセットを使用したい場合に特に役立ちます。template.Must(t *Template, err error)
: テンプレートの解析や初期化でエラーが発生した場合にパニックを発生させるヘルパー関数です。通常、プログラムの起動時にテンプレートをロードする際に使用され、テンプレートのロードが失敗した場合にはプログラムを終了させます。os.TempDir()
とioutil.TempDir()
: 一時ディレクトリを作成するための関数です。テストや一時的なファイル操作によく使用されます。filepath.Join()
: OSに依存しない形でパスを結合します。
技術的詳細
追加された examplefiles_test.go
ファイルは、text/template
パッケージの複数のテンプレートを扱うための3つの主要な例 (ExampleTemplate_glob
, ExampleTemplate_helpers
, ExampleTemplate_share
) を含んでいます。これらの例は、一時ディレクトリにテンプレートファイルを作成し、それらを text/template
パッケージで解析・実行するという共通のパターンに従っています。
createTestDir
関数
すべての例で共通して使用されるヘルパー関数です。
createTestDir([]templateFile)
:templateFile
スライスを受け取り、一時ディレクトリを作成し、その中に指定された内容でテンプレートファイルを作成します。これにより、テスト環境を簡単にセットアップできます。
ExampleTemplate_glob
この例は、template.ParseGlob
を使用して、ディレクトリ内の複数のテンプレートファイルを一度に読み込む方法を示しています。
T0.tmpl
:T1
を呼び出すプレーンなテンプレート。T1.tmpl
:T1
を定義し、T2
を呼び出すテンプレート。T2.tmpl
:T2
を定義するテンプレート。template.ParseGlob(pattern)
で*.tmpl
に一致するすべてのファイルを解析します。この際、最初にマッチしたファイル(この場合はT0.tmpl
)がテンプレートセットのメインテンプレートになります。tmpl.Execute(os.Stdout, nil)
を呼び出すと、メインテンプレートであるT0.tmpl
が実行され、T1
、T2
とネストされた呼び出しが解決されて出力されます。
ExampleTemplate_helpers
この例は、共通のヘルパーテンプレートをロードし、それらに手動で「ドライバー」テンプレートを追加する方法を示しています。
T1.tmpl
とT2.tmpl
をヘルパーテンプレートとしてtemplate.ParseGlob
でロードします。templates.Parse("{{define
driver1}}...")
とtemplates.Parse("{{define
driver2}}...")
を使って、driver1
とdriver2
という新しいテンプレートを既存のテンプレートセットに追加します。templates.ExecuteTemplate(os.Stdout, "driver1", nil)
とtemplates.ExecuteTemplate(os.Stdout, "driver2", nil)
を使って、それぞれのドライバーテンプレートを実行します。これにより、共通のヘルパーテンプレート (T1
,T2
) を異なるコンテキスト (driver1
,driver2
) で再利用できることが示されます。
ExampleTemplate_share
この例は、同じドライバーテンプレートのグループを、異なるヘルパーテンプレートのセットと組み合わせて使用する方法を示しています。これは、template.Clone()
メソッドの重要なユースケースです。
T0.tmpl
とT1.tmpl
をドライバーテンプレートとしてtemplate.ParseGlob
でロードします。T1
はT2
を呼び出しますが、T2
はまだ定義されていません。drivers.Clone()
を使用して、ドライバーテンプレートのセットをクローンします。first
というクローンに対して{{define "T2"}}T2, version A{{end}}
を解析し、T2
のバージョンAを定義します。- 再度
drivers.Clone()
を使用して、別のクローンsecond
を作成し、これには{{define "T2"}}T2, version B{{end}}
を解析してT2
のバージョンBを定義します。 second.ExecuteTemplate
とfirst.ExecuteTemplate
を実行することで、それぞれのクローンが独立したT2
の定義を使用し、元のdrivers
テンプレートセットには影響を与えないことが示されます。これは、テンプレートのスコープと独立性を管理する上で非常に強力な機能です。
コアとなるコードの変更箇所
このコミットで追加された唯一のファイルは src/pkg/text/template/examplefiles_test.go
です。
--- /dev/null
+++ b/src/pkg/text/template/examplefiles_test.go
@@ -0,0 +1,182 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template_test
+
+import (
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+// templateFile defines the contents of a template to be stored in a file, for testing.
+type templateFile struct {
+ name string
+ contents string
+}
+
+func createTestDir(files []templateFile) string {
+ dir, err := ioutil.TempDir("", "template")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for _, file := range files {
+ f, err := os.Create(filepath.Join(dir, file.name))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ _, err = io.WriteString(f, file.contents)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+ return dir
+}
+
+// Here we demonstrate loading a set of templates from a directory.
+func ExampleTemplate_glob() {
+ // Here we create a temporary directory and populate it with our sample
+ // template definition files; usually the template files would already
+ // exist in some location known to the program.
+ dir := createTestDir([]templateFile{
+ // T0.tmpl is a plain template file that just invokes T1.
+ {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
+ // T1.tmpl defines a template, T1 that invokes T2.
+ {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+ // T2.tmpl defines a template T2.
+ {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+ })
+ // Clean up after the test; another quirk of running as an example.
+ defer os.RemoveAll(dir)
+
+ // pattern is the glob pattern used to find all the template files.
+ pattern := filepath.Join(dir, "*.tmpl")
+
+ // Here starts the example proper.
+ // T0.tmpl is the first name matched, so it becomes the starting template,
+ // the value returned by ParseGlob.
+ tmpl := template.Must(template.ParseGlob(pattern))
+
+ err := tmpl.Execute(os.Stdout, nil)
+ if err != nil {
+ log.Fatalf("template execution: %s", err)
+ }
+ // Output:
+ // T0 invokes T1: (T1 invokes T2: (This is T2))
+}
+
+// This example demonstrates one way to share some templates
+// and use them in different contexts. In this variant we add multiple driver
+// templates by hand to an existing bundle of templates.
+func ExampleTemplate_helpers() {
+ // Here we create a temporary directory and populate it with our sample
+ // template definition files; usually the template files would already
+ // exist in some location known to the program.
+ dir := createTestDir([]templateFile{
+ // T1.tmpl defines a template, T1 that invokes T2.
+ {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+ // T2.tmpl defines a template T2.
+ {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+ })
+ // Clean up after the test; another quirk of running as an example.
+ defer os.RemoveAll(dir)
+
+ // pattern is the glob pattern used to find all the template files.
+ pattern := filepath.Join(dir, "*.tmpl")
+
+ // Here starts the example proper.
+ // Load the helpers.
+ templates := template.Must(template.ParseGlob(pattern))
+ // Add one driver template to the bunch; we do this with an explicit template definition.
+ _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\\n{{end}}")
+ if err != nil {
+ log.Fatal("parsing driver1: ", err)
+ }
+ // Add another driver template.
+ _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\\n{{end}}")
+ if err != nil {
+ log.Fatal("parsing driver2: ", err)
+ }
+ // We load all the templates before execution. This package does not require
+ // that behavior but html/template's escaping does, so it's a good habit.
+ err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
+ if err != nil {
+ log.Fatalf("driver1 execution: %s", err)
+ }
+ err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
+ if err != nil {
+ log.Fatalf("driver2 execution: %s", err)
+ }
+ // Output:
+ // Driver 1 calls T1: (T1 invokes T2: (This is T2))
+ // Driver 2 calls T2: (This is T2)
+}
+
+// This example demonstrates how to use one group of driver
+// templates with distinct sets of helper templates.
+func ExampleTemplate_share() {
+ // Here we create a temporary directory and populate it with our sample
+ // template definition files; usually the template files would already
+ // exist in some location known to the program.
+ dir := createTestDir([]templateFile{
+ // T0.tmpl is a plain template file that just invokes T1.
+ {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\\n"},
+ // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
+ {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+ })
+ // Clean up after the test; another quirk of running as an example.
+ defer os.RemoveAll(dir)
+
+ // pattern is the glob pattern used to find all the template files.
+ pattern := filepath.Join(dir, "*.tmpl")
+
+ // Here starts the example proper.
+ // Load the drivers.
+ drivers := template.Must(template.ParseGlob(pattern))
+
+ // We must define an implementation of the T2 template. First we clone
+ // the drivers, then add a definition of T2 to the template name space.
+
+ // 1. Clone the helper set to create a new name space from which to run them.
+ first, err := drivers.Clone()
+ if err != nil {
+ log.Fatal("cloning helpers: ", err)
+ }
+ // 2. Define T2, version A, and parse it.
+ _, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
+ if err != nil {
+ log.Fatal("parsing T2: ", err)
+ }
+
+ // Now repeat the whole thing, using a different version of T2.
+ // 1. Clone the drivers.
+ second, err := drivers.Clone()
+ if err != nil {
+ log.Fatal("cloning drivers: ", err)
+ }
+ // 2. Define T2, version B, and parse it.
+ _, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
+ if err != nil {
+ log.Fatal("parsing T2: ", err)
+ }
+
+ // Execute the templates in the reverse order to verify the
+ // first is unaffected by the second.
+ err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
+ if err != nil {
+ log.Fatalf("second execution: %s", err)
+ }
+ err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
+ if err != nil {
+ log.Fatalf("first: execution: %s", err)
+ }
+
+ // Output:
+ // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
+ // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
コアとなるコードの解説
src/pkg/text/template/examplefiles_test.go
は、Goのテストフレームワークの「Example」関数として実装されており、ドキュメントの一部として自動的にテストされ、出力が検証されます。これにより、コード例が常に最新かつ正確であることが保証されます。
このファイルは、text/template
パッケージの柔軟性と強力な機能を、具体的なコード例を通じて示しています。
-
templateFile
構造体とcreateTestDir
関数:templateFile
は、テスト用のテンプレートファイルの名前と内容を保持するシンプルな構造体です。createTestDir
は、これらのtemplateFile
オブジェクトを受け取り、一時ディレクトリ内に実際のファイルとして書き出すユーティリティ関数です。これにより、各例が独立したクリーンな環境で実行され、実際のファイルシステムからのテンプレート読み込みをシミュレートできます。
-
ExampleTemplate_glob
:- この例は、
template.ParseGlob
を使って複数のテンプレートファイルを一度にロードする最も基本的な方法を示します。 T0.tmpl
がT1
を呼び出し、T1
がT2
を呼び出すというネストされた構造を通じて、テンプレート間の依存関係と呼び出しフローを明確に示しています。- 出力が期待通りになることで、
ParseGlob
がすべての関連テンプレートを正しく解析し、それらが互いに参照できることを確認しています。
- この例は、
-
ExampleTemplate_helpers
:- この例は、共通の「ヘルパー」テンプレート(
T1
,T2
)を定義し、それらを異なる「ドライバー」テンプレート(driver1
,driver2
)から利用する方法を示します。 templates.Parse("{{define
driverX}}...")
を使って、プログラム内で動的に新しいテンプレート定義を追加できることを示しています。これは、アプリケーションのロジックに基づいてテンプレートを生成したり、既存のテンプレートセットに特定の機能を注入したりする場合に非常に有用です。ExecuteTemplate
を使用して特定の名前のテンプレートを実行することで、テンプレートセット内の任意のテンプレートをエントリポイントとして使用できることを示しています。
- この例は、共通の「ヘルパー」テンプレート(
-
ExampleTemplate_share
:- この例は、
template.Clone()
メソッドの最も強力なユースケースの一つを示しています。 - 同じ「ドライバー」テンプレート(
T0.tmpl
,T1.tmpl
)のセットをロードした後、それをクローンして、それぞれ異なるバージョンの「ヘルパー」テンプレート(T2
)を定義しています。 first
クローンではT2, version A
を、second
クローンではT2, version B
を定義し、それぞれのクローンが独立したT2
の定義を持つことを確認しています。- この機能は、例えば、同じ基本レイアウトを持つが、特定のコンポーネント(この場合は
T2
)の振る舞いが異なる複数のウェブページを生成する場合など、非常に複雑なテンプレート構造を管理する際に不可欠です。元のdrivers
テンプレートセットがクローンによって変更されないことも保証されます。
- この例は、
これらの例は、text/template
パッケージの設計思想である「テンプレートは名前空間を持つ」という概念を具体的に示しており、開発者がテンプレートをモジュール化し、再利用し、異なるコンテキストで独立して管理するための強力なパターンを提供します。
関連リンク
- Go言語
text/template
パッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語
html/template
パッケージ公式ドキュメント (セキュリティ上の理由からHTML生成にはこちらが推奨されますが、基本的な構文は共通です): https://pkg.go.dev/html/template - Go言語のIssueトラッカー (このコミットが修正したとされる #2742 は、Goの内部的なIssueトラッカーのIDである可能性があり、GitHubの公開Issueとは異なる場合があります): https://github.com/golang/go/issues
参考にした情報源リンク
- Go言語
text/template
パッケージのソースコードとドキュメント - Go言語の公式ドキュメント
- GitHubのコミット履歴
- Go言語のテストにおけるExample関数の慣習