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

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

このコミットは、Go言語のドキュメンテーション生成ツール go/doc における重要な改善を導入しています。具体的には、パッケージや関数のドキュメンテーションコメントから「最初の文(first sentence)」を抽出するロジックを、src/cmd/go/pkg.go から src/pkg/go/doc パッケージへと移動し、より汎用的な Synopsis 関数として再利用可能にしています。さらに、この Synopsis 関数は、一般的な略語(例: "T.S.Eliot" の "T.S.")を正しく処理し、文の区切りをより正確に判断するよう改良されています。

コミット

commit 0c2f3b7ffdae1c796f077c08d0cf4b5e7830ee4a
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Feb 22 10:49:37 2012 -0800

    go/doc: move firstSentence into go/doc
    
    - renamed firstSentence -> Synopsis
    - also deal with common abbreviations
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5676088

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

https://github.com/golang/go/commit/0c2f3b7ffdae1c796f077c08d0cf4b5e7830ee4a

元コミット内容

go/doc: move firstSentence into go/doc - renamed firstSentence -> Synopsis - also deal with common abbreviations

変更の背景

Go言語のドキュメンテーションツール(godoc コマンドなど)は、Goのソースコードに記述されたコメントから自動的にドキュメントを生成します。この際、パッケージや関数、型などの概要を簡潔に表示するために、ドキュメンテーションコメントの「最初の文」を抽出する機能が不可欠です。

以前は、この「最初の文」を抽出するロジックが src/cmd/go/pkg.gofirstSentence 関数として実装されていました。しかし、このロジックは godoc だけでなく、他のドキュメンテーション関連ツールやライブラリでも必要とされる汎用的な機能です。特定のコマンドの内部に閉じ込めておくのではなく、より適切な場所、すなわち go/doc パッケージに移動し、再利用可能な形で提供することが望ましいと判断されました。

また、従来の firstSentence 関数は、文の区切りを単純に「ピリオドの後にスペースが続く場合」と定義していました。このルールは、"T.S.Eliot" のような一般的な略語(イニシャルなど)の後にピリオドが続く場合に、誤って文の終わりと判断してしまうという問題がありました。これにより、生成される概要が不正確になる可能性がありました。このコミットは、この問題を解決し、より賢明な文の区切り判断を導入することを目的としています。

前提知識の解説

  • Go言語のドキュメンテーションシステム (go/doc): Go言語には、ソースコードのコメントから自動的にドキュメントを生成する強力なシステムが組み込まれています。godoc コマンドはその代表的なツールで、Goの標準ライブラリやサードパーティライブラリのドキュメントをブラウザで閲覧できるようにします。このシステムは、コメントの構造(例: パッケージコメント、関数コメント)を解析し、構造化されたドキュメントを生成します。
  • パッケージコメント: Goのソースファイルの一番最初に記述されるパッケージ宣言の直前のコメントは、そのパッケージ全体のドキュメンテーションとして扱われます。通常、パッケージの目的や機能の概要が記述されます。
  • go/build パッケージ: Goのビルドシステムに関する情報を提供するパッケージです。ソースファイルの解析やパッケージの依存関係の解決などに利用されます。このコミットでは、build.Package 構造体の PackageComment フィールドからドキュメントテキストを取得しています。
  • go/token パッケージ: Goのソースコードを解析する際に使用されるトークン(キーワード、識別子、演算子など)やファイル位置に関する情報を提供するパッケージです。
  • unicode パッケージ: Unicode文字のプロパティ(大文字/小文字、数字、記号など)を扱うための機能を提供するパッケージです。このコミットでは、文の区切りを判断する際に unicode.IsUpper を使用して、文字が大文字であるかどうかを判定しています。
  • Synopsis (概要): ドキュメンテーションコメントの最初の文を指します。これは、パッケージや関数の目的を簡潔に伝えるために使用されます。

技術的詳細

このコミットの主要な技術的変更点は、以下の2つです。

  1. firstSentence 関数の go/doc パッケージへの移動とリネーム:

    • 以前 src/cmd/go/pkg.go に存在した firstSentence 関数は、src/pkg/go/doc/synopsis.go という新しいファイルに移動され、Synopsis という名前に変更されました。
    • これにより、ドキュメンテーションの概要抽出ロジックが、Goのドキュメンテーションシステムの中核を担う go/doc パッケージに集約され、他のツールやライブラリからの再利用が容易になりました。
    • src/cmd/go/pkg.gosrc/cmd/godoc/dirtrees.go の両方で、firstSentence の呼び出しが doc.Synopsis の呼び出しに置き換えられています。
  2. Synopsis 関数の文区切りロジックの改善:

    • 新しい Synopsis 関数は、内部で firstSentenceLen というヘルパー関数を使用しています。
    • firstSentenceLen は、文の終わりを判断する際に、単に「ピリオドの後にスペースが続く」だけでなく、「ピリオドの後にスペースが続き、かつそのピリオドの直前がちょうど1つの大文字ではない」という条件を追加しています。
    • この新しいロジックは、unicode.IsUpper 関数を使用して、ピリオドの前の文字が大文字であるかどうかをチェックします。
    • 具体的には、ppp, pp, p という3つの rune 変数を使って、現在の文字 (q) とその直前の2つの文字 (p, pp)、さらにその前の文字 (ppp) を追跡します。
    • q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) という条件が文の終わりを判定します。
      • q == ' ': 現在の文字がスペースである。
      • p == '.': 直前の文字がピリオドである。
      • !unicode.IsUpper(pp) || unicode.IsUpper(ppp): これが新しいロジックの核心です。
        • !unicode.IsUpper(pp): ピリオドの直前の文字 (pp) が大文字ではない場合(例: "foo." の "o")。これは通常の文の終わりです。
        • unicode.IsUpper(ppp): ピリオドの2つ前の文字 (ppp) が大文字である場合(例: "T.S.Eliot" の "T")。これは略語の可能性があり、文の終わりではないと判断されます。
    • この改良により、"T.S.Eliot" のような略語が文の途中で誤って区切られることを防ぎ、より正確な概要抽出が可能になります。
    • また、Synopsis 関数は、抽出された文から改行文字 (\n, \r, \t) を削除し、複数のスペースを単一のスペースに正規化する処理も行います。これにより、整形されたクリーンな概要文字列が生成されます。

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

このコミットで変更された主要なファイルとコードスニペットは以下の通りです。

  1. src/cmd/go/pkg.go:

    • firstSentence 関数が削除されました。
    • import "go/doc" が追加されました。
    • p.Doc = firstSentence(info.PackageComment.Text()) の行が p.Doc = doc.Synopsis(info.PackageComment.Text()) に変更されました。
  2. src/cmd/godoc/dirtrees.go:

    • import "go/doc" が追加されました。
    • synopses[i] = firstSentence(file.Doc.Text()) の行が synopses[i] = doc.Synopsis(file.Doc.Text()) に変更されました。
  3. src/pkg/go/doc/synopsis.go (新規ファイル):

    • firstSentenceLen 関数が追加されました。この関数は、文の長さを計算する主要なロジックを含みます。
      func firstSentenceLen(s string) int {
      	var ppp, pp, p rune
      	for i, q := range s {
      		if q == '\n' || q == '\r' || q == '\t' {
      			q = ' '
      		}
      		if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) {
      			return i
      		}
      		ppp, pp, p = pp, p, q
      	}
      	return len(s)
      }
      
    • Synopsis 関数が追加されました。この関数は firstSentenceLen を呼び出し、結果の文字列を整形します。
      func Synopsis(s string) string {
      	n := firstSentenceLen(s)
      	var b []byte
      	p := byte(' ')
      	for i := 0; i < n; i++ {
      		q := s[i]
      		if q == '\n' || q == '\r' || q == '\t' {
      			q = ' '
      		}
      		if q != ' ' || p != ' ' {
      			b = append(b, q)
      			p = q
      		}
      	}
      	// remove trailing blank, if any
      	if n := len(b); n > 0 && p == ' ' {
      		b = b[0 : n-1]
      	}
      	return string(b)
      }
      
  4. src/pkg/go/doc/synopsis_test.go (新規ファイル):

    • Synopsis 関数と firstSentenceLen 関数の動作を検証するための単体テストが追加されました。特に、略語の処理やスペースの正規化に関するテストケースが含まれています。

コアとなるコードの解説

src/pkg/go/doc/synopsis.go に追加された firstSentenceLen 関数と Synopsis 関数がこのコミットの核心です。

firstSentenceLen(s string) int

この関数は、入力文字列 s の中で最初の文がどこで終わるかを示すインデックスを返します。

  • 文字の追跡: ppp, pp, p という3つの rune 変数を使って、現在の文字 q とその直前の2つの文字、さらにその前の文字を保持します。これにより、ピリオドの前後関係を詳細にチェックできます。
  • 改行・タブのスペース化: q == '\n' || q == '\r' || q == '\t' の条件で、改行やタブ文字をスペースに変換します。これは、文の区切りを判断する際にこれらの文字がノイズにならないようにするためです。
  • 文の区切り判定: if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) が文の終わりを判定する条件です。
    • q == ' ': 現在の文字がスペースであること。
    • p == '.': 直前の文字がピリオドであること。
    • (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)): この部分が略語の処理を改善しています。
      • !unicode.IsUpper(pp): ピリオドの直前の文字 (pp) が大文字でない場合。これは通常の文の終わり(例: "Hello world.")。
      • unicode.IsUpper(ppp): ピリオドの2つ前の文字 (ppp) が大文字である場合。これは略語(例: "T.S.Eliot" の "T.S.")の可能性があり、このピリオドは文の終わりではないと判断されます。この条件が true の場合、文はまだ続いていると見なされます。
  • ループと返り値: 文字列を1文字ずつ走査し、上記の条件が満たされた時点でそのインデックスを返します。最後まで文の区切りが見つからない場合は、文字列全体の長さを返します。

Synopsis(s string) string

この関数は、firstSentenceLen を利用して最初の文を抽出し、さらに整形処理を施します。

  • 文の長さの取得: n := firstSentenceLen(s) で、最初の文の長さを取得します。
  • バイトスライスへの構築: b というバイトスライスに整形された文を構築します。
  • 文字の整形:
    • q == '\n' || q == '\r' || q == '\t' の条件で、改行やタブ文字をスペースに変換します。
    • q != ' ' || p != ' ' の条件で、連続するスペースを単一のスペースに正規化します。これにより、"foo bar" のような文字列が "foo bar" になります。
  • 末尾のスペースの削除: if n := len(b); n > 0 && p == ' ' の条件で、抽出された文の末尾に余分なスペースがある場合にそれを削除します。
  • 文字列への変換: 最終的に整形されたバイトスライス b を文字列に変換して返します。

これらの変更により、Goのドキュメンテーションシステムは、より正確で読みやすいパッケージや関数の概要を生成できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/pkg/go/doc/synopsis.go と関連ファイル)
  • Go言語のコミット履歴 (GitHub)
  • Unicodeの文字プロパティに関する一般的な知識
  • 正規表現における文の区切りに関する一般的な知識 (略語の処理など)
  • Go言語の unicode パッケージのドキュメンテーション: https://pkg.go.dev/unicode