[インデックス 1950] ファイルの概要
このコミットは、Go言語の初期開発段階におけるgodoc
ツールの大幅なクリーンアップと機能改善を目的としています。具体的には、複数のHTMLテンプレートファイルを単一の汎用テンプレートに統合し、ローカルファイルへの依存関係を削減することで、godoc
のコードベースを簡素化しています。これにより、基本的なドキュメントサーバー機能がより堅牢になり、ルートディレクトリ下のGoパッケージ(複数ファイルにまたがるものも含む)を自動的に検出し、ファイルまたはパッケージのドキュメントを提供できるようになりました。
コミット
commit 9ef3d8e2e7abbf2b16e63a2b6168034bbe5ba802
Author: Robert Griesemer <gri@golang.org>
Date: Thu Apr 2 18:25:18 2009 -0700
Daily snapshot:
first round of cleanups:
- removed extra .html templates (reduced to one)
- removed dependencies on various local files
- minor fixes throughout
Basic docserver is now operational: Automatically finds all
(multi-file) packages under a root and serves either file
or package documentation.
R=r
OCL=27049
CL=27049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9ef3d8e2e7abbf2b16e63a2b6168034bbe5ba802
元コミット内容
このコミットは、Go言語のgodoc
ツールに対する「日次スナップショット」として記録されており、主に以下のクリーンアップと機能改善が含まれています。
- HTMLテンプレートの削減: 複数の
.html
テンプレートファイル(dir_template.html
,error_template.html
,packages_template.html
,template.html
)が削除され、単一のテンプレートに集約されました。これにより、テンプレート管理の複雑さが軽減されています。 - ローカルファイル依存の解消: コードが特定のローカルファイルへの依存を減らすように修正されました。
- 全体的な軽微な修正: コードベース全体にわたる小さなバグ修正や改善が行われました。
- 基本的なドキュメントサーバーの運用開始: これらの変更により、
godoc
は基本的なドキュメントサーバーとして機能するようになりました。このサーバーは、指定されたルートディレクトリ下のすべてのGoパッケージ(単一ファイルまたは複数ファイルで構成されるものを含む)を自動的に検出し、それらのファイルまたはパッケージのドキュメントを動的に提供します。
コミットメッセージに含まれるOCL=27049
とCL=27049
は、Goプロジェクトが以前使用していたPerforceバージョン管理システムにおけるChange List (CL) 番号を示しており、このコミットがGoの非常に初期の段階で行われたものであることを裏付けています。
変更の背景
このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の、活発な内部開発段階にありました。godoc
ツールは、Go言語の設計思想である「ドキュメントはコードと共に生きるべき」という原則を具現化するための重要なコンポーネントとして開発が進められていました。
初期のgodoc
は、おそらく機能のプロトタイプ段階であり、複数の異なるHTMLテンプレートを使用して、ディレクトリ一覧、エラー表示、パッケージ一覧、個別のパッケージドキュメントなどを生成していました。しかし、このようなアプローチは、テンプレートの管理が煩雑になり、将来的な機能拡張やデザイン変更の際にオーバーヘッドとなる可能性がありました。
このコミットの背景には、以下の目的があったと考えられます。
- コードベースの簡素化と保守性の向上: 複数のテンプレートを単一の汎用テンプレートに集約することで、
godoc
のレンダリングロジックを簡素化し、コードの保守性を高めることを目指しました。これにより、HTML構造の変更がより一元的に行えるようになります。 - 依存関係の整理: 特定のローカルファイルへのハードコードされた依存を排除し、より柔軟でポータブルな設計に移行することで、
godoc
のデプロイや利用を容易にしました。 - ドキュメントサーバー機能の安定化:
godoc
がGoパッケージのドキュメントを自動的に発見し、提供する基本的な機能を確立し、その運用を安定させることを目指しました。これは、Go言語のユーザーがコードベースのドキュメントに簡単にアクセスできるようにするための基盤となります。 - 開発効率の向上: クリーンアップと機能改善を通じて、
godoc
の開発プロセスを効率化し、将来的な機能追加のための強固な基盤を築くことを意図していました。
このコミットは、godoc
が単なるドキュメント生成ツールから、Goエコシステムにおける重要なドキュメント提供インフラへと進化する過程の重要な一歩を示しています。
前提知識の解説
このコミットの変更内容を深く理解するためには、以下の前提知識が役立ちます。
-
Go言語の初期開発とバージョン管理:
- PerforceとChange List (CL) 番号: Go言語は、初期にはGoogle内部でPerforceというバージョン管理システムを使用して開発されていました。Perforceでは、変更のまとまりを「Change List (CL)」と呼び、一意の番号が割り当てられます。このコミットメッセージにある
OCL=27049
とCL=27049
は、その名残であり、このコミットがGoがGitに移行する前の、非常に初期の段階のものであることを示しています。 GOROOT
環境変数: Goの初期から存在する重要な環境変数で、Goのインストールディレクトリ(標準ライブラリやツールなどが配置されている場所)を指します。godoc
のようなツールは、このGOROOT
を基準にGoパッケージを探します。
- PerforceとChange List (CL) 番号: Go言語は、初期にはGoogle内部でPerforceというバージョン管理システムを使用して開発されていました。Perforceでは、変更のまとまりを「Change List (CL)」と呼び、一意の番号が割り当てられます。このコミットメッセージにある
-
Goの
template
パッケージ(初期バージョン):- Goには標準ライブラリとして
text/template
パッケージとhtml/template
パッケージがあります。これらは、データ駆動型のテンプレートエンジンを提供し、GoのオブジェクトからテキストやHTMLを生成するために使用されます。 - このコミットが行われた2009年時点では、
template
パッケージはまだ初期段階にあり、現在のバージョンとはAPIや機能が異なる可能性があります。特に、このコミットでは複数のHTMLテンプレートが削除され、単一のテンプレートに集約される、あるいはHTML生成ロジックがGoコード内に直接埋め込まれる方向への変更が見られます。これは、当時のtemplate
パッケージの機能が限定的であったか、あるいはよりシンプルなアプローチが好まれたためと考えられます。 template.NewTemplateOrDie("filename.html")
: これは、指定されたファイルからテンプレートを読み込み、パースする関数です。パースに失敗した場合はプログラムを終了(panic
)させます。template.Apply(writer, "<!--", template.Substitution {...})
: これは、テンプレートを適用し、指定されたwriter
に結果を書き込むメソッドです。"<!--"
のような文字列を区切り文字として、template.Substitution
マップで定義された関数を実行し、動的なコンテンツを挿入します。
- Goには標準ライブラリとして
-
Goの
os
パッケージとファイルシステム操作:os.Open()
: ファイルを開くための関数。os.Dir
: ディレクトリ内のエントリ(ファイルまたはサブディレクトリ)を表す構造体。os.Stat()
: ファイルやディレクトリの情報を取得する関数。IsDirectory()
:os.Dir
のメソッドで、エントリがディレクトリであるかどうかを判定します。IsRegular()
:os.Dir
のメソッドで、エントリが通常のファイルであるかどうかを判定します。
-
Goの
net/http
パッケージ(初期バージョン):http.Conn
: HTTP接続を表す構造体。http.Request
: HTTPリクエストを表す構造体。http.ServeMux
: HTTPリクエストをハンドラにルーティングするためのマルチプレクサ。http.Handle()
: 特定のパスに対するハンドラを登録する関数。http.ListenAndServe()
: HTTPサーバーを起動する関数。
-
GoのコンパイルとAST (Abstract Syntax Tree):
parser
パッケージ: Goのソースコードを解析し、ASTを生成するためのパッケージ。ast
パッケージ: Goのソースコードの抽象構文木(AST)を表現するためのデータ構造を提供するパッケージ。token
パッケージ: Goのソースコードのトークン(キーワード、識別子、演算子など)と位置情報(ファイル名、行番号、オフセット)を定義するパッケージ。- コンパイルエラーハンドリング: Goのコンパイラやパーサーは、エラーが発生した場合にエラー情報を報告するメカニズムを持っています。このコミットでは、
parseError
、errorList
、errorHandler
といったカスタム型を導入し、より構造化されたエラー収集と表示を実現しています。
これらの知識を持つことで、コミットがGoの初期エコシステムにおいてどのような役割を果たし、どのように技術的な課題を解決しようとしたのかをより深く理解できます。
技術的詳細
このコミットの技術的詳細は、主にgodoc
ツールの内部構造とHTMLレンダリングロジックの変更に集約されます。
-
テンプレートエンジンの変更とHTML生成の統合:
- 複数のHTMLテンプレートの削除:
usr/gri/pretty/dir_template.html
,usr/gri/pretty/error_template.html
,usr/gri/pretty/packages_template.html
,usr/gri/pretty/template.html
といった個別のHTMLテンプレートファイルが削除されました。 - 単一の汎用テンプレートへの集約:
godoc.go
内でgodoc_template = template.NewTemplateOrDie("godoc.html")
という行が追加されており、これが新しい汎用テンプレートとして機能します。これにより、ディレクトリ一覧、エラー表示、パッケージドキュメントなど、すべてのHTML出力がこの単一のテンプレートを介して行われるようになりました。 docprinter.go
の変更: 以前はtemplate.Apply
を使用してHTMLを生成していましたが、このコミットではdoc.Print
メソッド内でfmt.Fprintf
やfmt.Fprintln
を直接使用してHTMLタグとコンテンツを書き出すように変更されています。これは、テンプレートの役割をより限定的なものにし、HTML構造の大部分をGoコード内で直接制御するという設計思想の変更を示唆しています。これにより、テンプレートファイル自体の複雑さを減らし、GoコードがHTMLのレンダリングロジックをより直接的に管理できるようになります。
- 複数のHTMLテンプレートの削除:
-
パス処理の改善:
godoc.go
にcleanPath
とsanitizePath
という新しい関数が導入されました。cleanPath(s string) string
: パス内の連続するスラッシュ(//
)を単一のスラッシュに正規化します。sanitizePath(s string) string
:cleanPath
を呼び出した後、末尾のスラッシュを削除します(ただし、ルートパス/
の場合は削除しない)。
- これらの関数は、HTTPリクエストのURLパスやファイルシステムのパスを処理する際に使用され、パスの正規化とセキュリティの向上に貢献します。以前は
Utils.SanitizePath
を使用していた箇所が、これらの新しい内部関数に置き換えられました。
-
コンパイルエラーハンドリングの強化:
godoc.go
内にparseError
、errorList
、errorHandler
という新しい型が定義されました。parseError
: トークンの位置(token.Position
)とエラーメッセージ(msg
)を持つ構造体で、パースエラーの詳細を保持します。errorList
:parseError
のスライスであり、sort.Interface
を実装しているため、エラーを位置でソートできます。errorHandler
:parser.Parse
に渡されるエラーハンドラで、エラーを収集し、重複するエラー(同じ行の複数エラー)を避けるロジックを含みます。
compile
関数が新しく導入されました。この関数は、指定されたGoファイルのパスとパースモードを受け取り、ASTとソートされたエラーリストを返します。以前はCompilation.Compile
やparser.Parse
を直接呼び出していましたが、compile
関数がエラーハンドリングロジックをカプセル化し、より堅牢なコンパイル処理を提供します。これにより、godoc
がGoソースファイルを解析する際のエラー報告が改善されました。
-
依存関係の整理とビルドプロセスの変更:
Makefile
が修正され、godoc.6
とpretty.6
のビルド依存関係が簡素化されました。特に、godoc.6
は以前のutils.6
,platform.6
,compilation.6
,docprinter.6
からdocprinter.6
,compilation.6
へと依存が減っています。これは、コードのモジュール化が進み、不要な依存が排除されたことを示唆しています。godoc.go
内のインポートリストも整理され、一部のパッケージ(例:utils
,platform
)への直接的な依存が削除されました。これは、これらの機能がgodoc
内部に統合されたか、より汎用的なGo標準ライブラリの機能に置き換えられたことを意味します。
これらの変更は、godoc
がより効率的で、保守しやすく、そして堅牢なドキュメントサーバーとして機能するための基盤を築きました。特に、テンプレートの統合とエラーハンドリングの改善は、初期のGoツールの開発における品質向上へのコミットメントを示しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
usr/gri/pretty/Makefile
:install
ターゲットにgodoc
とuntab
のコピーが追加されました。clean
ターゲットにuntab
とgodoc
の削除が追加されました。godoc.6
とpretty.6
のビルド依存関係が簡素化されました。特にgodoc.6
はutils.6
とplatform.6
への依存が削除されています。
-
usr/gri/pretty/dir_template.html
(削除) -
usr/gri/pretty/error_template.html
(削除) -
usr/gri/pretty/packages_template.html
(削除) -
usr/gri/pretty/template.html
(削除)- これら4つのHTMLテンプレートファイルが削除されました。
-
usr/gri/pretty/docprinter.go
:template
パッケージのインポートが削除されました。templ = template.NewTemplateOrDie("template.html")
というグローバル変数の定義が削除されました。func (doc *PackageDoc) Print(writer io.Write)
メソッド内で、以前templ.Apply
を使用していた部分が、fmt.Fprintf
やfmt.Fprintln
を直接使用してHTMLコンテンツを書き出すように大幅に書き換えられました。これにより、HTMLの構造がGoコード内に直接埋め込まれる形になりました。PackageName()
という新しいメソッドがPackageDoc
構造体に追加されました。
-
usr/gri/pretty/godoc.go
:time
とtoken
パッケージが新しくインポートされました。utils
とplatform
パッケージのインポートが削除されました。GOROOT
変数が追加され、root
フラグのデフォルト値がPlatform.GOROOT
からgetenv("GOROOT")
に変更されました。godoc_template = template.NewTemplateOrDie("godoc.html")
という新しい汎用テンプレートが定義されました。cleanPath
,sanitizePath
,contains
といったパス処理のためのヘルパー関数が追加されました。parseError
,errorList
,errorHandler
というコンパイルエラーハンドリングのための新しい型が定義されました。compile
関数が新しく導入され、Goソースファイルのコンパイルとエラー収集のロジックをカプセル化しました。serveDir
,printErrors
,serveGoFile
,servePackageList
といったHTTPハンドラ関数が、新しいgodoc_template
とcompile
関数を使用するように大幅に修正されました。特に、これらの関数はHTMLテンプレートの適用方法を変更し、より汎用的なテンプレートを使用するように変更されました。getAST
関数が削除され、その機能は新しいcompile
関数に置き換えられました。addFile
関数がgetAST
の代わりにcompile
関数を使用するように変更されました。makePackageMap
関数にverbose
モードでのログ出力が追加されました。serve
関数とmain
関数で、パスのサニタイズにUtils.SanitizePath
の代わりに新しいsanitizePath
関数が使用されるようになりました。
これらの変更は、godoc
のアーキテクチャを根本的に変更し、よりシンプルで統合されたHTMLレンダリングと、堅牢なコンパイルエラーハンドリングを実現しています。
コアとなるコードの解説
usr/gri/pretty/docprinter.go
の変更
以前のdocprinter.go
のPrint
メソッドは、template.Apply
という関数を使って、template.Substitution
マップに定義されたクロージャ(無名関数)を呼び出すことで、HTMLテンプレートの特定のプレースホルダー(例: <!--PACKAGE_NAME-->
)に動的なコンテンツを挿入していました。
変更前(抜粋):
// TODO make this a parameter for Init or Print?
var templ = template.NewTemplateOrDie("template.html");
func (doc *PackageDoc) Print(writer io.Write) {
var p astPrinter.Printer;
p.Init(writer, nil, true);
// TODO propagate Apply errors
templ.Apply(writer, "<!--", template.Substitution {
"PACKAGE_NAME-->" :
func() {
fmt.Fprint(writer, doc.name);
},
// ... 他のプレースホルダーとクロージャ
});
}
変更後(抜粋):
func (doc *PackageDoc) Print(writer io.Write) {
var p astPrinter.Printer;
p.Init(writer, nil, true);
// program header
fmt.Fprintf(writer, "<h1>package %s</h1>\n", doc.name);
fmt.Fprintf(writer, "<p><code>import \"%s\"</code></p>\n", doc.name);
printComments(&p, doc.doc);
// constants
if doc.consts.Len() > 0 {
fmt.Fprintln(writer, "<hr />");
fmt.Fprintln(writer, "<h2>Constants</h2>");
for i := 0; i < doc.consts.Len(); i++ {
doc.consts.At(i).(*valueDoc).print(&p);
}
}
// ... 他のセクション(types, variables, functions)も同様に直接HTMLを書き出す
}
この変更の核心は、テンプレートエンジンへの依存を減らし、HTML生成ロジックをGoコード内に直接埋め込んだ点にあります。これにより、docprinter.go
は外部のtemplate.html
ファイルに依存することなく、パッケージドキュメントのHTML構造を直接構築するようになりました。これは、テンプレートの数を減らすというコミットの目標と一致しており、HTMLのレンダリングをより直接的に制御できるようになります。
usr/gri/pretty/godoc.go
の変更
1. パス処理関数の追加
func cleanPath(s string) string {
for i := 0; i < len(s); i++ {
if s[i] == '/' {
i++;
j := i;
for j < len(s) && s[j] == '/' {
j++;
}
if j > i { // more then one '/'
return s[0 : i] + cleanPath(s[j : len(s)]);
}
}
}
return s;
}
func sanitizePath(s string) string {
s = cleanPath(s);
if s[len(s)-1] == '/' { // strip trailing '/'
s = s[0 : len(s)-1];
}
return s;
}
func contains(s, sub string, pos int) bool {
end := pos + len(sub);
return pos >= 0 && end <= len(s) && s[pos : end] == sub;
}
これらの関数は、URLパスやファイルパスを正規化するために導入されました。cleanPath
は連続するスラッシュを単一のスラッシュにまとめ、sanitizePath
はそれに加えて末尾のスラッシュを削除します(ただし、ルートパス/
は除く)。contains
は文字列が部分文字列を含むかを効率的にチェックするヘルパーです。これにより、以前Utils
パッケージに依存していたパス処理が、godoc
内部で完結するようになりました。
2. コンパイルエラーハンドリングの導入
type parseError struct {
pos token.Position;
msg string;
}
type errorList []parseError
func (list errorList) Len() int { return len(list); }
func (list errorList) Less(i, j int) bool { return list[i].pos.Offset < list[j].pos.Offset; }
func (list errorList) Swap(i, j int) { list[i], list[j] = list[j], list[i]; }
type errorHandler struct {
lastLine int;
errors *vector.Vector;
}
func (h *errorHandler) Error(pos token.Position, msg string) {
// only collect errors that are on a new line
// in the hope to avoid most follow-up errors
if pos.Line != h.lastLine {
h.lastLine = pos.Line;
if h.errors == nil {
// lazy initialize - most of the time there are no errors
h.errors = vector.New(0);
}
h.errors.Push(parseError{pos, msg});
}
}
func compile(path string, mode uint) (*ast.Program, errorList) {
src, err := os.Open(path, os.O_RDONLY, 0);
defer src.Close();
if err != nil {
log.Stdoutf("%s: %v", path, err);
var noPos token.Position;
return nil, errorList{parseError{noPos, err.String()}};
}
var handler errorHandler;
prog, ok := parser.Parse(src, &handler, mode);
if !ok {
// convert error list and sort it
errors := make(errorList, handler.errors.Len());
for i := 0; i < handler.errors.Len(); i++ {
errors[i] = handler.errors.At(i).(parseError);
}
sort.Sort(errors);
return nil, errors;
}
return prog, nil;
}
このセクションは、Goソースコードのパース時に発生するエラーをより詳細に、かつ構造的に扱うためのものです。
parseError
はエラーの位置とメッセージを保持します。errorList
は複数のparseError
をソート可能にするための型です。errorHandler
はparser.Parse
に渡され、エラーを収集します。同じ行で発生する複数のエラーを避けるために、lastLine
を使って重複を排除するロジックが含まれています。compile
関数は、ファイルを開き、parser.Parse
を呼び出し、errorHandler
を使ってエラーを収集し、最終的にソートされたerrorList
を返します。これにより、godoc
はコンパイルエラーをより正確に表示できるようになりました。
3. HTTPハンドラの変更と単一テンプレートの利用
serveDir
, printErrors
, serveGoFile
, servePackageList
といった関数は、削除された個別のHTMLテンプレートの代わりに、新しく定義されたgodoc_template
を使用するように変更されました。
変更前(例: serveDir
):
var dir_template = template.NewTemplateOrDie("dir_template.html");
func serveDir(c *http.Conn, dirname string) {
// ...
dir_template.Apply(c, "<!--", template.Substitution {
"PATH-->" : func() {
fmt.Fprintf(c, "%s", path);
},
// ... 他のセクション
});
}
変更後(例: serveDir
):
var godoc_template = template.NewTemplateOrDie("godoc.html"); // 新しい汎用テンプレート
func serveDir(c *http.Conn, dirname string) {
// ...
godoc_template.Apply(c, "<!--", template.Substitution {
"TITLE-->" : func() {
fmt.Fprint(c, dirname);
},
"HEADER-->" : func() {
fmt.Fprint(c, dirname);
},
"TIMESTAMP-->" : func() {
fmt.Fprint(c, time.UTC().String());
},
"CONTENTS-->" : func () { // すべてのコンテンツがこの単一のプレースホルダーに挿入される
fmt.Fprintln(c, "<h2>Directories</h2>");
for i, entry := range list {
if entry.IsDirectory() {
printLink(c, path, entry.Name);
}
}
fmt.Fprintln(c, "<h2>Go files</h2>");
// ... 他のセクションも同様に直接HTMLを書き出す
}
});
}
この変更は、godoc
のHTMLレンダリングアーキテクチャの大きな転換点です。以前は各コンテンツタイプ(ディレクトリ、エラー、パッケージ)ごとに専用のテンプレートがありましたが、このコミットにより、すべてのコンテンツがgodoc.html
という単一の汎用テンプレートの<!--CONTENTS-->
プレースホルダーに挿入されるようになりました。これにより、HTMLの全体的なレイアウトはgodoc.html
で一元的に管理され、個々のコンテンツセクションはGoコード内で直接HTMLを生成してCONTENTS
プレースホルダーに流し込む形になります。これは、テンプレートの数を減らし、HTML生成ロジックをより統合するというコミットの目標を達成しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
godoc
ツールの概要: https://go.dev/blog/godoc (このブログ記事はコミットより後のものですが、godoc
の哲学を理解するのに役立ちます)- Goの
text/template
パッケージ: https://pkg.go.dev/text/template - Goの
html/template
パッケージ: https://pkg.go.dev/html/template
参考にした情報源リンク
- Go Gitリポジトリのコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Goの初期のCL番号に関する情報:
swtch.com
(Russ Coxのブログ): https://swtch.com/~rsc/regexp/regexp.html (直接CL番号についてではないが、Goの初期開発に関する文脈で参照されることがある)- GoのGerritインスタンス: https://go.googlesource.com/go
- Goの
godoc
の歴史と進化に関する情報:go.dev
(Godoc: documenting Go code): https://go.dev/blog/godoc- Stack OverflowやRedditの議論 (例:
godoc
とgo doc
の役割分担): https://stackoverflow.com/questions/tagged/go-godoc
- Goの
template
パッケージの初期バージョンに関する情報:newmarch.name
(Go Programming Language - Templates): https://www.newmarch.name/go/template/- Goの公式ドキュメント (古いバージョン): https://go.dev/doc/go1.0.html (Go 1.0のリリースノートなどから、当時のパッケージの状況を推測)
- Goの
os
パッケージ: https://pkg.go.dev/os - Goの
net/http
パッケージ: https://pkg.go.dev/net/http - Goの
parser
パッケージ: https://pkg.go.dev/go/parser - Goの
ast
パッケージ: https://pkg.go.dev/go/ast - Goの
token
パッケージ: https://pkg.go.dev/go/token