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

[インデックス 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=27049CL=27049は、Goプロジェクトが以前使用していたPerforceバージョン管理システムにおけるChange List (CL) 番号を示しており、このコミットがGoの非常に初期の段階で行われたものであることを裏付けています。

変更の背景

このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の、活発な内部開発段階にありました。godocツールは、Go言語の設計思想である「ドキュメントはコードと共に生きるべき」という原則を具現化するための重要なコンポーネントとして開発が進められていました。

初期のgodocは、おそらく機能のプロトタイプ段階であり、複数の異なるHTMLテンプレートを使用して、ディレクトリ一覧、エラー表示、パッケージ一覧、個別のパッケージドキュメントなどを生成していました。しかし、このようなアプローチは、テンプレートの管理が煩雑になり、将来的な機能拡張やデザイン変更の際にオーバーヘッドとなる可能性がありました。

このコミットの背景には、以下の目的があったと考えられます。

  1. コードベースの簡素化と保守性の向上: 複数のテンプレートを単一の汎用テンプレートに集約することで、godocのレンダリングロジックを簡素化し、コードの保守性を高めることを目指しました。これにより、HTML構造の変更がより一元的に行えるようになります。
  2. 依存関係の整理: 特定のローカルファイルへのハードコードされた依存を排除し、より柔軟でポータブルな設計に移行することで、godocのデプロイや利用を容易にしました。
  3. ドキュメントサーバー機能の安定化: godocがGoパッケージのドキュメントを自動的に発見し、提供する基本的な機能を確立し、その運用を安定させることを目指しました。これは、Go言語のユーザーがコードベースのドキュメントに簡単にアクセスできるようにするための基盤となります。
  4. 開発効率の向上: クリーンアップと機能改善を通じて、godocの開発プロセスを効率化し、将来的な機能追加のための強固な基盤を築くことを意図していました。

このコミットは、godocが単なるドキュメント生成ツールから、Goエコシステムにおける重要なドキュメント提供インフラへと進化する過程の重要な一歩を示しています。

前提知識の解説

このコミットの変更内容を深く理解するためには、以下の前提知識が役立ちます。

  1. Go言語の初期開発とバージョン管理:

    • PerforceとChange List (CL) 番号: Go言語は、初期にはGoogle内部でPerforceというバージョン管理システムを使用して開発されていました。Perforceでは、変更のまとまりを「Change List (CL)」と呼び、一意の番号が割り当てられます。このコミットメッセージにあるOCL=27049CL=27049は、その名残であり、このコミットがGoがGitに移行する前の、非常に初期の段階のものであることを示しています。
    • GOROOT環境変数: Goの初期から存在する重要な環境変数で、Goのインストールディレクトリ(標準ライブラリやツールなどが配置されている場所)を指します。godocのようなツールは、このGOROOTを基準にGoパッケージを探します。
  2. 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マップで定義された関数を実行し、動的なコンテンツを挿入します。
  3. Goのosパッケージとファイルシステム操作:

    • os.Open(): ファイルを開くための関数。
    • os.Dir: ディレクトリ内のエントリ(ファイルまたはサブディレクトリ)を表す構造体。
    • os.Stat(): ファイルやディレクトリの情報を取得する関数。
    • IsDirectory(): os.Dirのメソッドで、エントリがディレクトリであるかどうかを判定します。
    • IsRegular(): os.Dirのメソッドで、エントリが通常のファイルであるかどうかを判定します。
  4. Goのnet/httpパッケージ(初期バージョン):

    • http.Conn: HTTP接続を表す構造体。
    • http.Request: HTTPリクエストを表す構造体。
    • http.ServeMux: HTTPリクエストをハンドラにルーティングするためのマルチプレクサ。
    • http.Handle(): 特定のパスに対するハンドラを登録する関数。
    • http.ListenAndServe(): HTTPサーバーを起動する関数。
  5. GoのコンパイルとAST (Abstract Syntax Tree):

    • parserパッケージ: Goのソースコードを解析し、ASTを生成するためのパッケージ。
    • astパッケージ: Goのソースコードの抽象構文木(AST)を表現するためのデータ構造を提供するパッケージ。
    • tokenパッケージ: Goのソースコードのトークン(キーワード、識別子、演算子など)と位置情報(ファイル名、行番号、オフセット)を定義するパッケージ。
    • コンパイルエラーハンドリング: Goのコンパイラやパーサーは、エラーが発生した場合にエラー情報を報告するメカニズムを持っています。このコミットでは、parseErrorerrorListerrorHandlerといったカスタム型を導入し、より構造化されたエラー収集と表示を実現しています。

これらの知識を持つことで、コミットがGoの初期エコシステムにおいてどのような役割を果たし、どのように技術的な課題を解決しようとしたのかをより深く理解できます。

技術的詳細

このコミットの技術的詳細は、主にgodocツールの内部構造とHTMLレンダリングロジックの変更に集約されます。

  1. テンプレートエンジンの変更と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.Fprintffmt.Fprintlnを直接使用してHTMLタグとコンテンツを書き出すように変更されています。これは、テンプレートの役割をより限定的なものにし、HTML構造の大部分をGoコード内で直接制御するという設計思想の変更を示唆しています。これにより、テンプレートファイル自体の複雑さを減らし、GoコードがHTMLのレンダリングロジックをより直接的に管理できるようになります。
  2. パス処理の改善:

    • godoc.gocleanPathsanitizePathという新しい関数が導入されました。
      • cleanPath(s string) string: パス内の連続するスラッシュ(//)を単一のスラッシュに正規化します。
      • sanitizePath(s string) string: cleanPathを呼び出した後、末尾のスラッシュを削除します(ただし、ルートパス/の場合は削除しない)。
    • これらの関数は、HTTPリクエストのURLパスやファイルシステムのパスを処理する際に使用され、パスの正規化とセキュリティの向上に貢献します。以前はUtils.SanitizePathを使用していた箇所が、これらの新しい内部関数に置き換えられました。
  3. コンパイルエラーハンドリングの強化:

    • godoc.go内にparseErrorerrorListerrorHandlerという新しい型が定義されました。
      • parseError: トークンの位置(token.Position)とエラーメッセージ(msg)を持つ構造体で、パースエラーの詳細を保持します。
      • errorList: parseErrorのスライスであり、sort.Interfaceを実装しているため、エラーを位置でソートできます。
      • errorHandler: parser.Parseに渡されるエラーハンドラで、エラーを収集し、重複するエラー(同じ行の複数エラー)を避けるロジックを含みます。
    • compile関数が新しく導入されました。この関数は、指定されたGoファイルのパスとパースモードを受け取り、ASTとソートされたエラーリストを返します。以前はCompilation.Compileparser.Parseを直接呼び出していましたが、compile関数がエラーハンドリングロジックをカプセル化し、より堅牢なコンパイル処理を提供します。これにより、godocがGoソースファイルを解析する際のエラー報告が改善されました。
  4. 依存関係の整理とビルドプロセスの変更:

    • Makefileが修正され、godoc.6pretty.6のビルド依存関係が簡素化されました。特に、godoc.6は以前のutils.6, platform.6, compilation.6, docprinter.6からdocprinter.6, compilation.6へと依存が減っています。これは、コードのモジュール化が進み、不要な依存が排除されたことを示唆しています。
    • godoc.go内のインポートリストも整理され、一部のパッケージ(例: utils, platform)への直接的な依存が削除されました。これは、これらの機能がgodoc内部に統合されたか、より汎用的なGo標準ライブラリの機能に置き換えられたことを意味します。

これらの変更は、godocがより効率的で、保守しやすく、そして堅牢なドキュメントサーバーとして機能するための基盤を築きました。特に、テンプレートの統合とエラーハンドリングの改善は、初期のGoツールの開発における品質向上へのコミットメントを示しています。

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

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

  1. usr/gri/pretty/Makefile:

    • installターゲットにgodocuntabのコピーが追加されました。
    • cleanターゲットにuntabgodocの削除が追加されました。
    • godoc.6pretty.6のビルド依存関係が簡素化されました。特にgodoc.6utils.6platform.6への依存が削除されています。
  2. usr/gri/pretty/dir_template.html (削除)

  3. usr/gri/pretty/error_template.html (削除)

  4. usr/gri/pretty/packages_template.html (削除)

  5. usr/gri/pretty/template.html (削除)

    • これら4つのHTMLテンプレートファイルが削除されました。
  6. usr/gri/pretty/docprinter.go:

    • templateパッケージのインポートが削除されました。
    • templ = template.NewTemplateOrDie("template.html")というグローバル変数の定義が削除されました。
    • func (doc *PackageDoc) Print(writer io.Write)メソッド内で、以前templ.Applyを使用していた部分が、fmt.Fprintffmt.Fprintlnを直接使用してHTMLコンテンツを書き出すように大幅に書き換えられました。これにより、HTMLの構造がGoコード内に直接埋め込まれる形になりました。
    • PackageName()という新しいメソッドがPackageDoc構造体に追加されました。
  7. usr/gri/pretty/godoc.go:

    • timetokenパッケージが新しくインポートされました。
    • utilsplatformパッケージのインポートが削除されました。
    • 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_templatecompile関数を使用するように大幅に修正されました。特に、これらの関数はHTMLテンプレートの適用方法を変更し、より汎用的なテンプレートを使用するように変更されました。
    • getAST関数が削除され、その機能は新しいcompile関数に置き換えられました。
    • addFile関数がgetASTの代わりにcompile関数を使用するように変更されました。
    • makePackageMap関数にverboseモードでのログ出力が追加されました。
    • serve関数とmain関数で、パスのサニタイズにUtils.SanitizePathの代わりに新しいsanitizePath関数が使用されるようになりました。

これらの変更は、godocのアーキテクチャを根本的に変更し、よりシンプルで統合されたHTMLレンダリングと、堅牢なコンパイルエラーハンドリングを実現しています。

コアとなるコードの解説

usr/gri/pretty/docprinter.go の変更

以前のdocprinter.goPrintメソッドは、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をソート可能にするための型です。
  • errorHandlerparser.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生成ロジックをより統合するというコミットの目標を達成しています。

関連リンク

参考にした情報源リンク