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

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

このコミットは、Go言語の公式ドキュメンテーションツールであるgodocの機能改善に関するものです。具体的には、コマンドのドキュメントにおける見出しにアンカー(HTMLのid属性)を追加し、クライアントサイドでの目次生成をより適切に行うための変更が含まれています。また、コマンドドキュメントにおいては、サーバーサイドでの目次生成を無効化しています。

コミット

commit 3358a5068a87bc25bd551698f4f0be7c5677168d
Author: Andrew Gerrand <adg@golang.org>
Date:   Thu Jan 19 18:59:06 2012 +1100

    godoc: add anchors to cmd documentation headings
    
    Also, disable server-side generation of TOC for commands as they would
    only ever show Overview. The JS does a better job (for now).
    
    Fixes #2732.
    
    R=gri, dsymonds
    CC=golang-dev
    https://golang.org/cl/5558046

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

https://github.com/golang/go/commit/3358a5068a87bc25bd551698f4f0be7c5677168d

元コミット内容

godoc: コマンドドキュメントの見出しにアンカーを追加。 また、コマンドの目次が「概要」しか表示しないため、サーバーサイドでの目次生成を無効化。JavaScriptが(現時点では)より良い仕事をする。 Issue #2732 を修正。

変更の背景

この変更の背景には、godocツールが生成するドキュメント、特にGoコマンド(例: go build, go runなど)のドキュメントにおけるユーザビリティの向上が挙げられます。

  1. 見出しへの直接リンクの必要性: 従来のgodocが生成するHTMLドキュメントでは、各セクションの見出し(h3タグなど)に一意のID(アンカー)が付与されていませんでした。これにより、特定のセクションへの直接リンクを共有したり、ブラウザの「ページ内検索」機能で正確な位置にジャンプしたりすることが困難でした。ユーザーはドキュメント全体をスクロールして目的のセクションを探す必要がありました。

  2. コマンドドキュメントの目次問題: godocはパッケージドキュメントに対しては詳細な目次を生成しますが、コマンドドキュメントの場合、その構造上、サーバーサイドで生成される目次が「概要 (Overview)」といった非常に限定的な内容しか表示しないという問題がありました。これはユーザーにとって有用な目次とは言えず、むしろスペースの無駄になっていました。

  3. クライアントサイド(JavaScript)の優位性: 一方で、godocにはクライアントサイドでJavaScriptを用いて目次を動的に生成する機能も存在しました。このJavaScriptによる目次生成は、HTMLのDOM構造を解析してより柔軟かつ詳細な目次を作成できるため、サーバーサイドの限定的な目次よりも優れていました。

これらの問題を解決し、ユーザーがgodocで生成されたドキュメントをより効率的に閲覧できるようにするために、本コミットが実施されました。特に、Issue #2732 はこのアンカー追加の必要性を指摘していたものと考えられます。

前提知識の解説

このコミットを理解するためには、以下の技術的知識が役立ちます。

  1. Go言語のgodocツール:

    • godocはGo言語の標準ドキュメンテーションツールです。Goのソースコード内のコメント(特にエクスポートされた識別子に付随するコメント)を解析し、HTML形式でドキュメントを生成します。
    • 開発者はgodoc -http=:6060のように実行することで、ローカルでドキュメントサーバーを立ち上げ、ブラウザでGoの標準ライブラリや自身のプロジェクトのドキュメントを閲覧できます。
    • godocは、Goのパッケージだけでなく、Goコマンド(例: go build, go run)のドキュメントも表示します。
  2. HTMLのアンカー(id属性):

    • HTML要素に付与されるid属性は、その要素を一意に識別するためのグローバル属性です。
    • id属性を持つ要素には、URLのフラグメント識別子(例: https://example.com/page.html#section-id)を使って直接リンクすることができます。これにより、ページの特定の位置にジャンプすることが可能になります。
    • JavaScriptから特定の要素にアクセスする際にもdocument.getElementById()などで利用されます。
  3. 目次 (Table of Contents - TOC):

    • ドキュメント内の主要な見出しを一覧表示し、各見出しへのリンクを提供するナビゲーション要素です。
    • サーバーサイド生成: サーバーがHTMLを生成する際に、ドキュメントの内容を解析して目次をHTMLに埋め込む方式です。静的なコンテンツに適しています。
    • クライアントサイド生成: ブラウザがHTMLを読み込んだ後、JavaScriptがDOM(Document Object Model)を解析し、動的に目次要素を生成してページに挿入する方式です。ページの読み込み後にコンテンツが動的に変更される場合や、より複雑なロジックで目次を生成したい場合に適しています。
  4. Go言語のtext/templateパッケージ:

    • Go言語の標準ライブラリで、データ構造をテキスト(HTML、XML、プレーンテキストなど)に変換するためのテンプレートエンジンを提供します。
    • godocは内部でこのテンプレートエンジンを使用してHTMLドキュメントを生成しています。
    • {{if .IsPkg}}のような構文は、Goテンプレートにおける条件分岐であり、.IsPkgという変数が真の場合にのみ、そのブロック内のHTMLが出力されます。
  5. 正規表現 (Regular Expression):

    • 文字列のパターンマッチングや置換を行うための強力なツールです。
    • このコミットでは、見出しテキストからHTMLのIDとして安全な文字列を生成するために使用されています。

技術的詳細

このコミットは、主に以下の3つのファイルにわたる変更で構成されており、それぞれが異なる役割を担っています。

  1. src/pkg/go/doc/comment.go:

    • このファイルは、Goのソースコードコメントを解析し、HTMLに変換するロジックを担っています。
    • アンカーID生成ロジックの追加:
      • nonAlphaNumRx = regexp.MustCompile([^a-zA-Z0-9]): 英数字以外の文字にマッチする正規表現が定義されました。
      • func anchorID(line string) string: この新しい関数は、入力された文字列(見出しテキスト)から、正規表現を使って英数字以外の文字をアンダースコア(_)に置換し、HTMLのid属性として安全な文字列を生成します。例えば、「Go Command Line」という見出しは「Go_Command_Line」のようなIDに変換されます。
    • HTML出力の変更:
      • ToHTML関数内のopHead(見出し処理)のケースが修正されました。
      • 以前は単に<h3>タグを出力していましたが、変更後はanchorID関数を使って生成したIDをid属性として<h3>タグに埋め込むようになりました。具体的には、<h3 id="generated_id">という形式で出力されます。これにより、各見出しに一意のアンカーが設定され、直接リンクが可能になります。
  2. lib/godoc/package.html:

    • これはgodocがHTMLドキュメントを生成する際に使用するGoテンプレートファイルです。
    • サーバーサイド目次生成の条件化:
      • manual-navというIDを持つdiv要素(サーバーサイドで生成される目次が含まれる部分)全体が、{{if .IsPkg}}...{{end}}というGoテンプレートの条件分岐で囲まれました。
      • .IsPkgは、現在表示しているドキュメントがGoパッケージである場合にtrueとなるコンテキスト変数です。
      • この変更により、コマンドドキュメント(.IsPkgfalseとなる)の場合には、サーバーサイドで目次が生成されなくなり、HTML出力からmanual-navブロックが完全に削除されます。これは、コマンドドキュメントの目次が「概要」しか表示しないという問題を解決するためのものです。
  3. doc/godocs.js:

    • このJavaScriptファイルは、クライアントサイドで動的に目次を生成するスクリプトです。
    • クライアントサイド目次生成ロジックの改善:
      • godocs_generateTOC()関数がリファクタリングされました。
      • 以前はh2h3タグに対して別々の処理ブロックがありましたが、これらが統合され、より汎用的なロジックになりました。
      • すべてのh2およびh3要素に対して、もしid属性がなければ、JavaScript側で一時的なID(tmp_プレフィックス付き)を生成して付与するようになりました。
      • これにより、サーバーサイドでIDが付与されていない見出し(例えば、godocが生成する他の種類のドキュメントや、将来的に変更される可能性のあるHTML構造)に対しても、クライアントサイドのJavaScriptが確実にアンカーを生成し、目次を作成できるようになります。
      • h2タグはdt要素(定義リストの用語)、h3タグはdd要素(定義リストの定義)として目次項目を生成するロジックは維持されています。

これらの変更により、godocはコマンドドキュメントの見出しにアンカーを付与し、サーバーサイドの冗長な目次を削除し、クライアントサイドのJavaScriptがより堅牢に目次を生成できるようになりました。

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

src/pkg/go/doc/comment.go

--- a/src/pkg/go/doc/comment.go
+++ b/src/pkg/go/doc/comment.go
@@ -68,7 +68,8 @@ var (
 	thtml_endp   = []byte("</p>\\n")
 	thtml_pre    = []byte("<pre>")
 	thtml_endpre = []byte("</pre>\\n")
-	thtml_h      = []byte("<h3>")
+	thtml_h      = []byte(`<h3 id="`)
+	thtml_hq     = []byte(`">`)
 	thtml_endh   = []byte("</h3>\\n")
 )
 
@@ -225,6 +226,12 @@ type block struct {
 	lines []string
 }
 
+var nonAlphaNumRx = regexp.MustCompile(`[^a-zA-Z0-9]`)
+
+func anchorID(line string) string {
+	return nonAlphaNumRx.ReplaceAllString(line, "_")
+}
+
 // ToHTML converts comment text to formatted HTML.
 // The comment was prepared by DocReader,
 // so it is known not to have leading, trailing blank lines
@@ -253,9 +260,18 @@ func ToHTML(w io.Writer, text string, words map[string]string) {
 		case opHead:
 			w.Write(html_h)
+			id := ""
 			for _, line := range b.lines {
+				if id == "" {
+					id = anchorID(line)
+					w.Write([]byte(id))
+					w.Write(html_hq)
+				}
 				commentEscape(w, line, true)
 			}
+			if id == "" {
+				w.Write(html_hq)
+			}
 			w.Write(html_endh)
 		case opPre:
 			w.Write(html_pre)

lib/godoc/package.html

--- a/lib/godoc/package.html
+++ b/lib/godoc/package.html
@@ -3,6 +3,7 @@
 	Use of this source code is governed by a BSD-style
 	license that can be found in the LICENSE file.
 -->
+{{if .IsPkg}}
 <!-- Table of contents; must be named manual-nav to turn off auto nav. -->
 <div id="manual-nav">
 {{with .PDoc}}
@@ -37,6 +38,7 @@
 	</dl>
 {{end}}
 </div>
+{{end}}
 
 <!-- Main page -->		
 {{with .PAst}}

doc/godocs.js

--- a/doc/godocs.js
+++ b/doc/godocs.js
@@ -66,44 +66,32 @@ function godocs_generateTOC() {
   var i;
   for (i = 0; i < navbar.parentNode.childNodes.length; i++) {
     var node = navbar.parentNode.childNodes[i];
-    if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
-      if (!node.id) {
-        node.id = 'tmp_' + i;
-      }
-      var text = godocs_nodeToText(node);
-      if (!text) { continue; }
-
-      var textNode = document.createTextNode(text);
-
-      var link = document.createElement('a');
-      link.href = '#' + node.id;
-      link.appendChild(textNode);
-
-      // Then create the item itself
-      var item = document.createElement('dt');
-
-      item.appendChild(link);
-      toc_items.push(item);
+    if ((node.tagName != 'h2') && (node.tagName != 'H2') &&
+        (node.tagName != 'h3') && (node.tagName != 'H3')) {
+      continue;
     }
-    if ((node.tagName == 'h3') || (node.tagName == 'H3')) {
-      if (!node.id) {
-        node.id = 'tmp_' + i;
-      }
-      var text = godocs_nodeToText(node);
-      if (!text) { continue; }
-
-      var textNode = document.createTextNode(text);
+    if (!node.id) {
+      node.id = 'tmp_' + i;
+    }
+    var text = godocs_nodeToText(node);
+    if (!text) { continue; }
  
-      var link = document.createElement('a');
-      link.href = '#' + node.id;
-      link.appendChild(textNode);
+    var textNode = document.createTextNode(text);
  
-      // Then create the item itself
-      var item = document.createElement('dd');
+    var link = document.createElement('a');
+    link.href = '#' + node.id;
+    link.appendChild(textNode);
  
-      item.appendChild(link);
-      toc_items.push(item);
+    // Then create the item itself
+    var item;
+    if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
+      item = document.createElement('dt');
+    } else { // h3
+      item = document.createElement('dd');
     }
+
+    item.appendChild(link);
+    toc_items.push(item);
   }
  
   if (toc_items.length <= 1) { return; }

コアとなるコードの解説

src/pkg/go/doc/comment.go の変更

このファイルは、GoのドキュメンテーションコメントをHTMLに変換するGo言語のコードです。 最も重要な変更は、anchorID関数の導入と、ToHTML関数内で<h3>タグに動的にid属性を付与するロジックです。

  1. nonAlphaNumRxanchorID関数:

    • nonAlphaNumRxは、正規表現[^a-zA-Z0-9]をコンパイルしたものです。これは、アルファベット(大文字・小文字)と数字以外のすべての文字にマッチします。
    • anchorID(line string)関数は、この正規表現を使って、入力された文字列(見出しのテキスト)から英数字以外の文字をアンダースコア_に置換します。これにより、HTMLのid属性として有効で、かつ見出しの内容をある程度反映したユニークな文字列が生成されます。例えば、「func (c *Client) Do(req *Request) (*Response, error)」のような関数シグネチャが見出しになった場合でも、「func__c__Client__Do_req__Request____Response__error」のような形式でIDが生成され、URLのフラグメントとして利用可能になります。
  2. ToHTML関数内のopHead処理:

    • ToHTML関数は、ドキュメントのブロックタイプ(段落、見出し、整形済みテキストなど)に応じてHTMLを生成します。
    • opHeadケースは、見出しブロックを処理します。
    • 変更前は単に<h3>タグを出力していましたが、変更後は、見出しの最初の行(b.lines[0])からanchorID関数を使ってIDを生成し、それを<h3 id="生成されたID">という形式でHTMLに出力するようにしました。
    • これにより、godocが生成するすべての<h3>見出しに一意のIDが付与され、ブラウザのアンカーリンク機能が利用できるようになります。

lib/godoc/package.html の変更

このファイルは、godocがHTMLページをレンダリングする際に使用するGoテンプレートです。 変更は非常にシンプルですが、その影響は大きいです。

  • manual-navというIDを持つdiv要素(サーバーサイドで生成される目次が含まれる)全体が、{{if .IsPkg}}...{{end}}という条件分岐で囲まれました。
  • .IsPkgは、現在表示しているページがGoパッケージのドキュメントである場合にtrueとなるテンプレート変数です。
  • この変更により、Goコマンドのドキュメント(パッケージではないため.IsPkgfalseとなる)を表示する際には、このmanual-navブロックがHTML出力に含まれなくなります。結果として、コマンドドキュメントからサーバーサイドで生成される冗長な目次が削除されます。

doc/godocs.js の変更

このJavaScriptファイルは、ブラウザ上で動的に目次を生成するクライアントサイドスクリプトです。

  • godocs_generateTOC()関数がリファクタリングされ、h2h3タグの処理が統合されました。
  • 最も重要な変更は、if (!node.id) { node.id = 'tmp_' + i; }という行です。これは、HTML要素にid属性がまだ存在しない場合、JavaScriptが動的にtmp_プレフィックスとインデックス番号を組み合わせた一時的なIDを付与することを意味します。
  • この変更は、src/pkg/go/doc/comment.go<h3>タグにIDが付与されるようになったことと合わせて、クライアントサイドの目次生成がより堅牢になることを保証します。たとえサーバーサイドでIDが付与されなかったとしても、JavaScriptがフォールバックとしてIDを生成するため、目次機能が損なわれることがありません。
  • また、h2タグはdt要素(定義リストの用語)、h3タグはdd要素(定義リストの定義)として目次項目を生成するという既存のロジックは維持されており、これにより階層的な目次が表現されます。

これらの変更が連携することで、godocはより使いやすいドキュメントを生成できるようになりました。

関連リンク

  • Go Issue 2732: https://github.com/golang/go/issues/2732
    • このコミットが修正したとされるIssueです。タイトルは「godoc: add anchors to headings」であり、まさにこのコミットの目的と合致しています。

参考にした情報源リンク