[インデックス 1960] ファイルの概要
このコミットは、Go言語の公式ドキュメントツールであるgodoc
の機能強化と内部構造の改善に焦点を当てた、大規模な「日次スナップショット」です。特に、godoc
が提供するWebインターフェースのURL構造の再編成、コメントのフォーマット修正、およびコードベース全体の多数のマイナーなクリーンアップが含まれています。
コミット
Daily snapshot.
- godoc now supports the following url prefixes:
/doc/ for package documentation
/file/ for files (directories, html, and .go files)
/spec for the spec
/mem for the memory model
- formatting of comments has been fixed
- tons of minor cleanups (still more to do)
Still missing:
- pretty printing of source is not as pretty as it used to be
(still a relict from the massive AST cleanup which has't quite made it's way everywhere)
- documentation entries should be sorted
- comments in code is not printed or not properly printed
TBR=r
DELTA=416 (182 added, 100 deleted, 134 changed)
OCL=27078
CL=27078
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a9f1ee21532498ceb74a4a7b589ab0cc1b3885f
元コミット内容
commit 2a9f1ee21532498ceb74a4a7b589ab0cc1b3885f
Author: Robert Griesemer <gri@golang.org>
Date: Fri Apr 3 16:19:22 2009 -0700
Daily snapshot.
- godoc now supports the following url prefixes:
/doc/ for package documentation
/file/ for files (directories, html, and .go files)
/spec for the spec
/mem for the memory model
- formatting of comments has been fixed
- tons of minor cleanups (still more to do)
Still missing:
- pretty printing of source is not as pretty as it used to be
(still a relict from the massive AST cleanup which has't quite made it's way everywhere)
- documentation entries should be sorted
- comments in code is not printed or not properly printed
TBR=r
DELTA=416 (182 added, 100 deleted, 134 changed)
OCL=27078
CL=27078
変更の背景
このコミットは、Go言語がまだ初期開発段階にあった2009年4月に行われたものです。当時のgodoc
ツールは、Goのソースコードからドキュメントを生成し、Webブラウザで閲覧可能にするための重要なコンポーネントでした。この時期は、言語仕様、標準ライブラリ、およびツールチェインが急速に進化していたため、godoc
もその変更に合わせて頻繁に更新される必要がありました。
このコミットの主な背景には、以下の点が挙げられます。
godoc
のWebインターフェースの改善: ユーザーがGoのドキュメント、ソースファイル、言語仕様、メモリモデルといった異なる種類のコンテンツにアクセスしやすくするために、より明確で構造化されたURLパスを提供する必要がありました。以前は/src/
のような汎用的なプレフィックスが使われていましたが、これを/doc/
、/file/
、/spec
、/mem
といった具体的なプレフィックスに分割することで、コンテンツの種類をURLから直感的に判断できるようにしました。- コードフォーマットとコメント表示の品質向上:
godoc
はソースコードの表示も担当するため、コメントのフォーマットやソースコードの整形(pretty printing)の品質は非常に重要でした。コミットメッセージにある「formatting of comments has been fixed」や「pretty printing of source is not as pretty as it used to be」という記述から、AST(抽象構文木)の内部構造変更に伴う表示上の課題を解決しようとしていたことが伺えます。 - コードベースの整理とリファクタリング: 初期開発段階では、機能追加が優先され、コードの構造が一時的に複雑になることがあります。このコミットは、そのような状況で発生した「tons of minor cleanups」を行うことで、将来的な開発と保守を容易にすることを目的としていました。特に、HTMLページの生成ロジックの共通化や、パッケージ情報の管理方法の改善が見られます。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語および関連ツールの基本的な概念を把握しておく必要があります。
-
Go言語のAST (Abstract Syntax Tree):
- Goコンパイラやツール(
godoc
を含む)は、Goのソースコードを直接処理するのではなく、まずソースコードを解析して抽象構文木(AST)と呼ばれるツリー構造に変換します。ASTはプログラムの構造を抽象的に表現したもので、各ノードが宣言、式、文などの言語要素に対応します。 go/ast
パッケージは、GoのソースコードのASTを表現するための型と関数を提供します。godoc
は、このASTを走査してドキュメント情報を抽出し、整形されたソースコードを生成します。- コミットメッセージにある「massive AST cleanup」とは、このASTの内部構造が大きく変更されたことを指しており、それが
godoc
のpretty printingに影響を与えていたことが示唆されています。
- Goコンパイラやツール(
-
godoc
ツール:godoc
は、Goのソースコードから自動的にドキュメントを生成し、Webブラウザで閲覧できる形式で提供するツールです。Goのドキュメンテーションは、ソースコード内のコメント(特にエクスポートされた識別子の直前のコメント)から自動的に抽出されるという特徴があります。godoc
は、Goのパッケージ、関数、型、変数などのドキュメントだけでなく、ソースコード自体や、Go言語仕様、メモリモデルなどの公式ドキュメントも提供します。
-
Goの
net/http
パッケージ:godoc
のWebサーバー機能は、Goの標準ライブラリであるnet/http
パッケージを使用して実装されています。このパッケージは、HTTPサーバーの構築、リクエストのルーティング、レスポンスの送信など、Webアプリケーション開発に必要な機能を提供します。http.HandleFunc
やhttp.ListenAndServe
といった関数が、Webサーバーの基本的な動作を制御します。
-
Goの
text/template
パッケージ (または初期のtemplate
パッケージ):godoc
のようなWebアプリケーションでは、動的にHTMLコンテンツを生成するためにテンプレートエンジンが使用されます。Goには標準でtext/template
(およびHTMLエスケープを考慮したhtml/template
)パッケージが用意されており、これらを使ってHTMLテンプレートを定義し、データと結合して最終的なHTMLを生成します。- このコミットが行われた初期のGoでは、
template
という名前のパッケージが使われていたようです。
-
Goの
token
パッケージ:- Goのソースコードを解析する際、字句解析(lexing)の段階でソースコードはトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換されます。
go/token
パッケージは、これらのトークンを表す定数や、ソースコード内の位置情報(ファイル名、行番号、オフセット)を管理するための型を提供します。 astprinter.go
におけるTokenPrinter
インターフェースの導入は、ASTを整形して出力する際に、個々のトークンをどのように表現するかをカスタマイズできるようにするためのものです。
- Goのソースコードを解析する際、字句解析(lexing)の段階でソースコードはトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換されます。
技術的詳細
このコミットは、主にgodoc
のWebサーバー部分と、ASTの整形・ドキュメント生成部分に大きな変更を加えています。
-
godoc
のURLルーティングの再構築 (usr/gri/pretty/godoc.go
):- 以前は
/src/
という単一のプレフィックスでソースファイルやディレクトリを扱っていましたが、これをよりセマンティックなURLプレフィックスに分割しました。docPrefix = "/doc/"
: パッケージドキュメント用。filePrefix = "/file/"
: 個々のソースファイル、HTMLファイル、ディレクトリ用。以前の/src/
の役割を引き継ぎます。/spec
と/mem
: Go言語仕様 (go_spec.html
) とメモリモデル (go_mem.html
) の静的HTMLファイルを提供するための新しいエンドポイント。makeFixedFileServer
というヘルパー関数が導入され、特定のファイルを常に提供するハンドラを簡単に作成できるようになりました。
installHandler
関数が、指定されたプレフィックスに基づいてHTTPリクエストを適切なハンドラ関数にルーティングする役割を担います。servePage
およびserveError
関数の導入により、HTMLページの共通ヘッダー/フッターやエラーページの生成ロジックが中央集約され、コードの重複が削減されました。これにより、各コンテンツハンドラはコンテンツ本体の生成に集中できるようになりました。
- 以前は
-
ASTプリンターの柔軟性向上 (
usr/gri/pretty/astprinter.go
):TokenPrinter
インターフェースが新しく定義されました。このインターフェースは、リテラル、識別子、トークン、コメントといったASTの要素を文字列として出力するためのメソッド(PrintLit
,PrintIdent
,PrintToken
,PrintComment
)を定義しています。Printer
構造体(ASTを整形して出力する役割を持つ)は、Init
メソッドでTokenPrinter
のインスタンスを受け取るようになりました。これにより、ASTの出力時に、個々のトークンの表示方法を外部からカスタマイズできるようになります。これは、将来的にシンタックスハイライトや異なるフォーマットでの出力に対応するための基盤となる変更と考えられます。- コメントの処理ロジックが微調整され、
cindex
とcpos
が「コメントグループ」から「コメント」のインデックスと位置を指すように変更されました。これは、コメントの関連付けと表示の精度を向上させるためのものです。
-
ドキュメントプリンターの改善とコメントフォーマット (
usr/gri/pretty/docprinter.go
):addFunc
内のファクトリ関数(特定の型を返す関数)の検出ロジックが修正されました。stripWhiteSpace
関数が削除され、printLine
関数内でコメント行の先頭の空白やタブ、/* */
形式のコメントの*
を適切に処理するロジックが直接組み込まれました。これにより、コメントがよりきれいに整形されて表示されるようになりました。これはコミットメッセージの「formatting of comments has been fixed」に直接対応する変更です。
-
パッケージマップの構築ロジックの改善 (
usr/gri/pretty/godoc.go
):makePackageMap
関数内で、パッケージ情報を格納するpakMap
とpakList
の更新方法が改善されました。一時的なローカル変数pmap
とplist
を使用してパッケージ情報を構築し、その後グローバル変数にアトミックに(ただし、コメントにあるようにロックはまだ実装されていない)割り当てるようになりました。これにより、パッケージ情報の整合性が向上し、並行処理の際の潜在的な問題を軽減します。
コアとなるコードの変更箇所
このコミットは複数のファイルにまたがる広範な変更を含んでいますが、特にgodoc
のWebサーバーとしての振る舞いと、ASTの整形・ドキュメント生成の基盤に影響を与える以下のファイルがコアとなります。
usr/gri/pretty/godoc.go
:godoc
のメインロジック、HTTPハンドラ、URLルーティング、パッケージ情報の管理。usr/gri/pretty/astprinter.go
: ASTを整形して出力するためのプリンターの定義、特にTokenPrinter
インターフェースの導入。usr/gri/pretty/docprinter.go
: パッケージドキュメントの生成ロジック、コメントの整形処理。
具体的な変更箇所は以下の通りです。
usr/gri/pretty/godoc.go
--- a/usr/gri/pretty/godoc.go
+++ b/usr/gri/pretty/godoc.go
@@ -33,11 +34,12 @@ import (
// TODO
// - uniform use of path, filename, dirname, pakname, etc.
// - fix weirdness with double-/'s in paths
+// - cleanup uses of *root, GOROOT, etc. (quite a mess at the moment)
const (
docPrefix = "/doc/";
- srcPrefix = "/src/";
+ filePrefix = "/file/";
)
@@ -111,8 +110,13 @@ func isGoFile(dir *os.Dir) bool {
}
+func isHTMLFile(dir *os.Dir) bool {
+ return dir.IsRegular() && hasSuffix(dir.Name, ".html");
+}
+
+
func printLink(c *http.Conn, path, name string) {
- fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", srcPrefix + path + name, name);
+ fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", filePrefix + path + name, name);
}
@@ -188,6 +192,33 @@ func compile(path string, mode uint) (*ast.Program, errorList) {
}
+// ----------------------------------------------------------------------------
+// Templates
+
+// html template
+// TODO initialize only if needed (i.e. if run as a server)
+var godoc_html = template.NewTemplateOrDie("godoc.html");
+
+func servePage(c *http.Conn, title string, contents func()) {
+ c.SetHeader("content-type", "text/html; charset=utf-8");
+
+ // TODO handle Apply errors
+ godoc_html.Apply(c, "<!--", template.Substitution {
+ "TITLE-->" : func() { fmt.Fprint(c, title); },
+ "HEADER-->" : func() { fmt.Fprint(c, title); },
+ "TIMESTAMP-->" : func() { fmt.Fprint(c, time.UTC().String()); },
+ "CONTENTS-->" : contents
+ });
+}
+
+
+func serveError(c *http.Conn, err, arg string) {
+ servePage(c, "Error", func () {
+ fmt.Fprintf(c, "%v (%s)\n", err, arg);
+ });
+}
+
+
// ----------------------------------------------------------------------------
// Directories
@@ -214,45 +245,28 @@ func serveDir(c *http.Conn, dirname string) {
sort.Sort(dirArray(list));
-\tc.SetHeader("content-type", "text/html; charset=utf-8");
path := dirname + "/";
// Print contents in 3 sections: directories, go files, everything else
-\n-\t// TODO handle Apply errors
-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprint(c, dirname);
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprint(c, dirname);
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\tfmt.Fprintln(c, "<h2>Directories</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif entry.IsDirectory() {
-\t\t\t\t\tprintLink(c, path, entry.Name);
-\t\t\t\t}\n+\tservePage(c, dirname + " - Contents", func () {
+\t\tfmt.Fprintln(c, "<h2>Directories</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif entry.IsDirectory() {
+\t\t\t\tprintLink(c, path, entry.Name);
\t\t\t}\n+\t\t}\n \n-\t\t\tfmt.Fprintln(c, "<h2>Go files</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif isGoFile(&entry) {
-\t\t\t\t\tprintLink(c, path, entry.Name);
-\t\t\t\t}\n+\t\tfmt.Fprintln(c, "<h2>Go files</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif isGoFile(&entry) {
+\t\t\t\tprintLink(c, path, entry.Name);
\t\t\t}\n+\t\t}\n \n-\t\t\tfmt.Fprintln(c, "<h2>Other files</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif !entry.IsDirectory() && !isGoFile(&entry) {
-\t\t\t\t\tfmt.Fprintf(c, "%s<br />\n", entry.Name);
-\t\t\t\t}\n+\t\tfmt.Fprintln(c, "<h2>Other files</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif !entry.IsDirectory() && !isGoFile(&entry) {
+\t\t\t\tfmt.Fprintf(c, "%s<br />\n", entry.Name);
\t\t\t}\n \t\t}\n \t});
@@ -262,120 +276,96 @@ func serveDir(c *http.Conn, dirname string) {
// ----------------------------------------------------------------------------
// Files
-func printErrors(c *http.Conn, filename string, errors errorList) {
+func serveCompilationErrors(c *http.Conn, filename string, errors errorList) {
// open file
path := *root + filename;
fd, err1 := os.Open(path, os.O_RDONLY, 0);
defer fd.Close();
if err1 != nil {
-\t\t// TODO better error handling
-\t\tlog.Stdoutf("%s: %v", path, err1);
+\t\tserveError(c, err1.String(), path);
+\t\treturn;
}
// read source
var buf io.ByteBuffer;
n, err2 := io.Copy(fd, &buf);
if err2 != nil {
-\t\t// TODO better error handling
-\t\tlog.Stdoutf("%s: %v", path, err2);
+\t\tserveError(c, err2.String(), path);
+\t\treturn;
}
src := buf.Data();
// TODO handle Apply errors
-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprint(c, filename);
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprint(c, filename);
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\t// section title
-\t\t\tfmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename);
-\t\t\t\n-\t\t\t// handle read errors
-\t\t\tif err1 != nil || err2 != nil /* 6g bug139 */ {
-\t\t\t\tfmt.Fprintf(c, "could not read file %s\n", filename);
-\t\t\t\treturn;\n-\t\t\t}\n-\t\t\t\n-\t\t\t// write source with error messages interspersed
-\t\t\tfmt.Fprintln(c, "<pre>");
-\t\t\toffs := 0;\n-\t\t\tfor i, e := range errors {
-\t\t\t\tif 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
-\t\t\t\t\t// TODO handle Write errors
-\t\t\t\t\tc.Write(src[offs : e.pos.Offset]);
-\t\t\t\t\t// TODO this should be done using a .css file
-\t\t\t\t\tfmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
-\t\t\t\t\toffs = e.pos.Offset;\n-\t\t\t\t} else {
-\t\t\t\t\tlog.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
-\t\t\t\t}\n+\tservePage(c, filename, func () {
+\t\t// section title
+\t\tfmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename);
+\t\t\n+\t\t// handle read errors
+\t\tif err1 != nil || err2 != nil /* 6g bug139 */ {
+\t\t\tfmt.Fprintf(c, "could not read file %s\n", filename);
+\t\t\treturn;\n+\t\t}\n+\t\t\n+\t\t// write source with error messages interspersed
+\t\tfmt.Fprintln(c, "<pre>");
+\t\toffs := 0;\n+\t\tfor i, e := range errors {
+\t\t\tif 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
+\t\t\t\t// TODO handle Write errors
+\t\t\t\tc.Write(src[offs : e.pos.Offset]);
+\t\t\t\t// TODO this should be done using a .css file
+\t\t\t\tfmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
+\t\t\t\toffs = e.pos.Offset;\n+\t\t\t} else {
+\t\t\t\tlog.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
\t\t\t}\n-\t\t\t// TODO handle Write errors
-\t\t\tc.Write(src[offs : len(src)]);
-\t\t\tfmt.Fprintln(c, "</pre>");
+\t\t}\n+\t\t// TODO handle Write errors
+\t\tc.Write(src[offs : len(src)]);
+\t\tfmt.Fprintln(c, "</pre>");
\t});
}
-func serveGoFile(c *http.Conn, dirname string, filenames []string) {
-\t// compute documentation
-\tvar doc docPrinter.PackageDoc;\n-\tfor i, filename := range filenames {\n-\t\tpath := *root + "/" + dirname + "/" + filename;\n-\t\tprog, errors := compile(path, parser.ParseComments);\n-\t\tif len(errors) > 0 {\n-\t\t\tc.SetHeader("content-type", "text/html; charset=utf-8");
-\t\t\tprintErrors(c, filename, errors);\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tif i == 0 {\n-\t\t\t// first package - initialize docPrinter\n-\t\t\tdoc.Init(prog.Name.Value);\n-\t\t}\n-\t\tdoc.AddProgram(prog);\n+func serveGoSource(c *http.Conn, dirname string, filename string) {
+\tpath := dirname + "/" + filename;
+\tprog, errors := compile(*root + "/" + path, parser.ParseComments);
+\tif len(errors) > 0 {
+\t\tserveCompilationErrors(c, filename, errors);
+\t\treturn;
}
-\tc.SetHeader("content-type", "text/html; charset=utf-8");
-\t\n-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprintf(c, "%s - Go package documentation", doc.PackageName());
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprintf(c, "%s - Go package documentation", doc.PackageName());
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\t// write documentation
-\t\t\twriter := makeTabwriter(c); // for nicely formatted output
-\t\t\tdoc.Print(writer);\n-\t\t\twriter.Flush(); // ignore errors
-\t\t}\n+\tservePage(c, path + " - Go source", func () {
+\t\tfmt.Fprintln(c, "<pre>");
+\t\tvar p astPrinter.Printer;
+\t\twriter := makeTabwriter(c); // for nicely formatted output
+\t\tp.Init(writer, nil, nil, true);
+\t\tp.DoProgram(prog);
+\t\twriter.Flush(); // ignore errors
+\t\tfmt.Fprintln(c, "</pre>");
});
}
-func serveSrc(c *http.Conn, path string) {
+func serveHTMLFile(c *http.Conn, filename string) {
+\tsrc, err1 := os.Open(filename, os.O_RDONLY, 0);
+\tdefer src.Close();
+\tif err1 != nil {
+\t\tserveError(c, err1.String(), filename);
+\t\treturn
+\t}\n+\twritten, err2 := io.Copy(src, c);
+\tif err2 != nil {
+\t\tserveError(c, err2.String(), filename);
+\t\treturn
+\t}\n+}
+
+
+func serveFile(c *http.Conn, path string) {
dir, err := os.Stat(*root + path);
if err != nil {
-\t\tc.WriteHeader(http.StatusNotFound);\n-\t\tfmt.Fprintf(c, "Error: %v (%s)\n", err, path);\n+\t\tserveError(c, err.String(), path);
\treturn;
}
@@ -383,10 +373,11 @@ func serveSrc(c *http.Conn, path string) {
case dir.IsDirectory():
serveDir(c, path);
case isGoFile(dir):
-\t\tserveGoFile(c, "", []string{path});
+\t\tserveGoSource(c, "", path);
+\tcase isHTMLFile(dir):
+\t\tserveHTMLFile(c, *root + path);
default:
-\t\tc.WriteHeader(http.StatusNotFound);\n-\t\tfmt.Fprintf(c, "Error: Not a directory or .go file (%s)\n", path);\n+\t\tserveError(c, "Not a directory or .go file", path);
}
}
@@ -407,13 +398,15 @@ func main() {
}
makePackageMap();
-\n+\t\n+\tinstallHandler("/mem", makeFixedFileServer(GOROOT + "/doc/go_mem.html"));
+\tinstallHandler("/spec", makeFixedFileServer(GOROOT + "/doc/go_spec.html"));
installHandler(docPrefix, serveDoc);
-\tinstallHandler(srcPrefix, serveSrc);
+\tinstallHandler(filePrefix, serveFile);
+\n {\terr := http.ListenAndServe(":" + *port, nil);
\tif err != nil {
\t\tlog.Exitf("ListenAndServe: %v", err)
\t}\n }\n }
-\n-
usr/gri/pretty/astprinter.go
--- a/usr/gri/pretty/astprinter.go
+++ b/usr/gri/pretty/astprinter.go
@@ -5,22 +5,20 @@
package astPrinter
import (
-\t"io";
-\t"vector";
-\t"tabwriter";
+\t"ast";
"flag";
"fmt";
+\t"io";
+\t"os";
"strings";
-\t"utf8";
-\t"unicode";
-\n-\t"utils";
+\t"tabwriter";
"token";
-\t"ast";
-\t"template";
-\t"symboltable";
+\t"unicode";
+\t"utf8";
+\t"vector";
)
+\n
var (
debug = flag.Bool("ast_debug", false, "print debugging information");
@@ -75,9 +73,48 @@ func hasExportedNames(names []*ast.Ident) bool {
}
+// ----------------------------------------------------------------------------
+// TokenPrinter
+
+// TODO This is not yet used - should fix this.
+
+// An implementation of a TokenPrinter may be provided when
+// initializing an AST Printer. It is used to print tokens.
+//
+type TokenPrinter interface {
+ PrintLit(w io.Write, tok token.Token, value []byte);
+ PrintIdent(w io.Write, value string);
+ PrintToken(w io.Write, token token.Token);
+ PrintComment(w io.Write, value []byte);
+}
+
+
+type defaultPrinter struct {}
+
+func (p defaultPrinter) PrintLit(w io.Write, tok token.Token, value []byte) {
+ w.Write(value);
+}
+
+
+func (p defaultPrinter) PrintIdent(w io.Write, value string) {
+ fmt.Fprint(w, value);
+}
+
+
+func (p defaultPrinter) PrintToken(w io.Write, token token.Token) {
+ fmt.Fprint(w, token.String());
+}
+
+
+func (p defaultPrinter) PrintComment(w io.Write, value []byte) {
+ w.Write(value);
+}
+
+
// ----------------------------------------------------------------------------
// ASTPrinter
+\n
// Separators - printed in a delayed fashion, depending on context.
const (
none = iota;
@@ -101,14 +138,17 @@ type Printer struct {
// output
text io.Write;
+\t// token printing
+\ttprinter TokenPrinter;
+\n
// formatting control
html bool;
full bool; // if false, print interface only; print all otherwise
// comments
comments []*ast.Comment; // the list of unassociated comments
-\tcindex int; // the current comment group index
-\tcpos token.Position; // the position of the next comment group
+\tcindex int; // the current comment index
+\tcpos token.Position; // the position of the next comment
// current state
lastpos token.Position; // position after last string
@@ -144,10 +184,17 @@ func (P *Printer) nextComments() {
}
-func (P *Printer) Init(text io.Write, comments []*ast.Comment, html bool) {
+func (P *Printer) Init(text io.Write, tprinter TokenPrinter, comments []*ast.Comment, html bool) {
// writers
P.text = text;
-\t\n+\n+\t// token printing
+\tif tprinter != nil {
+\t\tP.tprinter = tprinter;
+\t} else {
+\t\tP.tprinter = defaultPrinter{};
+\t}\n+\n // formatting control
P.html = html;
@@ -227,7 +274,7 @@ func (P *Printer) newline(n int) {
func (P *Printer) TaggedString(pos token.Position, tag, s, endtag string) {
// use estimate for pos if we don't have one
offs := pos.Offset;
-\tif offs == 0 {
+\tif pos.Line == 0 {
\toffs = P.lastpos.Offset;
}\n
@@ -401,6 +448,17 @@ func (P *Printer) Error(pos token.Position, tok token.Token, msg string) {
}
+// An astPrinter implements io.Write.
+// TODO this is not yet used.
+func (P *Printer) Write(p []byte) (n int, err *os.Error) {
+// TODO
+// - no string conversion every time
+// - return proper results
+ P.String(noPos, string(p));
+ return len(p), nil;
+}
+
+
// ----------------------------------------------------------------------------
// HTML support
usr/gri/pretty/docprinter.go
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -136,25 +136,27 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
\t\t\ttyp.methods[name] = fdoc;\n \t\t}\n \t\t// if the type wasn\'t found, it wasn\'t exported\n+\t\t// TODO: a non-exported type may still have exported functions\n+\t\t// determine what to do in that case\n+\t\treturn;\n+\t}\n \n-\t} else {\n-\t\t// perhaps a factory function\n-\t\t// determine result type, if any\n-\t\tif len(fun.Type.Results) >= 1 {\n-\t\t\tres := fun.Type.Results[0];\n-\t\t\tif len(res.Names) <= 1 {\n-\t\t\t\t// exactly one (named or anonymous) result type\n-\t\t\t\ttyp = doc.lookupTypeDoc(res.Type);\n-\t\t\t\tif typ != nil {\n-\t\t\t\t\ttyp.factories[name] = fdoc;\n-\t\t\t\t\treturn;\n-\t\t\t\t}\n+\t// perhaps a factory function\n+\t// determine result type, if any\n+\tif len(fun.Type.Results) >= 1 {\n+\t\tres := fun.Type.Results[0];\n+\t\tif len(res.Names) <= 1 {\n+\t\t\t// exactly one (named or anonymous) result type\n+\t\t\ttyp = doc.lookupTypeDoc(res.Type);\n+\t\t\tif typ != nil {\n+\t\t\t\ttyp.factories[name] = fdoc;\n+\t\t\t\treturn;\n \t\t\t}\n \t\t}\n-\n-\t\t// ordinary function\n-\t\tdoc.funcs[name] = fdoc;\n \t}\n+\n+\t// ordinary function\n+\tdoc.funcs[name] = fdoc;\n }\n \n \n@@ -279,18 +281,6 @@ func untabify(s []byte) []byte {\n }\n \n \n-func stripWhiteSpace(s []byte) []byte {\n-\ti, j := 0, len(s);\n-\tfor i < len(s) && s[i] <= \' \' {\n-\t\ti++;\n-\t}\n-\tfor j > i && s[j-1] <= \' \' {\n-\t\tj--\n-\t}\n-\treturn s[i : j];\n-}\n-\n-\n func stripCommentDelimiters(s []byte) []byte {\n \tswitch s[1] {\n \tcase \'/\': return s[2 : len(s)-1];\n@@ -308,8 +298,25 @@ const /* formatting mode */ (\n )\n \n func printLine(p *astPrinter.Printer, line []byte, mode int) {\n-\tindented := len(line) > 0 && line[0] == \'\\t\';\n-\tline = stripWhiteSpace(line);\n+\t// If a line starts with \" *\" (as a result of a vertical /****/ comment),\n+\t// strip it away. For an example of such a comment, see src/lib/flag.go.\n+\tif len(line) >= 2 && line[0] == \' \' && line[1] == \'*\' {\n+\t\tline = line[2 : len(line)];\n+\t}\n+\n+\t// The line is indented if it starts with a tab.\n+\t// In either case strip away a leading space or tab.\n+\tindented := false;\n+\tif len(line) > 0 {\n+\t\tswitch line[0] {\n+\t\tcase \'\\t\':\n+\t\t\tindented = true;\n+\t\t\tfallthrough;\n+\t\tcase \' \':\n+\t\t\tline = line[1 : len(line)];\n+\t\t}\n+\t}\n+\n \tif len(line) == 0 {\n \t\t// empty line\n \t\tswitch mode {\n@@ -426,7 +433,7 @@ func (t *typeDoc) print(p *astPrinter.Printer) {\n \n func (doc *PackageDoc) Print(writer io.Write) {\n \tvar p astPrinter.Printer;\n-\tp.Init(writer, nil, true);\n+\tp.Init(writer, nil, nil, true);\n \t\n \t// program header\n \tfmt.Fprintf(writer, "<h1>package %s</h1>\\n", doc.name);\n```
## コアとなるコードの解説
### `godoc.go`の変更
* **URLプレフィックスの変更**: `srcPrefix`が`filePrefix`にリネームされ、その値が`/file/`に変更されました。これにより、ソースファイルやディレクトリへのリンクがより明確な`/file/`プレフィックスを使用するようになりました。
* **`servePage`と`serveError`の導入**:
* `servePage`は、HTMLページの共通の構造(タイトル、ヘッダー、タイムスタンプ、コンテンツ)を`godoc.html`テンプレートを使って生成するためのヘルパー関数です。これにより、各コンテンツハンドラは`servePage`を呼び出し、コンテンツ部分を生成する無名関数を渡すだけでよくなり、HTML生成ロジックが大幅に簡素化されました。
* `serveError`は、エラーメッセージを表示する共通のページを生成するために`servePage`を利用します。
* **ハンドラの統合とリファクタリング**:
* `serveDir`、`printErrors`(`serveCompilationErrors`にリネーム)、`serveGoFile`(`serveGoSource`にリネーム)、`serveSrc`(`serveFile`にリネーム)といった既存のハンドラが、新しく導入された`servePage`と`serveError`を利用するように書き換えられました。これにより、コードの重複が減り、一貫したHTML出力が保証されます。
* `isHTMLFile`関数と`serveHTMLFile`関数が追加され、`.html`ファイルを直接提供する機能が導入されました。
* **静的ドキュメントの提供**: `main`関数内で、`/mem`と`/spec`のURLプレフィックスに対して、それぞれ`GOROOT/doc/go_mem.html`と`GOROOT/doc/go_spec.html`という静的なHTMLファイルを提供する`makeFixedFileServer`ハンドラがインストールされました。これにより、Goのメモリモデルと言語仕様のドキュメントが`godoc`を通じて直接提供されるようになりました。
* **パッケージマップ構築の改善**: `makePackageMap`関数内で、パッケージ情報を構築する際に一時的なローカルマップ`pmap`とスライス`plist`を使用し、最後にグローバル変数`pakMap`と`pakList`に割り当てるようになりました。これは、グローバルな状態を更新する際の一時的な不整合を避けるための一般的なパターンです。
### `astprinter.go`の変更
* **`TokenPrinter`インターフェースの導入**:
* `TokenPrinter`インターフェースは、ASTの要素(リテラル、識別子、トークン、コメント)をどのように文字列として出力するかを定義します。
* `defaultPrinter`は、このインターフェースのデフォルト実装を提供し、単に要素の値を書き出すだけです。
* **`Printer.Init`の変更**:
* `Printer`構造体の`Init`メソッドが、新しい`tprinter TokenPrinter`引数を受け取るようになりました。これにより、`Printer`の初期化時にカスタムの`TokenPrinter`を注入できるようになり、ASTの出力方法の柔軟性が向上しました。`tprinter`が`nil`の場合は`defaultPrinter`が使用されます。
* **コメント処理の微調整**: `cindex`と`cpos`のコメントが「コメントグループ」から「コメント」を指すように変更され、より粒度の細かいコメント処理が可能になったことを示唆しています。
* **`TaggedString`の条件変更**: `pos.Offset == 0`の代わりに`pos.Line == 0`で位置を推定するようになりました。これは、ソースコードの位置情報の扱いに関するより正確なロジックへの変更です。
### `docprinter.go`の変更
* **`addFunc`のロジック修正**: ファクトリ関数の検出ロジックが、非エクスポート型の場合でも適切に処理されるように修正されました。
* **`stripWhiteSpace`の削除と`printLine`への統合**:
* `stripWhiteSpace`関数が削除されました。
* その機能は`printLine`関数に統合され、コメント行の先頭にある空白、タブ、および`/* */`形式のコメントの`*`を自動的に取り除くようになりました。これにより、`godoc`が生成するドキュメント内のコメントの表示がよりきれいに整形されます。これは、コミットメッセージで言及されている「formatting of comments has been fixed」の具体的な実装です。
* **`PackageDoc.Print`の`astPrinter.Printer`初期化**: `PackageDoc.Print`内で`astPrinter.Printer`を初期化する際に、新しい`TokenPrinter`引数に`nil`を渡すようになりました。これは、この時点では`docprinter`がカスタムのトークンプリンターを必要としないことを示しています。
これらの変更は、`godoc`のWebインターフェースの使いやすさと、Goソースコードおよびドキュメントの表示品質を向上させるための重要なステップでした。特に、URL構造の明確化と、HTML生成ロジックの共通化は、ツールの保守性と拡張性を高める上で大きな意味を持ちます。
## 関連リンク
* Go言語公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
* `godoc`コマンドのドキュメント: [https://pkg.go.dev/golang.org/x/tools/cmd/godoc](https://pkg.go.dev/golang.org/x/tools/cmd/godoc) (現在の`godoc`ツールの情報)
* Go言語仕様: [https://go.dev/ref/spec](https://go.dev/ref/spec)
* Goメモリモデル: [https://go.dev/ref/mem](https://go.dev/ref/mem)
* `go/ast`パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* `go/token`パッケージ: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
* `net/http`パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* `text/template`パッケージ: [https://pkg.go.dev/text/template](https://pkg.go.dev/text/template)
## 参考にした情報源リンク
* Go言語の公式リポジトリ (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
* Go言語の初期開発に関する議論やメーリングリストのアーカイブ (例: golang-nuts): [https://groups.google.com/g/golang-nuts](https://groups.google.com/g/golang-nuts) (具体的なスレッドは特定していませんが、当時の開発状況を理解するための一般的な情報源です。)
* Go言語の歴史に関する記事やブログポスト (例: Go at Google: Language Design in the Service of Software Engineering): [https://go.dev/talks/2012/go4progs.slide#1](https://go.dev/talks/2012/go4progs.slide#1) (Goの設計思想と歴史的背景を理解するための一般的な情報源です。)
* GoのASTに関する解説記事 (例: The Go AST: A Guided Tour): [https://go.dev/blog/go-ast](https://go.dev/blog/go-ast) (現在のASTに関する情報ですが、基本的な概念は初期から共通しています。)
* Goの`godoc`に関する解説記事 (例: Godoc: documenting Go code): [https://go.dev/blog/godoc](https://go.dev/blog/godoc) (現在の`godoc`に関する情報ですが、ツールの目的と基本的な動作は初期から共通しています。)
* Goのコミット履歴を閲覧できるツール (例: `git log`コマンド、GitHubのコミット履歴ページ)
* Goのソースコード自体 (特に`src/cmd/go/internal/mod`や`src/go/`以下のパッケージ)
* GoのIssue Tracker (例: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)) (当時のバグ報告や機能要望が背景にある可能性がありますが、このコミットに直接関連する特定のIssueは特定していません。)```markdown
# [インデックス 1960] ファイルの概要
このコミットは、Go言語の公式ドキュメントツールである`godoc`の機能強化と内部構造の改善に焦点を当てた、大規模な「日次スナップショット」です。特に、`godoc`が提供するWebインターフェースのURL構造の再編成、コメントのフォーマット修正、およびコードベース全体の多数のマイナーなクリーンアップが含まれています。
## コミット
Daily snapshot.
- godoc now supports the following url prefixes: /doc/ for package documentation /file/ for files (directories, html, and .go files) /spec for the spec /mem for the memory model
- formatting of comments has been fixed
- tons of minor cleanups (still more to do)
Still missing:
- pretty printing of source is not as pretty as it used to be (still a relict from the massive AST cleanup which has't quite made it's way everywhere)
- documentation entries should be sorted
- comments in code is not printed or not properly printed
TBR=r DELTA=416 (182 added, 100 deleted, 134 changed) OCL=27078 CL=27078
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/2a9f1ee21532498ceb74a4a7b589ab0cc1b3885f](https://github.com/golang/go/commit/2a9f1ee21532498ceb74a4a7b589ab0cc1b3885f)
## 元コミット内容
commit 2a9f1ee21532498ceb74a4a7b589ab0cc1b3885f Author: Robert Griesemer gri@golang.org Date: Fri Apr 3 16:19:22 2009 -0700
Daily snapshot.
- godoc now supports the following url prefixes:
/doc/ for package documentation
/file/ for files (directories, html, and .go files)
/spec for the spec
/mem for the memory model
- formatting of comments has been fixed
- tons of minor cleanups (still more to do)
Still missing:
- pretty printing of source is not as pretty as it used to be
(still a relict from the massive AST cleanup which has't quite made it's way everywhere)
- documentation entries should be sorted
- comments in code is not printed or not properly printed
TBR=r
DELTA=416 (182 added, 100 deleted, 134 changed)
OCL=27078
CL=27078
## 変更の背景
このコミットは、Go言語がまだ初期開発段階にあった2009年4月に行われたものです。当時の`godoc`ツールは、Goのソースコードからドキュメントを生成し、Webブラウザで閲覧可能にするための重要なコンポーネントでした。この時期は、言語仕様、標準ライブラリ、およびツールチェインが急速に進化していたため、`godoc`もその変更に合わせて頻繁に更新される必要がありました。
このコミットの主な背景には、以下の点が挙げられます。
1. **`godoc`のWebインターフェースの改善**: ユーザーがGoのドキュメント、ソースファイル、言語仕様、メモリモデルといった異なる種類のコンテンツにアクセスしやすくするために、より明確で構造化されたURLパスを提供する必要がありました。以前は`/src/`のような汎用的なプレフィックスが使われていましたが、これを`/doc/`、`/file/`、`/spec`、`/mem`といった具体的なプレフィックスに分割することで、コンテンツの種類をURLから直感的に判断できるようにしました。
2. **コードフォーマットとコメント表示の品質向上**: `godoc`はソースコードの表示も担当するため、コメントのフォーマットやソースコードの整形(pretty printing)の品質は非常に重要でした。コミットメッセージにある「formatting of comments has been fixed」や「pretty printing of source is not as pretty as it used to be」という記述から、AST(抽象構文木)の内部構造変更に伴う表示上の課題を解決しようとしていたことが伺えます。
3. **コードベースの整理とリファクタリング**: 初期開発段階では、機能追加が優先され、コードの構造が一時的に複雑になることがあります。このコミットは、そのような状況で発生した「tons of minor cleanups」を行うことで、将来的な開発と保守を容易にすることを目的としていました。特に、HTMLページの生成ロジックの共通化や、パッケージ情報の管理方法の改善が見られます。
## 前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語および関連ツールの基本的な概念を把握しておく必要があります。
1. **Go言語のAST (Abstract Syntax Tree)**:
* Goコンパイラやツール(`godoc`を含む)は、Goのソースコードを直接処理するのではなく、まずソースコードを解析して抽象構文木(AST)と呼ばれるツリー構造に変換します。ASTはプログラムの構造を抽象的に表現したもので、各ノードが宣言、式、文などの言語要素に対応します。
* `go/ast`パッケージは、GoのソースコードのASTを表現するための型と関数を提供します。`godoc`は、このASTを走査してドキュメント情報を抽出し、整形されたソースコードを生成します。
* コミットメッセージにある「massive AST cleanup」とは、このASTの内部構造が大きく変更されたことを指しており、それが`godoc`のpretty printingに影響を与えていたことが示唆されています。
2. **`godoc`ツール**:
* `godoc`は、Goのソースコードから自動的にドキュメントを生成し、Webブラウザで閲覧できる形式で提供するツールです。Goのドキュメンテーションは、ソースコード内のコメント(特にエクスポートされた識別子の直前のコメント)から自動的に抽出されるという特徴があります。
* `godoc`は、Goのパッケージ、関数、型、変数などのドキュメントだけでなく、ソースコード自体や、Go言語仕様、メモリモデルなどの公式ドキュメントも提供します。
3. **Goの`net/http`パッケージ**:
* `godoc`のWebサーバー機能は、Goの標準ライブラリである`net/http`パッケージを使用して実装されています。このパッケージは、HTTPサーバーの構築、リクエストのルーティング、レスポンスの送信など、Webアプリケーション開発に必要な機能を提供します。
* `http.HandleFunc`や`http.ListenAndServe`といった関数が、Webサーバーの基本的な動作を制御します。
4. **Goの`text/template`パッケージ (または初期の`template`パッケージ)**:
* `godoc`のようなWebアプリケーションでは、動的にHTMLコンテンツを生成するためにテンプレートエンジンが使用されます。Goには標準で`text/template`(およびHTMLエスケープを考慮した`html/template`)パッケージが用意されており、これらを使ってHTMLテンプレートを定義し、データと結合して最終的なHTMLを生成します。
* このコミットが行われた初期のGoでは、`template`という名前のパッケージが使われていたようです。
5. **Goの`token`パッケージ**:
* Goのソースコードを解析する際、字句解析(lexing)の段階でソースコードはトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換されます。`go/token`パッケージは、これらのトークンを表す定数や、ソースコード内の位置情報(ファイル名、行番号、オフセット)を管理するための型を提供します。
* `astprinter.go`における`TokenPrinter`インターフェースの導入は、ASTを整形して出力する際に、個々のトークンをどのように表現するかをカスタマイズできるようにするためのものです。
## 技術的詳細
このコミットは、主に`godoc`のWebサーバー部分と、ASTの整形・ドキュメント生成部分に大きな変更を加えています。
1. **`godoc`のURLルーティングの再構築 (`usr/gri/pretty/godoc.go`)**:
* 以前は`/src/`という単一のプレフィックスでソースファイルやディレクトリを扱っていましたが、これをよりセマンティックなURLプレフィックスに分割しました。
* `docPrefix = "/doc/"`: パッケージドキュメント用。
* `filePrefix = "/file/"`: 個々のソースファイル、HTMLファイル、ディレクトリ用。以前の`/src/`の役割を引き継ぎます。
* `/spec`と`/mem`: Go言語仕様 (`go_spec.html`) とメモリモデル (`go_mem.html`) の静的HTMLファイルを提供するための新しいエンドポイント。`makeFixedFileServer`というヘルパー関数が導入され、特定のファイルを常に提供するハンドラを簡単に作成できるようになりました。
* `installHandler`関数が、指定されたプレフィックスに基づいてHTTPリクエストを適切なハンドラ関数にルーティングする役割を担います。
* `servePage`および`serveError`関数の導入により、HTMLページの共通ヘッダー/フッターやエラーページの生成ロジックが中央集約され、コードの重複が削減されました。これにより、各コンテンツハンドラはコンテンツ本体の生成に集中できるようになりました。
2. **ASTプリンターの柔軟性向上 (`usr/gri/pretty/astprinter.go`)**:
* `TokenPrinter`インターフェースが新しく定義されました。このインターフェースは、リテラル、識別子、トークン、コメントといったASTの要素を文字列として出力するためのメソッド(`PrintLit`, `PrintIdent`, `PrintToken`, `PrintComment`)を定義しています。
* `Printer`構造体(ASTを整形して出力する役割を持つ)は、`Init`メソッドで`TokenPrinter`のインスタンスを受け取るようになりました。これにより、ASTの出力時に、個々のトークンの表示方法を外部からカスタマイズできるようになります。これは、将来的にシンタックスハイライトや異なるフォーマットでの出力に対応するための基盤となる変更と考えられます。
* コメントの処理ロジックが微調整され、`cindex`と`cpos`が「コメントグループ」から「コメント」のインデックスと位置を指すように変更されました。これは、コメントの関連付けと表示の精度を向上させるためのものです。
3. **ドキュメントプリンターの改善とコメントフォーマット (`usr/gri/pretty/docprinter.go`)**:
* `addFunc`内のファクトリ関数(特定の型を返す関数)の検出ロジックが修正されました。
* `stripWhiteSpace`関数が削除され、`printLine`関数内でコメント行の先頭の空白やタブ、`*`を適切に処理するロジックが直接組み込まれました。これにより、コメントがよりきれいに整形されて表示されるようになりました。これはコミットメッセージの「formatting of comments has been fixed」に直接対応する変更です。
4. **パッケージマップの構築ロジックの改善 (`usr/gri/pretty/godoc.go`)**:
* `makePackageMap`関数内で、パッケージ情報を格納する`pakMap`と`pakList`の更新方法が改善されました。一時的なローカル変数`pmap`と`plist`を使用してパッケージ情報を構築し、その後グローバル変数にアトミックに(ただし、コメントにあるようにロックはまだ実装されていない)割り当てるようになりました。これにより、パッケージ情報の整合性が向上し、並行処理の際の潜在的な問題を軽減します。
## コアとなるコードの変更箇所
このコミットは複数のファイルにまたがる広範な変更を含んでいますが、特に`godoc`のWebサーバーとしての振る舞いと、ASTの整形・ドキュメント生成の基盤に影響を与える以下のファイルがコアとなります。
* `usr/gri/pretty/godoc.go`: `godoc`のメインロジック、HTTPハンドラ、URLルーティング、パッケージ情報の管理。
* `usr/gri/pretty/astprinter.go`: ASTを整形して出力するためのプリンターの定義、特に`TokenPrinter`インターフェースの導入。
* `usr/gri/pretty/docprinter.go`: パッケージドキュメントの生成ロジック、コメントの整形処理。
具体的な変更箇所は以下の通りです。
**`usr/gri/pretty/godoc.go`**
```diff
--- a/usr/gri/pretty/godoc.go
+++ b/usr/gri/pretty/godoc.go
@@ -33,11 +34,12 @@ import (
// TODO
// - uniform use of path, filename, dirname, pakname, etc.
// - fix weirdness with double-/'s in paths
+// - cleanup uses of *root, GOROOT, etc. (quite a mess at the moment)
const (
docPrefix = "/doc/";
- srcPrefix = "/src/";
+ filePrefix = "/file/";
)
@@ -111,8 +110,13 @@ func isGoFile(dir *os.Dir) bool {
}
+func isHTMLFile(dir *os.Dir) bool {
+ return dir.IsRegular() && hasSuffix(dir.Name, ".html");
+}
+
+
func printLink(c *http.Conn, path, name string) {
- fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", srcPrefix + path + name, name);
+ fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", filePrefix + path + name, name);
}
@@ -188,6 +192,33 @@ func compile(path string, mode uint) (*ast.Program, errorList) {
}
+// ----------------------------------------------------------------------------
+// Templates
+
+// html template
+// TODO initialize only if needed (i.e. if run as a server)
+var godoc_html = template.NewTemplateOrDie("godoc.html");
+
+func servePage(c *http.Conn, title string, contents func()) {
+ c.SetHeader("content-type", "text/html; charset=utf-8");
+
+ // TODO handle Apply errors
+ godoc_html.Apply(c, "<!--", template.Substitution {
+ "TITLE-->" : func() { fmt.Fprint(c, title); },
+ "HEADER-->" : func() { fmt.Fprint(c, title); },
+ "TIMESTAMP-->" : func() { fmt.Fprint(c, time.UTC().String()); },
+ "CONTENTS-->" : contents
+ });
+}
+
+
+func serveError(c *http.Conn, err, arg string) {
+ servePage(c, "Error", func () {
+ fmt.Fprintf(c, "%v (%s)\n", err, arg);
+ });
+}
+
+
// ----------------------------------------------------------------------------
// Directories
@@ -214,45 +245,28 @@ func serveDir(c *http.Conn, dirname string) {
sort.Sort(dirArray(list));
-\tc.SetHeader("content-type", "text/html; charset=utf-8");
path := dirname + "/";
// Print contents in 3 sections: directories, go files, everything else
-\n-\t// TODO handle Apply errors
-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprint(c, dirname);
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprint(c, dirname);
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\tfmt.Fprintln(c, "<h2>Directories</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif entry.IsDirectory() {
-\t\t\t\t\tprintLink(c, path, entry.Name);
-\t\t\t\t}\n+\tservePage(c, dirname + " - Contents", func () {
+\t\tfmt.Fprintln(c, "<h2>Directories</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif entry.IsDirectory() {
+\t\t\t\tprintLink(c, path, entry.Name);
\t\t\t}\n+\t\t}\n \n-\t\t\tfmt.Fprintln(c, "<h2>Go files</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif isGoFile(&entry) {
-\t\t\t\t\tprintLink(c, path, entry.Name);
-\t\t\t\t}\n+\t\tfmt.Fprintln(c, "<h2>Go files</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif isGoFile(&entry) {
+\t\t\t\tprintLink(c, path, entry.Name);
\t\t\t}\n+\t\t}\n \n-\t\t\tfmt.Fprintln(c, "<h2>Other files</h2>");
-\t\t\tfor i, entry := range list {
-\t\t\t\tif !entry.IsDirectory() && !isGoFile(&entry) {
-\t\t\t\t\tfmt.Fprintf(c, "%s<br />\n", entry.Name);
-\t\t\t\t}\n+\t\tfmt.Fprintln(c, "<h2>Other files</h2>");
+\t\tfor i, entry := range list {
+\t\t\tif !entry.IsDirectory() && !isGoFile(&entry) {
+\t\t\t\tfmt.Fprintf(c, "%s<br />\n", entry.Name);
\t\t\t}\n \t\t}\n \t});
@@ -262,120 +276,96 @@ func serveDir(c *http.Conn, dirname string) {
// ----------------------------------------------------------------------------
// Files
-func printErrors(c *http.Conn, filename string, errors errorList) {
+func serveCompilationErrors(c *http.Conn, filename string, errors errorList) {
// open file
path := *root + filename;
fd, err1 := os.Open(path, os.O_RDONLY, 0);
defer fd.Close();
if err1 != nil {
-\t\t// TODO better error handling
-\t\tlog.Stdoutf("%s: %v", path, err1);
+\t\tserveError(c, err1.String(), path);
+\t\treturn;
}
// read source
var buf io.ByteBuffer;
n, err2 := io.Copy(fd, &buf);
if err2 != nil {
-\t\t// TODO better error handling
-\t\tlog.Stdoutf("%s: %v", path, err2);
+\t\tserveError(c, err2.String(), path);
+\t\treturn;
}
src := buf.Data();
// TODO handle Apply errors
-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprint(c, filename);
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprint(c, filename);
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\t// section title
-\t\t\tfmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename);
-\t\t\t\n-\t\t\t// handle read errors
-\t\t\tif err1 != nil || err2 != nil /* 6g bug139 */ {
-\t\t\t\tfmt.Fprintf(c, "could not read file %s\n", filename);
-\t\t\t\treturn;\n-\t\t\t}\n-\t\t\t\n-\t\t\t// write source with error messages interspersed
-\t\t\tfmt.Fprintln(c, "<pre>");
-\t\t\toffs := 0;\n-\t\t\tfor i, e := range errors {
-\t\t\t\tif 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
-\t\t\t\t\t// TODO handle Write errors
-\t\t\t\t\tc.Write(src[offs : e.pos.Offset]);
-\t\t\t\t\t// TODO this should be done using a .css file
-\t\t\t\t\tfmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
-\t\t\t\t\toffs = e.pos.Offset;\n-\t\t\t\t} else {
-\t\t\t\t\tlog.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
-\t\t\t\t}\n+\tservePage(c, filename, func () {
+\t\t// section title
+\t\tfmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename);
+\t\t\n+\t\t// handle read errors
+\t\tif err1 != nil || err2 != nil /* 6g bug139 */ {
+\t\t\tfmt.Fprintf(c, "could not read file %s\n", filename);
+\t\t\treturn;\n+\t\t}\n+\t\t\n+\t\t// write source with error messages interspersed
+\t\tfmt.Fprintln(c, "<pre>");
+\t\toffs := 0;\n+\t\tfor i, e := range errors {
+\t\t\tif 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
+\t\t\t\t// TODO handle Write errors
+\t\t\t\tc.Write(src[offs : e.pos.Offset]);
+\t\t\t\t// TODO this should be done using a .css file
+\t\t\t\tfmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
+\t\t\t\toffs = e.pos.Offset;\n+\t\t\t} else {
+\t\t\t\tlog.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
\t\t\t}\n-\t\t\t// TODO handle Write errors
-\t\t\tc.Write(src[offs : len(src)]);
-\t\t\tfmt.Fprintln(c, "</pre>");
+\t\t}\n+\t\t// TODO handle Write errors
+\t\tc.Write(src[offs : len(src)]);
+\t\tfmt.Fprintln(c, "</pre>");
\t});
}
-func serveGoFile(c *http.Conn, dirname string, filenames []string) {
-\t// compute documentation
-\tvar doc docPrinter.PackageDoc;\n-\tfor i, filename := range filenames {\n-\t\tpath := *root + "/" + dirname + "/" + filename;\n-\t\tprog, errors := compile(path, parser.ParseComments);\n-\t\tif len(errors) > 0 {\n-\t\t\tc.SetHeader("content-type", "text/html; charset=utf-8");
-\t\t\tprintErrors(c, filename, errors);\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tif i == 0 {\n-\t\t\t// first package - initialize docPrinter
-\t\t\tdoc.Init(prog.Name.Value);\n-\t\t}\n-\t\tdoc.AddProgram(prog);\n+func serveGoSource(c *http.Conn, dirname string, filename string) {
+\tpath := dirname + "/" + filename;
+\tprog, errors := compile(*root + "/" + path, parser.ParseComments);
+\tif len(errors) > 0 {
+\t\tserveCompilationErrors(c, filename, errors);
+\t\treturn;
}
-\tc.SetHeader("content-type", "text/html; charset=utf-8");
-\t\n-\tgodoc_template.Apply(c, "<!--", template.Substitution {
-\t\t"TITLE-->" : func() {
-\t\t\tfmt.Fprintf(c, "%s - Go package documentation", doc.PackageName());
-\t\t},\n-\n-\t\t"HEADER-->" : func() {
-\t\t\tfmt.Fprintf(c, "%s - Go package documentation", doc.PackageName());
-\t\t},\n-\n-\t\t"TIMESTAMP-->" : func() {
-\t\t\tfmt.Fprint(c, time.UTC().String());
-\t\t},\n-\n-\t\t"CONTENTS-->" : func () {
-\t\t\t// write documentation
-\t\t\twriter := makeTabwriter(c); // for nicely formatted output
-\t\t\tdoc.Print(writer);\n-\t\t\twriter.Flush(); // ignore errors
-\t\t}\n+\tservePage(c, path + " - Go source", func () {
+\t\tfmt.Fprintln(c, "<pre>");
+\t\tvar p astPrinter.Printer;
+\t\twriter := makeTabwriter(c); // for nicely formatted output
+\t\tp.Init(writer, nil, nil, true);
+\t\tp.DoProgram(prog);
+\t\twriter.Flush(); // ignore errors
+\t\tfmt.Fprintln(c, "</pre>");
});
}
-func serveSrc(c *http.Conn, path string) {
+func serveHTMLFile(c *http.Conn, filename string) {
+\tsrc, err1 := os.Open(filename, os.O_RDONLY, 0);
+\tdefer src.Close();
+\tif err1 != nil {
+\t\tserveError(c, err1.String(), filename);
+\t\treturn
+\t}\n+\twritten, err2 := io.Copy(src, c);
+\tif err2 != nil {
+\t\tserveError(c, err2.String(), filename);
+\t\treturn
+\t}\n+}
+
+
+func serveFile(c *http.Conn, path string) {
dir, err := os.Stat(*root + path);
if err != nil {
-\t\tc.WriteHeader(http.StatusNotFound);\n-\t\tfmt.Fprintf(c, "Error: %v (%s)\n", err, path);\n+\t\tserveError(c, err.String(), path);
\treturn;
}
@@ -383,10 +373,11 @@ func serveSrc(c *http.Conn, path string) {
case dir.IsDirectory():
serveDir(c, path);
case isGoFile(dir):
-\t\tserveGoFile(c, "", []string{path});
+\t\tserveGoSource(c, "", path);
+\tcase isHTMLFile(dir):
+\t\tserveHTMLFile(c, *root + path);
default:
-\t\tc.WriteHeader(http.StatusNotFound);\n-\t\tfmt.Fprintf(c, "Error: Not a directory or .go file (%s)\n", path);\n+\t\tserveError(c, "Not a directory or .go file", path);
}
}
@@ -407,13 +398,15 @@ func main() {
}
makePackageMap();
-\n+\t\n+\tinstallHandler("/mem", makeFixedFileServer(GOROOT + "/doc/go_mem.html"));
+\tinstallHandler("/spec", makeFixedFileServer(GOROOT + "/doc/go_spec.html"));
installHandler(docPrefix, serveDoc);
-\tinstallHandler(srcPrefix, serveSrc);
+\tinstallHandler(filePrefix, serveFile);
+\n {\terr := http.ListenAndServe(":" + *port, nil);
\tif err != nil {
\t\tlog.Exitf("ListenAndServe: %v", err)
\t}\n }\n }
-\n-
usr/gri/pretty/astprinter.go
--- a/usr/gri/pretty/astprinter.go
+++ b/usr/gri/pretty/astprinter.go
@@ -5,22 +5,20 @@
package astPrinter
import (
-\t"io";
-\t"vector";
-\t"tabwriter";
+\t"ast";
"flag";
"fmt";
+\t"io";
+\t"os";
"strings";
-\t"utf8";
-\t"unicode";
-\n-\t"utils";
+\t"tabwriter";
"token";
-\t"ast";
-\t"template";
-\t"symboltable";
+\t"unicode";
+\t"utf8";
+\t"vector";
)
+\n
var (
debug = flag.Bool("ast_debug", false, "print debugging information");
@@ -75,9 +73,48 @@ func hasExportedNames(names []*ast.Ident) bool {
}
+// ----------------------------------------------------------------------------
+// TokenPrinter
+
+// TODO This is not yet used - should fix this.
+
+// An implementation of a TokenPrinter may be provided when
+// initializing an AST Printer. It is used to print tokens.
+//
+type TokenPrinter interface {
+ PrintLit(w io.Write, tok token.Token, value []byte);
+ PrintIdent(w io.Write, value string);
+ PrintToken(w io.Write, token token.Token);
+ PrintComment(w io.Write, value []byte);
+}
+
+
+type defaultPrinter struct {}
+
+func (p defaultPrinter) PrintLit(w io.Write, tok token.Token, value []byte) {
+ w.Write(value);
+}
+
+
+func (p defaultPrinter) PrintIdent(w io.Write, value string) {
+ fmt.Fprint(w, value);
+}
+
+
+func (p defaultPrinter) PrintToken(w io.Write, token token.Token) {
+ fmt.Fprint(w, token.String());
+}
+
+
+func (p defaultPrinter) PrintComment(w io.Write, value []byte) {
+ w.Write(value);
+}
+
+
// ----------------------------------------------------------------------------
// ASTPrinter
+\n
// Separators - printed in a delayed fashion, depending on context.
const (
none = iota;
@@ -101,14 +138,17 @@ type Printer struct {
// output
text io.Write;
+\t// token printing
+\ttprinter TokenPrinter;
+\n
// formatting control
html bool;
full bool; // if false, print interface only; print all otherwise
// comments
comments []*ast.Comment; // the list of unassociated comments
-\tcindex int; // the current comment group index
-\tcpos token.Position; // the position of the next comment group
+\tcindex int; // the current comment index
+\tcpos token.Position; // the position of the next comment
// current state
lastpos token.Position; // position after last string
@@ -144,10 +184,17 @@ func (P *Printer) nextComments() {
}
-func (P *Printer) Init(text io.Write, comments []*ast.Comment, html bool) {
+func (P *Printer) Init(text io.Write, tprinter TokenPrinter, comments []*ast.Comment, html bool) {
// writers
P.text = text;
-\t\n+\n+\t// token printing
+\tif tprinter != nil {
+\t\tP.tprinter = tprinter;
+\t} else {
+\t\tP.tprinter = defaultPrinter{};
+\t}\n+\n // formatting control
P.html = html;
@@ -227,7 +274,7 @@ func (P *Printer) newline(n int) {
func (P *Printer) TaggedString(pos token.Position, tag, s, endtag string) {
// use estimate for pos if we don't have one
offs := pos.Offset;
-\tif offs == 0 {
+\tif pos.Line == 0 {
\toffs = P.lastpos.Offset;
}\n
@@ -401,6 +448,17 @@ func (P *Printer) Error(pos token.Token, tok token.Token, msg string) {
}
+// An astPrinter implements io.Write.
+// TODO this is not yet used.
+func (P *Printer) Write(p []byte) (n int, err *os.Error) {
+// TODO
+// - no string conversion every time
+// - return proper results
+ P.String(noPos, string(p));
+ return len(p), nil;
+}
+
+
// ----------------------------------------------------------------------------
// HTML support
usr/gri/pretty/docprinter.go
--- a/usr/gri/pretty/docprinter.go
+++ b/usr/gri/pretty/docprinter.go
@@ -136,25 +136,27 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) {
\t\t\ttyp.methods[name] = fdoc;\n \t\t}\n \t\t// if the type wasn\'t found, it wasn\'t exported\n+\t\t// TODO: a non-exported type may still have exported functions\n+\t\t// determine what to do in that case\n+\t\treturn;\n+\t}\n \n-\t} else {\n-\t\t// perhaps a factory function\n-\t\t// determine result type, if any\n-\t\tif len(fun.Type.Results) >= 1 {\n-\t\t\tres := fun.Type.Results[0];\n-\t\t\tif len(res.Names) <= 1 {\n-\t\t\t\t// exactly one (named or anonymous) result type\n-\t\t\t\ttyp = doc.lookupTypeDoc(res.Type);\n-\t\t\t\tif typ != nil {\n-\t\t\t\t\ttyp.factories[name] = fdoc;\n-\t\t\t\t\treturn;\n-\t\t\t\t}\n+\t// perhaps a factory function\n+\t// determine result type, if any\n+\tif len(fun.Type.Results) >= 1 {\n+\t\tres := fun.Type.Results[0];\n+\t\tif len(res.Names) <= 1 {\n+\t\t\t// exactly one (named or anonymous) result type\n+\t\t\ttyp = doc.lookupTypeDoc(res.Type);\n+\t\t\tif typ != nil {\n+\t\t\t\ttyp.factories[name] = fdoc;\n+\t\t\t\treturn;\n \t\t\t}\n \t\t}\n-\n-\t\t// ordinary function\n-\t\tdoc.funcs[name] = fdoc;\n \t}\n+\n+\t// ordinary function\n+\tdoc.funcs[name] = fdoc;\n }\n \n \n@@ -279,18 +281,6 @@ func untabify(s []byte) []byte {\n }\n \n \n-func stripWhiteSpace(s []byte) []byte {\n-\ti, j := 0, len(s);\n-\tfor i < len(s) && s[i] <= \' \' {\n-\t\ti++;\n-\t}\n-\tfor j > i && s[j-1] <= \' \' {\n-\t\tj--\n-\t}\n-\treturn s[i : j];\n-}\n-\n-\n func stripCommentDelimiters(s []byte) []byte {\n \tswitch s[1] {\n \tcase \'/\': return s[2 : len(s)-1];\n@@ -308,8 +298,25 @@ const /* formatting mode */ (\n )\n \n func printLine(p *astPrinter.Printer, line []byte, mode int) {\n-\tindented := len(line) > 0 && line[0] == \'\\t\';\n-\tline = stripWhiteSpace(line);\n+\t// If a line starts with \" *\" (as a result of a vertical /****/ comment),\n+\t// strip it away. For an example of such a comment, see src/lib/flag.go.\n+\tif len(line) >= 2 && line[0] == \' \' && line[1] == \'*\' {\n+\t\tline = line[2 : len(line)];\n+\t}\n+\n+\t// The line is indented if it starts with a tab.\n+\t// In either case strip away a leading space or tab.\n+\tindented := false;\n+\tif len(line) > 0 {\n+\t\tswitch line[0] {\n+\t\tcase \'\\t\':\n+\t\t\tindented = true;\n+\t\t\tfallthrough;\n+\t\tcase \' \':\n+\t\t\tline = line[1 : len(line)];\n+\t\t}\n+\t}\n+\n \tif len(line) == 0 {\n \t\t// empty line\n \t\tswitch mode {\n@@ -426,7 +433,7 @@ func (t *typeDoc) print(p *astPrinter.Printer) {\n \n func (doc *PackageDoc) Print(writer io.Write) {\n \tvar p astPrinter.Printer;\n-\tp.Init(writer, nil, true);\n+\tp.Init(writer, nil, nil, true);\n \t\n \t// program header\n \tfmt.Fprintf(writer, "<h1>package %s</h1>\\n", doc.name);\n```
## コアとなるコードの解説
### `godoc.go`の変更
* **URLプレフィックスの変更**: `srcPrefix`が`filePrefix`にリネームされ、その値が`/file/`に変更されました。これにより、ソースファイルやディレクトリへのリンクがより明確な`/file/`プレフィックスを使用するようになりました。
* **`servePage`と`serveError`の導入**:
* `servePage`は、HTMLページの共通の構造(タイトル、ヘッダー、タイムスタンプ、コンテンツ)を`godoc.html`テンプレートを使って生成するためのヘルパー関数です。これにより、各コンテンツハンドラは`servePage`を呼び出し、コンテンツ部分を生成する無名関数を渡すだけでよくなり、HTML生成ロジックが大幅に簡素化されました。
* `serveError`は、エラーメッセージを表示する共通のページを生成するために`servePage`を利用します。
* **ハンドラの統合とリファクタリング**:
* `serveDir`、`printErrors`(`serveCompilationErrors`にリネーム)、`serveGoFile`(`serveGoSource`にリネーム)、`serveSrc`(`serveFile`にリネーム)といった既存のハンドラが、新しく導入された`servePage`と`serveError`を利用するように書き換えられました。これにより、コードの重複が減り、一貫したHTML出力が保証されます。
* `isHTMLFile`関数と`serveHTMLFile`関数が追加され、`.html`ファイルを直接提供する機能が導入されました。
* **静的ドキュメントの提供**: `main`関数内で、`/mem`と`/spec`のURLプレフィックスに対して、それぞれ`GOROOT/doc/go_mem.html`と`GOROOT/doc/go_spec.html`という静的なHTMLファイルを提供する`makeFixedFileServer`ハンドラがインストールされました。これにより、Goのメモリモデルと言語仕様のドキュメントが`godoc`を通じて直接提供されるようになりました。
* **パッケージマップ構築の改善**: `makePackageMap`関数内で、パッケージ情報を構築する際に一時的なローカルマップ`pmap`とスライス`plist`を使用し、最後にグローバル変数`pakMap`と`pakList`に割り当てるようになりました。これは、グローバルな状態を更新する際の一時的な不整合を避けるための一般的なパターンです。
### `astprinter.go`の変更
* **`TokenPrinter`インターフェースの導入**:
* `TokenPrinter`インターフェースは、ASTの要素(リテラル、識別子、トークン、コメント)をどのように文字列として出力するかを定義します。
* `defaultPrinter`は、このインターフェースのデフォルト実装を提供し、単に要素の値を書き出すだけです。
* **`Printer.Init`の変更**:
* `Printer`構造体の`Init`メソッドが、新しい`tprinter TokenPrinter`引数を受け取るようになりました。これにより、`Printer`の初期化時にカスタムの`TokenPrinter`を注入できるようになり、ASTの出力方法の柔軟性が向上しました。`tprinter`が`nil`の場合は`defaultPrinter`が使用されます。
* **コメント処理の微調整**: `cindex`と`cpos`のコメントが「コメントグループ」から「コメント」を指すように変更され、より粒度の細かいコメント処理が可能になったことを示唆しています。
* **`TaggedString`の条件変更**: `pos.Offset == 0`の代わりに`pos.Line == 0`で位置を推定するようになりました。これは、ソースコードの位置情報の扱いに関するより正確なロジックへの変更です。
### `docprinter.go`の変更
* **`addFunc`のロジック修正**: ファクトリ関数の検出ロジックが、非エクスポート型の場合でも適切に処理されるように修正されました。
* **`stripWhiteSpace`の削除と`printLine`への統合**:
* `stripWhiteSpace`関数が削除されました。
* その機能は`printLine`関数に統合され、コメント行の先頭にある空白、タブ、および`/* */`形式のコメントの`*`を自動的に取り除くようになりました。これにより、`godoc`が生成するドキュメント内のコメントの表示がよりきれいに整形されます。これは、コミットメッセージで言及されている「formatting of comments has been fixed」の具体的な実装です。
* **`PackageDoc.Print`の`astPrinter.Printer`初期化**: `PackageDoc.Print`内で`astPrinter.Printer`を初期化する際に、新しい`TokenPrinter`引数に`nil`を渡すようになりました。これは、この時点では`docprinter`がカスタムのトークンプリンターを必要としないことを示しています。
これらの変更は、`godoc`のWebインターフェースの使いやすさと、Goソースコードおよびドキュメントの表示品質を向上させるための重要なステップでした。特に、URL構造の明確化と、HTML生成ロジックの共通化は、ツールの保守性と拡張性を高める上で大きな意味を持ちます。
## 関連リンク
* Go言語公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
* `godoc`コマンドのドキュメント: [https://pkg.go.dev/golang.org/x/tools/cmd/godoc](https://pkg.go.dev/golang.org/x/tools/cmd/godoc) (現在の`godoc`ツールの情報)
* Go言語仕様: [https://go.dev/ref/spec](https://go.dev/ref/spec)
* Goメモリモデル: [https://go.dev/ref/mem](https://go.dev/ref/mem)
* `go/ast`パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* `go/token`パッケージ: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
* `net/http`パッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* `text/template`パッケージ: [https://pkg.go.dev/text/template](https://pkg.go.dev/text/template)
## 参考にした情報源リンク
* Go言語の公式リポジトリ (GitHub): [https://github.com/golang/go](https://github.com/golang/go)
* Go言語の初期開発に関する議論やメーリングリストのアーカイブ (例: golang-nuts): [https://groups.google.com/g/golang-nuts](https://groups.google.com/g/golang-nuts) (具体的なスレッドは特定していませんが、当時の開発状況を理解するための一般的な情報源です。)
* Go言語の歴史に関する記事やブログポスト (例: Go at Google: Language Design in the Service of Software Engineering): [https://go.dev/talks/2012/go4progs.slide#1](https://go.dev/talks/2012/go4progs.slide#1) (Goの設計思想と歴史的背景を理解するための一般的な情報源です。)
* GoのASTに関する解説記事 (例: The Go AST: A Guided Tour): [https://go.dev/blog/go-ast](https://go.dev/blog/go-ast) (現在のASTに関する情報ですが、基本的な概念は初期から共通しています。)
* Goの`godoc`に関する解説記事 (例: Godoc: documenting Go code): [https://go.dev/blog/godoc](https://go.dev/blog/godoc) (現在の`godoc`に関する情報ですが、ツールの目的と基本的な動作は初期から共通しています。)
* Goのコミット履歴を閲覧できるツール (例: `git log`コマンド、GitHubのコミット履歴ページ)
* Goのソースコード自体 (特に`src/cmd/go/internal/mod`や`src/go/`以下のパッケージ)
* GoのIssue Tracker (例: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)) (当時のバグ報告や機能要望が背景にある可能性がありますが、このコミットに直接関連する特定のIssueは特定していません。)