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

[インデックス 1765] ファイルの概要

このコミットは、Go言語のprettyパッケージにおけるHTML出力の生成方法を改善するものです。具体的には、これまでコード内にハードコードされていたHTML構造を、template.htmlという外部ファイルから読み込むテンプレート方式に移行しています。これにより、HTMLの構造変更が容易になり、コードの保守性が向上します。また、selftest2.goの一部コードがコメントアウトされていますが、これはおそらく開発中の機能やテストの調整によるものと考えられます。

コミット

commit 42b49b1907dd9e5fd7543203cd90db4e7ca9e551
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Mar 5 18:44:21 2009 -0800

    - using doc template for gds now
    - no interface extraction yet
    
    R=r
    OCL=25808
    CL=25808

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/42b49b1907dd9e5fd75443203cd90db4e7ca9e551

元コミット内容

diff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go
index 0bf281176e..cc1d5ae1e1 100644
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -11,6 +11,7 @@ import (
 	"tabwriter";
 	"flag";
 	"fmt";
+\t"strings";
 	Utils "utils";
 	"token";
 	"ast";
@@ -388,8 +389,64 @@ func (P *Printer) Error(pos int, tok int, msg string) {
 // ----------------------------------------------------------------------------
 // HTML support
 
+const template_name = "template.html"
+var html_template string  // TODO should probably be []byte
+
+// tags for substitution in html_template
+const body_tag = "<!--BODY-->";
+
+// indexes of various tags in html_template
+var body_index int;
+
+func init() {
+\tfd, err0 := os.Open(template_name, os.O_RDONLY, 0);
+\tdefer fd.Close();
+\tif err0 != nil {
+\t\tpanic("cannot open html template");
+\t}\n
+\t// TODO not sure why this didn't work
+\t/*
+\tvar buf io.ByteBuffer;
+\tlen, err1 := io.Copy(fd, buf);\n+\tif err1 == io.ErrEOF {
+\t\terr1 = nil;
+\t}\n+\tif err1 != nil {
+\t\tpanic("cannot read html template");
+\t}\n+\tif len == 0 {
+\t\tpanic("html template empty");
+\t}\n+\thtml_template = string(buf.AllData());
+\t*/
+\n+\tvar buf [8*1024]byte;
+\tlen, err1 := io.Readn(fd, buf);\n+\tif err1 == io.ErrEOF {
+\t\terr1 = nil;
+\t}\n+\tif err1 != nil {
+\t\tpanic("cannot read html template");
+\t}\n+\tif len == 0 {
+\t\tpanic("html template empty");
+\t}\n+\thtml_template = string(buf[0 : len]);
+\n+\tbody_index = strings.Index(html_template, body_tag);\n+\tif body_index < 0 {
+\t\tpanic("html_template has no BODY tag");
+\t}\n+}\n+\n+\n func (P *Printer) HtmlPrologue(title string) {
 \tif P.html {
+\t\tP.Printf("%s\\n", html_template[0 : body_index]);
+\t\tP.Printf("<h1>%s</h1>\\n", "package " + title);\n+\t\tP.Printf("<pre>\\n");
+\t\t/*
  \t\tP.TaggedString(0,
  \t\t\t"<html>\\n"
  \t\t\t"<head>\\n"
@@ -402,18 +459,24 @@ func (P *Printer) HtmlPrologue(title string) {
  \t\t\t"<pre>\\n",
  \t\t\t"", ""
  \t\t)
+\t\t*/
  \t}\n }\n \n \n func (P *Printer) HtmlEpilogue() {
  \tif P.html {
+\t\tP.String(0, "");  // flush
+\t\tP.Printf("</pre>\\n");
+\t\tP.Printf("%s", html_template[body_index : len(html_template)]);
+\t\t/*
  \t\tP.TaggedString(0,
  \t\t\t"</pre>\\n"
  \t\t\t"</body>\\n"
  \t\t\t"<html>\\n",
  \t\t\t"", ""
  \t\t)
+\t\t*/
  \t}\n }\n \n@@ -1130,7 +1193,7 @@ func Print(writer io.Write, html bool, prog *ast.Program) {
  \tP.Init(text, html, prog.Comments);\n \n  \t// TODO would be better to make the name of the src file be the title
-\tP.HtmlPrologue("package " + prog.Ident.Str);\n+\tP.HtmlPrologue(prog.Ident.Str);\n  \tP.Program(prog);\n  \tP.HtmlEpilogue();
 \ndiff --git a/usr/gri/pretty/selftest2.go b/usr/gri/pretty/selftest2.go
index 48fe33cf97..a3de38b1b1 100644
--- a/usr/gri/pretty/selftest2.go
+++ b/usr/gri/pretty/selftest2.go
@@ -130,8 +130,9 @@ func f3(a *[]int, m map[string] int) {
  
  type I interface {}
  
-func f3(x I) int {
-\tswitch tmp := tmp.(type) {
+/*
+func f4(x I) int {
+\tswitch tmp := x.(type) {
  \tcase S: return 1;
  \t}\n \tswitch {
  @@ -139,7 +140,7 @@ func f3(x I) int {
  \t}\n \treturn 0;
  }\n-\n+*/
  
  func main() {
  // the prologue
diff --git a/usr/gri/pretty/template.html b/usr/gri/pretty/template.html
new file mode 100644
index 0000000000..8efbdfa7f3
--- /dev/null
+++ b/usr/gri/pretty/template.html
@@ -0,0 +1,6 @@
+
+<!--BODY-->
+
+</div>  <!-- content -->
+</body>
+</html>

変更の背景

このコミットの主な背景は、Go言語のprettyパッケージが生成するHTML出力の柔軟性と保守性を向上させることです。以前は、HTMLの構造がprinter.go内のGoコードに直接埋め込まれていました。このようなハードコードされたHTMLは、デザインの変更や新しい要素の追加が必要になった際に、Goコード自体を修正する必要があり、開発効率を低下させ、エラーのリスクを高めます。

そこで、HTMLテンプレートを導入することで、HTMLの構造とGoコードのロジックを分離し、それぞれの変更が互いに影響を与えにくくする目的があります。template.htmlという外部ファイルにHTMLの骨格を定義し、Goコードからはそのテンプレートを読み込み、動的に内容を埋め込む形にすることで、HTMLの変更が容易になり、よりクリーンなコードベースを維持できるようになります。

コミットメッセージにある「using doc template for gds now」という記述から、この変更がGoのドキュメント生成システム(gds: Go Document System の略称と推測される)に関連しており、その出力形式を改善するためのものであることが示唆されます。

前提知識の解説

Go言語の基本的なファイル操作

Go言語でファイルを扱う際には、osパッケージが提供する関数を使用します。

  • os.Open(name string, flag int, perm int) (*File, error): 指定されたパスのファイルを読み取り専用で開きます。成功すると*os.File型のファイルディスクリプタとnilエラーを返します。失敗するとnilとエラーオブジェクトを返します。
  • defer fd.Close(): deferキーワードは、その関数がリターンする直前に指定された関数を実行することを保証します。ここでは、os.Openで開いたファイルディスクリプタfdを、関数が終了する際に必ず閉じるために使用されています。これにより、リソースのリークを防ぎます。
  • io.Readn(r Reader, buf []byte) (n int, err error): ioパッケージの関数で、指定されたReaderからbufのサイズ分だけデータを読み込みます。読み込んだバイト数とエラーを返します。
  • panic(v interface{}): Go言語における回復不可能なエラー処理メカニズムです。panicが呼び出されると、現在の関数の実行が停止し、遅延関数(deferで登録された関数)が実行され、その後呼び出し元の関数へとパニックが伝播していきます。最終的に、プログラム全体がクラッシュするか、recoverによってパニックが捕捉されない限り、実行が停止します。このコミットでは、テンプレートファイルの読み込みに失敗した場合など、プログラムの続行が不可能な状況でpanicが使用されています。

Go言語の文字列操作

  • strings.Index(s, substr string) int: stringsパッケージの関数で、文字列s内で部分文字列substrが最初に現れるインデックスを返します。見つからない場合は-1を返します。このコミットでは、HTMLテンプレート内の特定のタグ(<!--BODY-->)の位置を特定するために使用されています。

HTMLテンプレートの概念

HTMLテンプレートとは、HTMLの骨格を定義し、動的に変化するコンテンツを埋め込むためのプレースホルダー(目印)を持つファイルのことです。サーバーサイドのアプリケーションでは、このテンプレートを読み込み、データベースから取得したデータやプログラムで生成したデータをプレースホルダーに挿入して、最終的なHTMLページを生成します。これにより、プレゼンテーション層(HTML)とビジネスロジック層(Goコード)を分離し、開発の効率化と保守性の向上を図ることができます。

init() 関数の役割

Go言語のinit()関数は、パッケージがインポートされた際に、そのパッケージ内の他のどの関数よりも先に自動的に実行される特別な関数です。各パッケージは複数のinit()関数を持つことができ、それらは定義された順序で実行されます。このコミットでは、プログラムの起動時にHTMLテンプレートファイルを読み込み、必要な初期設定を行うためにinit()関数が利用されています。これにより、テンプレートが使用される前に確実にロードされていることが保証されます。

技術的詳細

このコミットの技術的な核心は、usr/gri/pretty/printer.goにおけるHTML出力の生成ロジックの変更と、usr/gri/pretty/template.htmlという新しいテンプレートファイルの導入にあります。

printer.go での template.html の読み込みと処理

  1. 定数と変数宣言:

    • const template_name = "template.html": テンプレートファイルの名前を定数として定義しています。
    • var html_template string: 読み込んだテンプレートの内容を保持する文字列変数です。コメントで// TODO should probably be []byteとあり、将来的にはバイトスライスに変更する意図があることが示唆されています。
    • const body_tag = "<!--BODY-->": HTMLテンプレート内でコンテンツを挿入する場所を示すマーカー(プレースホルダー)を定義しています。
    • var body_index int: body_taghtml_template内で見つかった場合の開始インデックスを保持します。
  2. init() 関数でのテンプレート読み込み:

    • init()関数内で、os.Openを使ってtemplate_nametemplate.html)を開きます。
    • defer fd.Close()により、関数終了時にファイルが確実に閉じられるようにします。
    • ファイルオープンに失敗した場合(err0 != nil)、panic("cannot open html template")を呼び出してプログラムを終了させます。
    • ファイルの内容を読み込むために、var buf [8*1024]byteという8KBのバイト配列をバッファとして用意し、io.Readnでファイルからバッファに読み込みます。
    • 読み込み中にエラーが発生した場合や、ファイルが空だった場合もpanicを呼び出します。
    • 読み込んだバイト配列bufの内容をstring(buf[0 : len])で文字列に変換し、html_templateに格納します。
    • strings.Index(html_template, body_tag)を使って、html_template内でbody_tagの位置を検索し、そのインデックスをbody_indexに格納します。
    • body_tagが見つからなかった場合(body_index < 0)、panic("html_template has no BODY tag")を呼び出してプログラムを終了させます。これは、テンプレートが正しくない形式である場合に早期に問題を検出するためです。

HtmlPrologueHtmlEpilogue の変更点

これらの関数は、HTML出力の開始部分(プロローグ)と終了部分(エピローグ)を生成する役割を担っています。

  • HtmlPrologue(title string):

    • 以前は、<html>, <head>, <body>, <pre>などのHTMLタグをP.TaggedStringを使って直接出力していました。
    • 変更後は、html_templatebody_indexで分割し、テンプレートの冒頭部分(html_template[0 : body_index])を出力します。
    • その後、<h1>タグでパッケージタイトルを出力し、<pre>タグを開始します。
    • これにより、HTMLのヘッダーやボディの開始タグなどがテンプレートから提供されるようになり、Goコードからは動的なコンテンツ(タイトルなど)のみを挿入する形になります。
  • HtmlEpilogue():

    • 以前は、</pre>, </body>, <html>などのHTMLタグを直接出力していました。
    • 変更後は、</pre>タグを閉じ、html_templatebody_index以降の残りの部分(html_template[body_index : len(html_template)])を出力します。
    • これにより、HTMLのボディの終了タグやHTML全体の終了タグなどがテンプレートから提供されるようになります。

selftest2.go の変更

selftest2.goでは、f3という関数がf4にリネームされ、全体がコメントアウトされています。これは、このコミットがHTMLテンプレートの導入に焦点を当てているため、関連性の低いテストコードや開発中の機能が一時的に無効化されたものと推測されます。Go言語の初期段階では、このようなテストファイルの変更は頻繁に行われる可能性があります。

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

usr/gri/pretty/printer.go

@@ -11,6 +11,7 @@ import (
 	"tabwriter";
 	"flag";
 	"fmt";
+\t"strings"; // stringsパッケージのインポート
 	Utils "utils";
 	"token";
 	"ast";
@@ -388,8 +389,64 @@ func (P *Printer) Error(pos int, tok int, msg string) {
 // ----------------------------------------------------------------------------
 // HTML support
 
+const template_name = "template.html" // テンプレートファイル名の定義
+var html_template string  // テンプレート内容を保持する変数
+
+// tags for substitution in html_template
+const body_tag = "<!--BODY-->"; // ボディ挿入位置のマーカー
+
+// indexes of various tags in html_template
+var body_index int; // ボディ挿入位置のインデックス
+
+func init() { // 初期化関数
+\tfd, err0 := os.Open(template_name, os.O_RDONLY, 0); // テンプレートファイルを開く
+\tdefer fd.Close(); // 関数終了時にファイルを閉じる
+\tif err0 != nil {
+\t\tpanic("cannot open html template"); // ファイルオープン失敗でパニック
+\t}
+
+	// ... (io.ByteBufferを使った古いコードのコメントアウト) ...
+
+\tvar buf [8*1024]byte; // 8KBのバッファ
+\tlen, err1 := io.Readn(fd, buf); // ファイル内容を読み込む
+\tif err1 == io.ErrEOF {
+\t\terr1 = nil;
+\t}
+\tif err1 != nil {
+\t\tpanic("cannot read html template"); // ファイル読み込み失敗でパニック
+\t}
+\tif len == 0 {
+\t\tpanic("html template empty"); // テンプレートが空でパニック
+\t}
+\thtml_template = string(buf[0 : len]); // 読み込んだ内容を文字列に変換
+
+\tbody_index = strings.Index(html_template, body_tag); // body_tagの位置を検索
+\tif body_index < 0 {
+\t\tpanic("html_template has no BODY tag"); // body_tagが見つからない場合パニック
+\t}
+}
+
+
 func (P *Printer) HtmlPrologue(title string) {
  \tif P.html {
+\t\tP.Printf("%s\\n", html_template[0 : body_index]); // テンプレートの冒頭部分を出力
+\t\tP.Printf("<h1>%s</h1>\\n", "package " + title); // タイトルを出力
+\t\tP.Printf("<pre>\\n"); // <pre>タグを開始
+\t\t/* (古いハードコードされたHTML出力のコメントアウト) */
  \t}\n }\n \n \n func (P *Printer) HtmlEpilogue() {
  \tif P.html {
+\t\tP.String(0, "");  // flush
+\t\tP.Printf("</pre>\\n"); // </pre>タグを閉じる
+\t\tP.Printf("%s", html_template[body_index : len(html_template)]); // テンプレートの残りの部分を出力
+\t\t/* (古いハードコードされたHTML出力のコメントアウト) */
  \t}\n }\n \n@@ -1130,7 +1193,7 @@ func Print(writer io.Write, html bool, prog *ast.Program) {
  \tP.Init(text, html, prog.Comments);\n \n  \t// TODO would be better to make the name of the src file be the title
-\tP.HtmlPrologue("package " + prog.Ident.Str);\n+\tP.HtmlPrologue(prog.Ident.Str); // HtmlPrologueの引数変更
  \tP.Program(prog);\n  \tP.HtmlEpilogue();

usr/gri/pretty/template.html

new file mode 100644
index 0000000000..8efbdfa7f3
--- /dev/null
+++ b/usr/gri/pretty/template.html
@@ -0,0 +1,6 @@
+<html>
+<head>
+<title>Go Documentation</title>
+</head>
+<body>
+<div id="content">
+<!--BODY-->
+</div>  <!-- content -->
+</body>
+</html>

コアとなるコードの解説

このコミットの核となるのは、printer.goinit()関数と、HtmlPrologueHtmlEpilogue関数の変更、そして新しく追加されたtemplate.htmlファイルです。

init() 関数

init()関数は、プログラムが起動し、prettyパッケージがロードされる際に一度だけ実行されます。この関数は、HTMLテンプレートシステムを初期化する責任を負っています。

  1. テンプレートファイルの読み込み: os.Openio.Readnを使用して、template.htmlファイルの内容をhtml_template変数に読み込みます。この処理は、ファイルが存在しない、読み込めない、または内容が空である場合にpanicを発生させ、プログラムの異常終了を促します。これは、テンプレートが正しくロードされなければ、HTML生成機能が正常に動作しないため、早期に問題を検出するための設計です。
  2. ボディタグの検索: strings.Indexを使用して、読み込んだhtml_templateの中から<!--BODY-->という特定のコメントタグの位置を検索します。このタグは、動的に生成されるコンテンツ(Goコードの整形済み出力など)が挿入される場所を示します。body_tagが見つからない場合もpanicを発生させ、テンプレートの形式が不正であることを開発者に知らせます。

このinit()関数により、html_templatebody_indexがプログラムの実行開始前に適切に設定され、後続のHTML生成処理で利用できるようになります。

HtmlPrologueHtmlEpilogue

これらの関数は、init()関数で準備されたhtml_templateを利用して、HTML出力の開始と終了を制御します。

  • HtmlPrologue:

    • html_template[0 : body_index]というスライス表記を使って、テンプレートの冒頭から<!--BODY-->タグの直前までの部分を出力します。これには、<html>, <head>, <title>, <body>, <div id="content">などのHTML開始タグが含まれます。
    • その後、<h1>タグでパッケージ名を出力し、整形済みテキストの開始を示す<pre>タグを出力します。
    • これにより、HTMLの基本的な構造がテンプレートから提供され、Goコードは動的なタイトルとコンテンツの開始タグのみを挿入する形になります。
  • HtmlEpilogue:

    • まず、P.String(0, "")を呼び出して、それまでの出力バッファをフラッシュします。
    • 次に、整形済みテキストの終了を示す</pre>タグを出力します。
    • 最後に、html_template[body_index : len(html_template)]というスライス表記を使って、<!--BODY-->タグの直後からテンプレートの末尾までの部分を出力します。これには、</div>, </body>, </html>などのHTML終了タグが含まれます。
    • これにより、HTMLの閉じタグがテンプレートから提供され、一貫性のあるHTML構造が保証されます。

template.html

この新しいファイルは、HTMLドキュメントの基本的な構造を定義しています。<!--BODY-->というコメントが、Goコードによって動的に生成されるコンテンツが挿入されるプレースホルダーとして機能します。この分離により、HTMLのレイアウトやデザインを変更する際に、Goコードを修正する必要がなくなり、フロントエンドとバックエンドの役割分担が明確になります。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント
  • Go言語のソースコード