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

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

このコミットは、Go言語のドキュメンテーションツールである godoc の挙動を改善し、特にコマンドのドキュメンテーション表示に関する問題を修正するものです。具体的には、パッケージパスがサブディレクトリのみを指す場合にコマンドのドキュメントを表示するようになり、また cmd/ プレフィックスを使用することでコマンドのドキュメントを強制的に表示できるようになりました。

コミット

commit 3a582a768be7c95cbaf1a450140f367d9b7221d0
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Feb 14 13:57:21 2012 -0800

    godoc: make godoc go work
    
    - if a package path leads to subdirectories only,
      show command instead, if any
    - to force documentation for a command, use the
      cmd/ prefix, as in: godoc cmd/go
      (note that for the go command, the prefix is
      not required since there is no actual go library
      package at the moment)
    
    Fixes #3012.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5665049

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

https://github.com/golang/go/commit/3a582a768be7c95cbaf1a450140f367d9b7221d0

元コミット内容

godoc: make godoc go work

  • パッケージパスがサブディレクトリのみを指す場合、もしあればコマンドを表示する。
  • コマンドのドキュメントを強制的に表示するには、cmd/ プレフィックスを使用する。例: godoc cmd/gogo コマンドの場合、現在 go ライブラリパッケージが存在しないため、プレフィックスは不要です。)

Fixes #3012.

変更の背景

このコミットは、Go Issue #3012「godoc go should show documentation for the go command」を修正するために行われました。

当時の godoc コマンドは、go のような特定のコマンドのドキュメントを表示する際に問題がありました。例えば、godoc go と入力しても、go コマンド自体のドキュメントではなく、go という名前のライブラリパッケージ(もし存在すれば)のドキュメントを探そうとするか、あるいは何も表示しないという挙動でした。これは、godoc がデフォルトでパッケージのドキュメントを優先して検索するためです。

しかし、go コマンドのように、同名のライブラリパッケージが存在しない、あるいは存在してもコマンドのドキュメントの方がユーザーにとって重要である場合、この挙動は不便でした。特に、go コマンドはGoツールチェインの根幹をなすものであり、そのドキュメントに簡単にアクセスできることは非常に重要です。

このコミットは、このような状況下で godoc が意図したコマンドのドキュメントを正しく表示できるようにするための改善を目的としています。

前提知識の解説

godoc コマンド

godoc はGo言語の公式ドキュメンテーションツールです。Goのソースコードに記述されたコメント(特にエクスポートされた識別子に付随するコメント)を解析し、HTML形式またはプレーンテキスト形式でドキュメントを生成します。開発者は godoc を使用して、Goの標準ライブラリやサードパーティのパッケージ、さらには自身のプロジェクトのコードベースのドキュメントを閲覧できます。

godoc は主に以下の2つのモードで動作します。

  1. コマンドラインモード: godoc <package_path> のように実行し、指定されたパッケージやシンボルのドキュメントを標準出力にプレーンテキストで表示します。
  2. HTTPサーバーモード: -http フラグを付けて実行すると、ローカルにドキュメンテーションサーバーを立ち上げ、ウェブブラウザからドキュメントを閲覧できるようになります。これは pkg.go.dev のようなオンラインのGoドキュメンテーションサイトの基盤となっています。

Goのパッケージとコマンド

Goのソースコードは「パッケージ」という単位で構成されます。パッケージは関連する機能の集合であり、再利用可能なコードの単位です。

  • ライブラリパッケージ: 他のパッケージからインポートされて利用されることを目的としたパッケージです。通常、main 関数を含まず、実行可能ファイルとしてはコンパイルされません。
  • コマンドパッケージ: 実行可能なプログラムをビルドすることを目的としたパッケージです。必ず main パッケージに属し、main 関数を含みます。Goツールチェインの go コマンド自体も src/cmd/go というパスにあるコマンドパッケージです。

godoc は、ユーザーが指定したパスがライブラリパッケージを指すのか、それともコマンドパッケージを指すのかを内部的に判断する必要があります。この判断が曖昧な場合や、同名のライブラリパッケージとコマンドパッケージが存在する場合に、どちらのドキュメントを表示すべきかという問題が生じます。

filepath.IsAbs

Go標準ライブラリの path/filepath パッケージに含まれる関数です。 func IsAbs(path string) bool この関数は、与えられたパスが絶対パスであるかどうかを報告します。例えば、/usr/local/bin は絶対パスですが、./mytoolmytool は絶対パスではありません。godoc の内部処理では、ユーザーが入力したパスが絶対パスか相対パスかによって、その後の解決ロジックが変わる可能性があります。

strings.HasPrefix

Go標準ライブラリの strings パッケージに含まれる関数です。 func HasPrefix(s, prefix string) bool この関数は、文字列 s がプレフィックス prefix で始まるかどうかを報告します。このコミットでは、ユーザーが入力したパスが cmd/ で始まるかどうかをチェックするために使用されています。

技術的詳細

このコミットの主要な技術的変更点は、godoc がユーザーから与えられた引数(パス)をどのように解釈し、それがパッケージのドキュメントを指すのか、それともコマンドのドキュメントを指すのかを判断するロジックを改善した点にあります。

1. cmd/ プレフィックスの導入

ユーザーが godoc cmd/go のように cmd/ プレフィックスを付けてパスを指定した場合、godoc はそのパスがコマンドを指していると明示的に解釈するようになりました。これにより、同名のライブラリパッケージが存在する場合でも、コマンドのドキュメントが優先的に表示されます。

2. パッケージとコマンドの検索順序の変更と優先順位付け

以前の godoc は、まず指定されたパスをパッケージとして解決しようとし、それが失敗した場合にのみコマンドとして解決しようとしていました。このコミットでは、このロジックがより洗練されました。

  • 強制コマンドモード: cmd/ プレフィックスが指定された場合 (forceCmdtrue)、最初からコマンドとしてのみ検索を行います。
  • デフォルトの検索: cmd/ プレフィックスがない場合、まずパッケージとして検索を試みます。
  • フォールバックと解決:
    • もしパッケージとして何も見つからず、かつコマンドとして何か見つかった場合、コマンドのドキュメントを表示します。
    • もしパッケージとしてもコマンドとしても何か見つかった場合(つまり、同名のパッケージとコマンドが存在する場合)、godoc は以下のロジックでどちらを表示するかを決定します。
      • もしパッケージの情報がサブディレクトリ情報しか含んでいない場合(info.PAst == nil && info.PDoc == nil)、コマンドのドキュメントを優先します。これは、ユーザーが特定のパッケージのドキュメントを求めているのではなく、そのパスにある実行可能なコマンドのドキュメントを求めている可能性が高いと判断するためです。
      • それ以外の場合(パッケージが実際のドキュメントコンテンツを持っている場合)、パッケージのドキュメントを優先し、ユーザーに対して cmd/ プレフィックスを使ってコマンドのドキュメントを明示的に要求するよう促すメッセージを表示します。

3. go コマンドの特殊な扱い

コミットメッセージにもあるように、go コマンドの場合、現時点では同名のライブラリパッケージが存在しないため、cmd/ プレフィックスなしで godoc go と入力しても go コマンドのドキュメントが表示されるようになりました。これは、上記の検索ロジックの結果として自然に実現されます。

これらの変更により、godoc はより直感的で期待通りのドキュメント表示を提供するようになりました。

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

このコミットでは、主に以下の2つのファイルが変更されています。

  1. src/cmd/godoc/doc.go: godoc コマンドのドキュメンテーション文字列が更新され、cmd/ プレフィックスの新しい挙動が説明されています。
  2. src/cmd/godoc/main.go: godoc の主要なロジックが含まれるファイルで、パスの解析、パッケージとコマンドの検索、そしてどちらのドキュメントを表示するかを決定するロジックが大幅に変更されています。

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

--- a/src/cmd/godoc/doc.go
+++ b/src/cmd/godoc/doc.go
@@ -9,12 +9,15 @@ Godoc extracts and generates documentation for Go programs.
 It has two modes.
 
 Without the -http flag, it runs in command-line mode and prints plain text
-documentation to standard output and exits. If the -src flag is specified,
-godoc prints the exported interface of a package in Go source form, or the
-implementation of a specific exported language entity:\n
+documentation to standard output and exits. If both a library package and
+a command with the same name exists, using the prefix cmd/ will force
+documentation on the command rather than the library package. If the -src
+flag is specified, godoc prints the exported interface of a package in Go
+source form, or the implementation of a specific exported language entity:\n
 
  	godoc fmt                # documentation for package fmt
  	godoc fmt Printf         # documentation for fmt.Printf
+\tgodoc cmd/go             # force documentation for the go command
  	godoc -src fmt           # fmt package interface in Go source form
  	godoc -src fmt Printf    # implementation of fmt.Printf
 

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

--- a/src/cmd/godoc/main.go
+++ b/src/cmd/godoc/main.go
@@ -374,11 +374,16 @@ func main() {
 	}
 
 	// determine paths
+	const cmdPrefix = "cmd/"
 	path := flag.Arg(0)
-	if len(path) > 0 && path[0] == '.' {
+	var forceCmd bool
+	if strings.HasPrefix(path, ".") {
 		// assume cwd; don't assume -goroot
 		cwd, _ := os.Getwd() // ignore errors
 		path = filepath.Join(cwd, path)
+	} else if strings.HasPrefix(path, cmdPrefix) {
+		path = path[len(cmdPrefix):]
+		forceCmd = true
 	}
 	relpath := path
 	abspath := path
@@ -393,6 +398,7 @@ func main() {
 
 	var mode PageInfoMode
 	if relpath == builtinPkgPath {
+		// the fake built-in package contains unexported identifiers
 		mode = noFiltering
 	}
 	if *srcMode {
@@ -404,20 +410,35 @@ func main() {
 	// TODO(gri): Provide a mechanism (flag?) to select a package
 	//            if there are multiple packages in a directory.
-	info := pkgHandler.getPageInfo(abspath, relpath, "", mode)
+	// first, try as package unless forced as command
+	var info PageInfo
+	if !forceCmd {
+		info = pkgHandler.getPageInfo(abspath, relpath, "", mode)
+	}
+
+	// second, try as command
+	if !filepath.IsAbs(path) {
+		abspath = absolutePath(path, cmdHandler.fsRoot)
+	}
+	cinfo := cmdHandler.getPageInfo(abspath, relpath, "", mode)
+
+	// determine what to use
 	if info.IsEmpty() {
-		// try again, this time assume it's a command
-		if !filepath.IsAbs(path) {
-			abspath = absolutePath(path, cmdHandler.fsRoot)
+		if !cinfo.IsEmpty() {
+			// only cinfo exists - switch to cinfo
+			info = cinfo
 		}
-		cmdInfo := cmdHandler.getPageInfo(abspath, relpath, "", mode)
-		// only use the cmdInfo if it actually contains a result
-		// (don't hide errors reported from looking up a package)
-		if !cmdInfo.IsEmpty() {
-			info = cmdInfo
+	} else if !cinfo.IsEmpty() {
+		// both info and cinfo exist - use cinfo if info
+		// contains only subdirectory information
+		if info.PAst == nil && info.PDoc == nil {
+			info = cinfo
+		} else {
+			fmt.Printf("use 'godoc %s%s' for documentation on the %s command \n\n", cmdPrefix, relpath, relpath)
 		}
 	}
+
 	if info.Err != nil {
 		log.Fatalf("%v", info.Err)
 	}

コアとなるコードの解説

src/cmd/godoc/doc.go

このファイルは godoc コマンドのヘルプメッセージを定義しています。変更点としては、godoc cmd/go の例が追加され、cmd/ プレフィックスを使用することでコマンドのドキュメントを強制的に表示できるようになったことが明記されています。これはユーザーに対する新しい機能の告知であり、ドキュメントの正確性を保つ上で重要です。

src/cmd/godoc/main.go

このファイルには godoc コマンドの main 関数が含まれており、ドキュメントの取得と表示の主要なロジックが実装されています。

  1. const cmdPrefix = "cmd/" の追加: cmd/ プレフィックスを定数として定義し、コードの可読性と保守性を向上させています。

  2. var forceCmd bool の追加と strings.HasPrefix(path, cmdPrefix) による判定: ユーザーが入力したパスが cmd/ で始まるかどうかを strings.HasPrefix でチェックし、もしそうであれば forceCmd フラグを true に設定します。これにより、後続のドキュメント検索ロジックでコマンドのドキュメントを優先するようになります。また、パスから cmd/ プレフィックスを取り除き、実際のパッケージ/コマンド名のみを path 変数に残します。

  3. ドキュメント検索ロジックの変更:

    • if !forceCmd { info = pkgHandler.getPageInfo(abspath, relpath, "", mode) }: forceCmdfalse の場合(つまり cmd/ プレフィックスが指定されていない場合)、まず指定されたパスを通常のパッケージとして pkgHandler.getPageInfo を使って検索します。
    • cinfo := cmdHandler.getPageInfo(abspath, relpath, "", mode): 次に、指定されたパスをコマンドとして cmdHandler.getPageInfo を使って検索します。この検索は、forceCmd の値に関わらず常に実行されます。
    • if info.IsEmpty() { ... } else if !cinfo.IsEmpty() { ... }: info (パッケージ情報) と cinfo (コマンド情報) の両方が取得された後、どちらのドキュメントを表示すべきかを決定する複雑なロジックが導入されています。
      • info.IsEmpty() の場合: パッケージ情報が見つからなかった場合、もしコマンド情報 (cinfo) が存在すれば、それを info に代入して表示します。
      • !cinfo.IsEmpty() の場合: パッケージ情報 (info) もコマンド情報 (cinfo) も両方存在する場合の処理です。
        • if info.PAst == nil && info.PDoc == nil: これは、info が実際のパッケージドキュメントコンテンツを含まず、単にサブディレクトリ情報のみを指している場合に true となります。この場合、ユーザーはコマンドのドキュメントを求めている可能性が高いと判断し、infocinfo (コマンド情報) で上書きします。
        • else { fmt.Printf(...) }: 上記の条件が false の場合、つまり info が実際のパッケージドキュメントコンテンツを持っている場合、パッケージのドキュメントを優先し、ユーザーに対して cmd/ プレフィックスを使ってコマンドのドキュメントを明示的に要求するよう促すメッセージを表示します。

これらの変更により、godoc はユーザーの意図をより正確に推測し、適切なドキュメントを表示できるようになりました。特に、cmd/ プレフィックスによる明示的な指定と、パッケージとコマンドの検索結果を比較して最適なものを選択するロジックが、この改善の核心です。

関連リンク

参考にした情報源リンク