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

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

このコミットは、Go言語のドキュメンテーションツールである godoc に、テキストモードでGoのコード例(Examples)を表示する機能を追加するものです。具体的には、godoc コマンドに -ex フラグが導入され、これによりパッケージや関数のドキュメントを表示する際に、関連するコード例も一緒に表示されるようになります。

コミット

commit d97b975d5c1f87ecdda29211c46fa81b747248dc
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date:   Mon Feb 25 10:37:17 2013 +1100

    cmd/godoc: show examples in text mode
    
    Added the command line flag -ex to godoc to print examples in
    text output.
    
    Samples from the generated output:
    
    $ godoc -ex strings Index
    ...
    func Index(s, sep string) int
        Index returns the index of the first instance of sep in s, or -1 if sep
        is not present in s.
    
        Example:
            fmt.Println(strings.Index("chicken", "ken"))
            fmt.Println(strings.Index("chicken", "dmr"))
            // Output:
            // 4
            // -1
    ...
    
    $ godoc -ex container/heap
    ...
    package heap
        import "container/heap"
    
        Package heap provides heap operations for any type that implements
        heap.Interface. A heap is a tree with the property that each node is the
        minimum-valued node in its subtree.
    
        Example:
            // This example demonstrates an integer heap built using the heap interface.
            package heap_test
    
            import (
                "container/heap"
                "fmt"
            ...)
    
        Example:
            // This example demonstrates a priority queue built using the heap interface.
            package heap_test
    
            import (
                "container/heap"
                "fmt"
            )
    ...
    
    Fixes #3587.
    
    R=golang-dev, minux.ma, adg, rsc, gri
    CC=golang-dev
    https://golang.org/cl/7356043

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

https://github.com/golang/go/commit/d97b975d5c1f87ecdda29211c46fa81b747248dc

元コミット内容

このコミットの元々の目的は、godoc コマンドラインツールがテキスト出力モードでGoのExampleコードを表示できるようにすることです。これまでは、godoc はHTMLモード(ウェブインターフェース)ではExampleを表示できましたが、コマンドラインでのテキスト出力では表示されませんでした。この機能追加により、開発者はターミナルから直接、より包括的なドキュメント(コード例を含む)を参照できるようになります。

コミットメッセージには、godoc -ex strings Indexgodoc -ex container/heap といった具体的な使用例が示されており、出力されるExampleのフォーマットも確認できます。

変更の背景

この変更の背景には、Go言語のドキュメンテーションシステムにおけるExampleの重要性があります。Goでは、Example 関数として記述されたコードスニペットが、パッケージや関数の使用方法を具体的に示すための重要な手段として推奨されています。これらはテストとしても機能し、go test コマンドで実行可能であり、同時に godoc によってドキュメントとして表示されます。

しかし、このコミットが作成される以前は、godoc のコマンドラインインターフェース(テキストモード)ではこれらのExampleが表示されませんでした。これは、開発者がターミナルでドキュメントを参照する際に、Exampleという重要な情報源が欠落していることを意味しました。

コミットメッセージにある Fixes #3587 は、この変更がGitHubのIssue #3587を解決するものであることを示しています。このIssueは、godoc のテキスト出力にExampleを含めることの要望であったと推測されます。開発者の利便性向上と、Goのドキュメンテーション文化におけるExampleの活用を促進することが、この変更の主な動機です。

前提知識の解説

Go言語のドキュメンテーションとExample

Go言語では、コードのコメントがそのままドキュメンテーションとして機能します。特に、Example 関数は、特定の関数やパッケージの使用例を示すために用いられます。これらの関数は Example<Name> の形式で命名され、go test コマンドで実行可能なテストとしても扱われます。// Output: コメントをExample関数内に記述することで、そのExampleの実行結果をドキュメントに含めることができ、go test は実際の出力とこのコメントを比較してExampleが正しく動作するかを検証します。

godoc ツール

godoc はGo言語の標準ドキュメンテーションツールです。Goのソースコードからドキュメントを抽出し、ウェブインターフェースまたはコマンドラインで表示します。ウェブインターフェースでは、Exampleを含む完全なドキュメントが表示されますが、このコミット以前はコマンドライン(テキストモード)ではExampleが表示されませんでした。

text/template パッケージ

Goの標準ライブラリである text/template パッケージは、テキストベースのテンプレートを生成するための強力なツールです。HTML、XML、プレーンテキストなど、様々な形式の出力を生成するのに使用されます。godoc はこのパッケージを利用して、Goのソースコードから抽出したドキュメント情報を整形し、最終的な出力(HTMLまたはテキスト)を生成しています。テンプレート内では、データ構造のフィールドにアクセスしたり、関数を呼び出したりすることができます。

flag パッケージ

Goの標準ライブラリである flag パッケージは、コマンドライン引数を解析するための機能を提供します。このコミットでは、godoc コマンドに新しいフラグ -ex を追加するために flag パッケージが使用されています。

技術的詳細

このコミットの技術的な核心は、godoc がテキスト出力時にExampleをレンダリングするための新しいロジックと、それを制御するコマンドラインフラグの追加です。

  1. -ex フラグの追加: src/cmd/godoc/godoc.goshowExamples = flag.Bool("ex", false, "show examples in command line mode") が追加されました。これにより、godoc コマンドに -ex フラグが導入され、デフォルトでは false(Exampleを非表示)ですが、フラグが指定されると true になりExampleが表示されるようになります。

  2. example_textFunc の実装: src/cmd/godoc/godoc.goexample_textFunc という新しい関数が追加されました。この関数は text/template パッケージから呼び出されるカスタム関数として登録され、Exampleのテキスト表現を生成する役割を担います。

    • showExamples フラグが false の場合、この関数は空文字列を返し、Exampleは表示されません。
    • Exampleのコードは printer.CommentedNode を使用して整形され、bytes.Buffer に書き込まれます。
    • Exampleのコードが {...} で囲まれている場合(関数本体の形式)、外側の波括弧が削除され、インデントが調整されます。これは、Exampleが通常、関数本体として記述されるため、ドキュメント表示時にはその部分を整形する必要があるためです。
    • 最終的に、整形されたExampleコードは Example: というプレフィックスと適切なインデントと共にバッファに書き込まれ、文字列として返されます。
  3. テンプレートの変更 (lib/godoc/package.txt): lib/godoc/package.txt は、godoc がテキストモードでドキュメントを生成する際に使用するテンプレートファイルです。このファイルに {{example_text ...}} というテンプレートアクションが追加されました。

    • パッケージ全体のドキュメント、関数、型、メソッドのそれぞれに対して、関連するExampleを表示するための example_text 関数呼び出しが追加されています。
    • これにより、godoc がドキュメントをレンダリングする際に、example_textFunc が呼び出され、-ex フラグの状態に応じてExampleがテキスト出力に組み込まれるようになります。

この変更により、godoc はHTML出力と同様に、テキスト出力でもExampleを適切に整形して表示できるようになり、コマンドラインでのドキュメント参照体験が大幅に向上しました。

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

lib/godoc/package.txt

このファイルは、godoc がテキストモードでドキュメントを生成する際に使用するGoの text/template テンプレートです。

--- a/lib/godoc/package.txt
+++ b/lib/godoc/package.txt
@@ -9,7 +9,8 @@ package {{.Name}}\n \n {{else}}COMMAND DOCUMENTATION\n \n-{{end}}{{comment_text .Doc "    " "\t"}}{{/*\n+{{end}}{{comment_text .Doc "    " "\t"}}\n+{{example_text "" $.Examples $.FSet "    "}}{{/*
 \n ---------------------------------------\n \n@@ -36,6 +37,7 @@ FUNCTIONS\n \n {{range .}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n+{{example_text .Name $.Examples $.FSet "    "}}\n {{end}}{{end}}{{/*
 \n ---------------------------------------\n@@ -43,16 +45,19 @@ FUNCTIONS\n */}}{{with .Types}}\n TYPES\n \n-{{range .}}{{node .Decl $.FSet}}\n+{{range .}}{{$tname := .Name}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n {{range .Consts}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n {{end}}{{range .Vars}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n-{{end}}{{range .Funcs}}{{node .Decl $.FSet}}\n+{{end}}{{example_text .Name $.Examples $.FSet "    "}}\n+{{range .Funcs}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n+{{example_text .Name $.Examples $.FSet "    "}}\n {{end}}{{range .Methods}}{{node .Decl $.FSet}}\n {{comment_text .Doc "    " "\t"}}\n+{{$name := printf "%s_%s" $tname .Name}}{{example_text $name $.Examples $.FSet "    "}}\n {{end}}{{end}}{{end}}{{/*
 \n ---------------------------------------\

src/cmd/godoc/godoc.go

このファイルは godoc コマンドの主要なロジックを含んでいます。

--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -65,6 +65,7 @@ var (\
 	showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
 	templateDir    = flag.String("templates", "", "directory containing alternate template files")
 	showPlayground = flag.Bool("play", false, "enable playground in web interface")
+	showExamples   = flag.Bool("ex", false, "show examples in command line mode")
 
 	// search index
 	indexEnabled = flag.Bool("index", false, "enable search index")
@@ -329,6 +330,47 @@ func stripExampleSuffix(name string) string {
 	return name
 }
 
+func example_textFunc(funcName string, examples []*doc.Example, fset *token.FileSet, indent string) string {
+	if !*showExamples {
+		return ""
+	}
+
+	var buf bytes.Buffer
+	first := true
+	for _, eg := range examples {
+		name := stripExampleSuffix(eg.Name)
+		if name != funcName {
+			continue
+		}
+
+		if !first {
+			buf.WriteString("\n")
+		}
+		first = false
+
+		// print code
+		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+		var buf1 bytes.Buffer
+		writeNode(&buf1, fset, cnode)
+		code := buf1.String()
+		// Additional formatting if this is a function body.
+		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
+			// remove surrounding braces
+			code = code[1 : n-1]
+			// unindent
+			code = strings.Replace(code, "\n    ", "\n", -1)
+		}
+		code = strings.Trim(code, "\n")
+		code = strings.Replace(code, "\n", "\n\t", -1)
+
+		buf.WriteString(indent)
+		buf.WriteString("Example:\n\t")
+		buf.WriteString(code)
+		buf.WriteString("\n")
+	}
+	return buf.String()
+}
+
 func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string {
 	var buf bytes.Buffer
 	for _, eg := range examples {
@@ -494,6 +536,7 @@ var fmap = template.FuncMap{
 
 	// formatting of Examples
 	"example_html":   example_htmlFunc,
+	"example_text":   example_textFunc,
 	"example_name":   example_nameFunc,
 	"example_suffix": example_suffixFunc,
 }

コアとなるコードの解説

lib/godoc/package.txt の変更点

このテンプレートファイルでは、{{example_text ...}} という新しいテンプレートアクションが複数箇所に追加されています。

  • {{example_text "" $.Examples $.FSet " "}}: これはパッケージ全体のExampleを表示するために追加されました。最初の引数 "" は、特定の関数名に紐づかないパッケージレベルのExampleを意味します。
  • {{example_text .Name $.Examples $.FSet " "}}: これは関数や型のExampleを表示するために追加されました。.Name は現在の関数や型の名前を example_textFunc に渡し、その関数や型に関連するExampleをフィルタリングして表示します。
  • {{$tname := .Name}}{{example_text $name $.Examples $.FSet " "}} および {{$name := printf "%s_%s" $tname .Name}}{{example_text $name $.Examples $.FSet " "}}: これらは型やメソッドのExampleを表示するために追加されました。特にメソッドの場合、printf "%s_%s" $tname .Name を使用して TypeName_MethodName の形式でExample名を構築し、関連するExampleを正確に取得できるようにしています。

これらの変更により、godoc はテキスト出力時に、パッケージ、関数、型、メソッドのそれぞれに関連するExampleを、テンプレートエンジンを通じて動的に組み込むことができるようになりました。

src/cmd/godoc/godoc.go の変更点

  1. showExamples フラグの定義: var showExamples = flag.Bool("ex", false, "show examples in command line mode") この行は、godoc コマンドに -ex という新しいブーリアン型のコマンドラインフラグを追加します。デフォルト値は false で、Exampleは表示されません。ユーザーが godoc -ex ... と実行した場合に true になります。

  2. example_textFunc 関数の追加: この関数は、godoc のテキスト出力用にExampleを整形するロジックをカプセル化しています。

    • if !*showExamples { return "" }: このガード句により、-ex フラグが指定されていない場合は、Exampleの処理をスキップし、空文字列を返します。これにより、Exampleの表示がユーザーの選択に依存するようになります。
    • for _, eg := range examples: 渡されたExampleのスライスをイテレートします。
    • name := stripExampleSuffix(eg.Name): Example名から _test などのサフィックスを削除します。
    • if name != funcName { continue }: funcName と一致するExampleのみを処理します。これにより、特定の関数や型に関連するExampleのみが表示されます。
    • Exampleのコードの整形:
      • cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}: ExampleのASTノードとコメントを printer.CommentedNode にラップします。
      • writeNode(&buf1, fset, cnode): go/printer パッケージを使用して、ASTノードをコード文字列に変換します。
      • if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}': Exampleが関数本体として記述されている場合(func ExampleFoo() { ... } のように波括弧で囲まれている場合)を検出します。
      • code = code[1 : n-1]: 外側の波括弧を削除します。
      • code = strings.Replace(code, "\n ", "\n", -1): Exampleコード内の余分なインデントを削除し、整形します。
      • code = strings.Trim(code, "\n"): 先頭と末尾の改行を削除します。
      • code = strings.Replace(code, "\n", "\n\t", -1): 各行の先頭にタブ文字を追加し、適切なインデントを適用します。
    • buf.WriteString(indent): テンプレートから渡されたインデント文字列を追加します。
    • buf.WriteString("Example:\n\t"): "Example:" というプレフィックスとそれに続くタブインデントを追加します。
    • buf.WriteString(code): 整形されたExampleコードを書き込みます。
    • buf.WriteString("\n"): Exampleの終わりに改行を追加します。
  3. fmap への登録: "example_text": example_textFunc, この行は、example_textFunctext/template の関数マップ fmapexample_text という名前で登録します。これにより、package.txt テンプレート内で {{example_text ...}} としてこの関数を呼び出すことが可能になります。

これらの変更により、godoc はコマンドラインでExampleを表示する機能が追加され、その表示はユーザーが -ex フラグを指定した場合にのみ有効になります。Exampleの整形ロジックも、テキスト出力に適した形に調整されています。

関連リンク

参考にした情報源リンク